Reputation: 1245
I have this input image (feel free to download it and try your solution, please):
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:
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:
It gives wrong output:
Upvotes: 3
Views: 645
Reputation: 991
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:
I hope this is robust enough
Upvotes: 2
Reputation: 817
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)
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]
Upvotes: 2