Source code for improutils.preprocessing.contours

import cv2
import numpy as np


[docs] def contour_to_image(contour, img_bin, size=None): """Create a new image from the contour. Parameters ---------- contour : ndarray Contour that represents the area from image to be cropped. img_bin : ndarray Input binary image. size : tuple Optional size of the created image. If it's not used, the image's size is the same as the size of bounding rectangle of the input contour. Returns ------- Output cropped image. """ if size is None: _, _, w, h = cv2.boundingRect(contour) size = (w, h) assert type(size) is tuple, "Param size should be a tuple!" blank = np.zeros_like(img_bin) half_x = int(size[0] * 0.5) half_y = int(size[1] * 0.5) c = get_center(contour) cv2.drawContours(blank, [contour], -1, (255, 255, 255), cv2.FILLED) return blank[c[1] - half_y : c[1] + half_y, c[0] - half_x : c[0] + half_x].copy()
[docs] def find_contours(img_bin, min_area=0, max_area=np.inf, fill=True, external=True): """Find contours in a binary image and filter them by area. The function counts the filtered contours and draws them on a new binary image. Contours can be optionally filled or just outlined, and either external or all contours can be considered. Parameters ---------- img_bin : ndarray Input binary image. min_area : int, optional Minimum contour area to keep (smaller contours are discarded). Default is 0. max_area : int or float, optional Maximum contour area to keep (larger contours are discarded). Default is np.inf. fill : bool, optional If True, filled contours are drawn; if False, contours are drawn as outlines. Default is True. external : bool, optional If True, only external contours are considered; if False, all contours are considered. Default is True. Returns ------- contour_drawn : ndarray Output binary image with drawn filtered contours. count : int Number of filtered contours. contours : list of ndarray List of filtered contour arrays. """ mode = cv2.RETR_EXTERNAL if not external: mode = cv2.RETR_LIST contours, _ = cv2.findContours(img_bin, mode, cv2.CHAIN_APPROX_SIMPLE) contours = [ c for c in contours if cv2.contourArea(c) > min_area and cv2.contourArea(c) < max_area ] thick = cv2.FILLED if not fill: thick = 2 contour_drawn = cv2.drawContours( np.zeros(img_bin.shape, dtype=np.uint8), contours, -1, color=(255, 255, 255), thickness=thick, ) return contour_drawn, len(contours), contours
[docs] def fill_holes(img_bin, close=False, size=5): """Fill the holes in found contours. It could merge the contour using close input with appropriate size. Parameters ---------- img_bin : ndarray Input binary image. close : boolean If it should merge contours with missing points using close operation. size : int Size of the close operation element. fill : bool, optional If True, filled contours are drawn; if False, contours are drawn as outlines. Default is True. Returns ------- Output binary image. """ if close: struct = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (size, size)) img_bin = cv2.morphologyEx(img_bin, cv2.MORPH_CLOSE, struct) res, _, _ = find_contours(img_bin) return res
[docs] def find_holes(img_bin, min_area=0, max_area=np.inf, fill=True): """Find inner contours (holes) in a binary image and filter them by area. The function identifies contours that are inside other contours (holes), filters them based on the specified minimum and maximum area, and draws them on a new binary image. Contours can be optionally filled or outlined. Parameters ---------- img_bin : ndarray Input binary image. min_area : int, optional Minimum contour area to keep (smaller contours are discarded). Default is 0. max_area : int or float, optional Maximum contour area to keep (larger contours are discarded). Default is np.inf. fill : bool, optional If True, filled contours are drawn; if False, contours are drawn as outlines. Default is True. Returns ------- contours_drawn : ndarray Output binary image with drawn filtered hole contours. count : int Number of filtered hole contours. contours : list of ndarray List of filtered hole contour arrays. """ contours, hierarchy = cv2.findContours( img_bin, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE ) # filter out only hole contours (contours that are inside another contour) # for more info about hierarchy retrieval modes visit: https://docs.opencv.org/4.x/d9/d8b/tutorial_py_contours_hierarchy.html hole_indices = [i for i in range(len(hierarchy[0])) if hierarchy[0, i, -1] != -1] # filter out contours by area contours = [ contours[hole_index] for hole_index in hole_indices if min_area < cv2.contourArea(contours[hole_index]) <= max_area ] # draw contours thick = cv2.FILLED if not fill: thick = 2 contours_drawn = cv2.drawContours( np.zeros(img_bin.shape, dtype=np.uint8), contours, -1, color=(255, 255, 255), thickness=thick, ) return contours_drawn, len(contours), contours
[docs] def get_center(contour): """Get the center of a contour in pixels in tuple format. Parameters ---------- contour : ndarray input contour. Returns ------- A tuple with x and y coordinates of the contour's center. """ M = cv2.moments(contour) cX = int(M["m10"] / M["m00"]) cY = int(M["m01"] / M["m00"]) return cX, cY