Spaces:
Sleeping
Sleeping
| """ | |
| https://www.pyimagesearch.com/2015/04/06/zero-parameter-automatic-canny-edge-detection-with-python-and-opencv/ | |
| """ | |
| import cv2 | |
| import numpy as np | |
| from src.logger import logger | |
| from src.processors.interfaces.ImagePreprocessor import ImagePreprocessor | |
| from src.utils.image import ImageUtils | |
| from src.utils.interaction import InteractionUtils | |
| MIN_PAGE_AREA = 80000 | |
| def normalize(image): | |
| return cv2.normalize(image, 0, 255, norm_type=cv2.NORM_MINMAX) | |
| def check_max_cosine(approx): | |
| # assumes 4 pts present | |
| max_cosine = 0 | |
| min_cosine = 1.5 | |
| for i in range(2, 5): | |
| cosine = abs(angle(approx[i % 4], approx[i - 2], approx[i - 1])) | |
| max_cosine = max(cosine, max_cosine) | |
| min_cosine = min(cosine, min_cosine) | |
| if max_cosine >= 0.35: | |
| logger.warning("Quadrilateral is not a rectangle.") | |
| return False | |
| return True | |
| def validate_rect(approx): | |
| return len(approx) == 4 and check_max_cosine(approx.reshape(4, 2)) | |
| def angle(p_1, p_2, p_0): | |
| dx1 = float(p_1[0] - p_0[0]) | |
| dy1 = float(p_1[1] - p_0[1]) | |
| dx2 = float(p_2[0] - p_0[0]) | |
| dy2 = float(p_2[1] - p_0[1]) | |
| return (dx1 * dx2 + dy1 * dy2) / np.sqrt( | |
| (dx1 * dx1 + dy1 * dy1) * (dx2 * dx2 + dy2 * dy2) + 1e-10 | |
| ) | |
| class CropPage(ImagePreprocessor): | |
| def __init__(self, *args, **kwargs): | |
| super().__init__(*args, **kwargs) | |
| cropping_ops = self.options | |
| self.morph_kernel = tuple( | |
| int(x) for x in cropping_ops.get("morphKernel", [10, 10]) | |
| ) | |
| def apply_filter(self, image, file_path): | |
| image = normalize(cv2.GaussianBlur(image, (3, 3), 0)) | |
| # Resize should be done with another preprocessor is needed | |
| sheet = self.find_page(image, file_path) | |
| if len(sheet) == 0: | |
| logger.error( | |
| f"\tError: Paper boundary not found for: '{file_path}'\nHave you accidentally included CropPage preprocessor?" | |
| ) | |
| return None | |
| logger.info(f"Found page corners: \t {sheet.tolist()}") | |
| # Warp layer 1 | |
| image = ImageUtils.four_point_transform(image, sheet) | |
| # Return preprocessed image | |
| return image | |
| def find_page(self, image, file_path): | |
| config = self.tuning_config | |
| image = normalize(image) | |
| _ret, image = cv2.threshold(image, 200, 255, cv2.THRESH_TRUNC) | |
| image = normalize(image) | |
| kernel = cv2.getStructuringElement(cv2.MORPH_RECT, self.morph_kernel) | |
| # Close the small holes, i.e. Complete the edges on canny image | |
| closed = cv2.morphologyEx(image, cv2.MORPH_CLOSE, kernel) | |
| edge = cv2.Canny(closed, 185, 55) | |
| if config.outputs.show_image_level >= 5: | |
| InteractionUtils.show("edge", edge, config=config) | |
| # findContours returns outer boundaries in CW and inner ones, ACW. | |
| cnts = ImageUtils.grab_contours( | |
| cv2.findContours(edge, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) | |
| ) | |
| # convexHull to resolve disordered curves due to noise | |
| cnts = [cv2.convexHull(c) for c in cnts] | |
| cnts = sorted(cnts, key=cv2.contourArea, reverse=True)[:5] | |
| sheet = [] | |
| for c in cnts: | |
| if cv2.contourArea(c) < MIN_PAGE_AREA: | |
| continue | |
| peri = cv2.arcLength(c, True) | |
| approx = cv2.approxPolyDP(c, epsilon=0.025 * peri, closed=True) | |
| if validate_rect(approx): | |
| sheet = np.reshape(approx, (4, -1)) | |
| cv2.drawContours(image, [approx], -1, (0, 255, 0), 2) | |
| cv2.drawContours(edge, [approx], -1, (255, 255, 255), 10) | |
| break | |
| return sheet | |