Created
June 29, 2018 14:14
-
-
Save aplz/efc8a01e5e667d051f3aee6117d9b5c0 to your computer and use it in GitHub Desktop.
Crop rectangular object from image.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# working version of https://bretahajek.com/2017/01/scanning-documents-photos-opencv/ | |
import cv2 | |
import numpy as np | |
def resize(img, height=800): | |
""" Resize image to given height """ | |
ratio = height / img.shape[0] | |
return cv2.resize(img, (int(ratio * img.shape[1]), height)) | |
def fourCornersSort(pts): | |
""" Sort corners: top-left, bot-left, bot-right, top-right """ | |
# Difference and sum of x and y values | |
diffs_yx = np.diff(pts, axis=1) | |
sums_xy = pts.sum(axis=1) | |
# Top-left point has smallest sum... | |
# np.argmin() returns INDEX of min | |
return np.array([pts[np.argmin(sums_xy)], | |
pts[np.argmax(diffs_yx)], | |
pts[np.argmax(sums_xy)], | |
pts[np.argmin(diffs_yx)]]) | |
def crop(input_path, minimum_percentage=0.1, show=False): | |
""" | |
:param input_path: the path to the image to process. | |
:param minimum_percentage: the item area should have at least 'minimum_percentage' of the overall image area. | |
Defaults to 0.1, i.e. 10 percent. If no such area can be found, return the original image. | |
:param show: if true, intermediate results are shown. | |
:return: the cropped image. | |
""" | |
image = cv2.imread(input_path) | |
img = resize(image) | |
if show: | |
cv2.imshow("original, resized", img) | |
cv2.waitKey(0) | |
# convert to grayscale | |
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) | |
# Bilateral filter preserves edges | |
gray = cv2.bilateralFilter(gray, 9, 75, 75) | |
# Create black and white image based on adaptive threshold | |
gray = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 115, 4) | |
# Median filter clears small details | |
gray = cv2.medianBlur(gray, 11) | |
edges = cv2.Canny(gray, 200, 250) | |
_, contours, _ = cv2.findContours(edges.copy(), cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) | |
if show: | |
cv2.drawContours(img, contours, -1, [0, 255, 0], 2) | |
cv2.imshow('Contours', img) | |
cv2.waitKey(0) | |
height = edges.shape[0] | |
width = edges.shape[1] | |
MAX_CONTOUR_AREA = width * height | |
maxAreaFound = MAX_CONTOUR_AREA * minimum_percentage | |
pageContour = None | |
# Go through all contours | |
for cnt in contours: | |
# Simplify contour | |
perimeter = cv2.arcLength(cnt, True) | |
approx = cv2.approxPolyDP(cnt, 0.03 * perimeter, True) | |
approx_area = cv2.contourArea(approx) | |
if (len(approx) == 4 and cv2.isContourConvex(approx) # item has 4 corners and is convex | |
and maxAreaFound < approx_area < MAX_CONTOUR_AREA): # item area must be bigger than maxAreaFound | |
maxAreaFound = approx_area | |
pageContour = approx | |
if pageContour is None: | |
return image | |
else: | |
# Sort and offset corners | |
pageContour = fourCornersSort(pageContour[:, 0]) | |
# Recalculate to original scale - start Points | |
sPoints = pageContour.dot(image.shape[0] / 800) | |
# Using Euclidean distance | |
# Calculate maximum height (maximal length of vertical edges) and width | |
height = max(np.linalg.norm(sPoints[0] - sPoints[1]), | |
np.linalg.norm(sPoints[2] - sPoints[3])) | |
width = max(np.linalg.norm(sPoints[1] - sPoints[2]), | |
np.linalg.norm(sPoints[3] - sPoints[0])) | |
# Create target points | |
tPoints = np.array([[0, 0], [0, height], [width, height], [width, 0]], np.float32) | |
# getPerspectiveTransform() needs float32 | |
if sPoints.dtype != np.float32: | |
sPoints = sPoints.astype(np.float32) | |
# Warping perspective | |
M = cv2.getPerspectiveTransform(sPoints, tPoints) | |
cropped = cv2.warpPerspective(image, M, (int(width), int(height))) | |
if show: | |
cv2.imshow("cropped", cropped) | |
cv2.waitKey(0) | |
return cropped | |
if __name__ == '__main__': | |
input_file = "original.jpg" | |
output_file = "cropped.jpg" | |
result = crop(input_file, 0.1, True) | |
cv2.imwrite(output_file, result) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment