how to cut image according to two points in opencv?

Question:

I have this input image (feel free to download it and try your solution, please):

enter image description here

I need to find points A and B that are closest to the left down and right upper corner. And than I would like to cut of the image. See desired output:

enter image description here

So far I have this function, but it does not find points A, B correctly:


def CheckForLess(list1, val):
    return(all(x < val for x in list1))

def find_corner_pixels(img):
    # Get image dimensions
    height, width = img.shape[:2]

    # Find the first non-black pixel closest to the left-down and right-up corners
    nonempty = []


    for i in range(height):
        for j in range(width):
            # Check if the current pixel is non-black
            if not CheckForLess(img[i, j], 10):
                nonempty.append([i, 1080 - j])


    return min(nonempty) , max(nonempty)

Can you help me please?


The solution provided by Achille works on one picture, but if I change input image to this:

enter image description here

It gives wrong output:

enter image description here

Asked By: vojtam

||

Answers:

I’m a bit rusted, haven’t practiced opencv2 for a long time but this is what I came up with:

import numpy as np
import cv2

img = cv2.imread("book.png")
timg = img.copy()

cv2.imshow("img", img)

# Get a mask to get only the colour you need (cover of the book)

hsv_img = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)

lower = np.array([10, 150, 150])

upper = np.array([35, 255, 255])

mask = cv2.inRange(hsv_img, lower, upper)

masked = cv2.bitwise_and(hsv_img, hsv_img, mask=mask)    

img[mask == 0] = 255


cv2.imshow("mask", img)

# Find contours of the masked image
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

ret, thresh = cv2.threshold(gray, 128, 255, cv2.THRESH_BINARY)
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
# For some reason, first contour was the entire screen so only take the second rectangle
contours = sorted(contours, key=cv2.contourArea, reverse=True)[1:2]

for cnt in contours:
    x, y, w, h = cv2.boundingRect(cnt)
    
    # get the corners of the rectangle
    top_left = (x, y)
    top_right = (x + w, y)
    bottom_right = (x + w, y + h)
    bottom_left = (x, y + h)

height, width = img.shape[:2]

pt1 = (0, top_left[1]) 
pt2 = (width, top_left[1])
pt3 = (0, bottom_left[1]) 
pt4 = (width, bottom_left[1])
cv2.line(timg, pt1, pt2, [10, 150, 150],1 )
cv2.line(timg, pt3, pt4, [10, 150, 150], 1)

cv2.imshow("Bounding Rectangles", timg)

cv2.waitKey(0)

enter image description here

hope this helps (Note that you could retrieve only the book by getting the content of the contours

Then, cropping is really easy

# Select the area to crop
cropped = img[y1:y2, x1:x2]
Answered By: Achille G

I noticed that your image has an alpha mask that already segment the foreground. This imply using the flag cv.IMREAD_UNCHANGED when reading the image with openCV (cv.imread(filename, cv.IMREAD_UNCHANGED)).

If this is the case you can have a try to the following:

import sys
from typing import Tuple

import cv2 as cv
import numpy as np


class DetectROI:
    def __init__(self,
                 alpha_threshold: int = 125,
                 display: bool = False,
                 gaussian_sigma: float = 1.,
                 gaussian_window: Tuple[int, int] = (3, 3),
                 relative_corner: float = 0.25,
                 relative_line_length: float = 0.25,
                 relative_max_line_gap: float = 0.02,
                 working_size: Tuple[int, int] = (256, 256)):
        self.alpha_threshold = alpha_threshold
        self.display = display
        self.working_size = working_size
        self.gaussian_sigma = gaussian_sigma
        self.gaussian_window = gaussian_window
        self.relative_line_length = relative_line_length
        self.relative_max_line_gap = relative_max_line_gap
        self.relative_corner = relative_corner

        self._origin: Tuple[int, int] = (0, 0)
        self._src_shape: Tuple[int, int] = (0, 0)

    def __call__(self, src):
        # get cropped contour
        cnt_img = self.get_cropped_contour(src)

        left_lines, right_lines = self.detect_lines(cnt_img)

        x, y, w, h = self.get_bounding_rectangle(left_lines + right_lines)

        # top_left = (x, y)
        top_right = (x + w, y)
        # bottom_right = (x + w, y + h)
        bottom_left = (x, y + h)

        if self.display:
            src = cv.rectangle(src, bottom_left, top_right, (0, 0, 255, 255), 3)
            cv.namedWindow("Source", cv.WINDOW_KEEPRATIO)
            cv.imshow("Source", src)
            cv.waitKey()
        return bottom_left, top_right

    def get_cropped_contour(self, src):
        self._src_shape = tuple(src.shape[:2])
        msk = np.uint8((src[:, :, 3] > self.alpha_threshold) * 255)
        msk = cv.resize(msk, self.working_size)
        cnt, _ = cv.findContours(msk, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
        cnt_img = cv.drawContours(np.zeros_like(msk), cnt, 0, (255,))

        cnt = cnt[0]
        x, y, w, h = cv.boundingRect(np.array(cnt))

        top_left = (x, y)
        # top_right = (x + w, y)
        bottom_right = (x + w, y + h)
        # bottom_left = (x, y + h)
        self._origin = top_left

        cnt_img = cnt_img[self._origin[1]:bottom_right[1], self._origin[0]:bottom_right[0]]

        if self.display:
            cv.namedWindow("Contours", cv.WINDOW_KEEPRATIO)
            cv.imshow("Contours", cnt_img)
        return cnt_img

    def detect_lines(self, img):
        img = cv.GaussianBlur(img, self.gaussian_window, self.gaussian_sigma)

        lines = cv.HoughLinesP(img, 1, np.pi / 180, 50, 50,
                               int(self.relative_line_length*img.shape[0]),
                               int(self.relative_max_line_gap*img.shape[0]))

        if self.display:
            lines_img = np.repeat(img[:, :, None], 3, axis=2)
            if lines is not None:
                for i in range(0, len(lines)):
                    l = lines[i][0]
                    cv.line(lines_img, (l[0], l[1]), (l[2], l[3]), (255, 0, 0), 2, cv.LINE_AA)

        # keep lines close to bottom left and bottom right images
        corner = self.relative_corner
        left_lines = []
        right_lines = []
        if lines is not None:
            # left side
            for i in range(0, len(lines)):
                l = lines[i][0]
                if (l[1] > (1 - corner) * img.shape[1] and l[0] < corner * img.shape[0]) 
                        or (l[3] > (1 - corner) * img.shape[1] and l[2] < corner * img.shape[0]):
                    left_lines.append(l)
                elif (l[1] > (1 - corner) * img.shape[1] and l[0] > (1 - corner) * img.shape[0]) 
                        or (l[3] > (1 - corner) * img.shape[1] and l[2] > (1 - corner) * img.shape[0]):
                    right_lines.append(l)

        if self.display:
            if lines is not None:
                for l in left_lines + right_lines:
                    cv.line(lines_img, (l[0], l[1]), (l[2], l[3]), (0, 0, 255), 2, cv.LINE_AA)
            cv.namedWindow("Contours", cv.WINDOW_KEEPRATIO)
            cv.imshow("Contours", lines_img)
        return left_lines, right_lines

    def get_bounding_rectangle(self, lines):

        cnt = sum(([(l[0], l[1]), (l[2], l[3])] for l in lines), [])
        x, y, w, h = cv.boundingRect(np.array(cnt))

        x += self._origin[0]
        y += self._origin[1]
        y = np.int32(np.round(y * self._src_shape[0] / self.working_size[0]))
        h = np.int32(np.round(h * self._src_shape[0] / self.working_size[0]))
        x = np.int32(np.round(x * self._src_shape[1] / self.working_size[1]))
        w = np.int32(np.round(w * self._src_shape[1] / self.working_size[1]))

        return x, y, w, h


def main(argv):
    default_file = r'book.png'
    filename = argv[0] if len(argv) > 0 else default_file
    src = cv.imread(filename, cv.IMREAD_UNCHANGED)

    detector = DetectROI(display=True)
    return detector(src)


if __name__ == "__main__":
    print("bottom_left: {}, top_right: {}".format(*main(sys.argv[1:])))

The underlying idea is the following:

  1. threshold the alpha mask to get the foreground
  2. compute the contour of the alpha mask
  3. detect the lines (assuming the right and left border to be rather strait)
  4. keep the lines that start from the bottom left and the bottom right of the image (drawn in red)

enter image description here

Here is the obtained result
enter image description here

I hope this is robust enough

Answered By: Remi Cuingnet
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.