Source code for ovl.math.contours

import typing
from functools import reduce
from typing import Tuple

import cv2
import numpy as np

from .geometry import distance_between_points, law_of_cosine
from .shape_fill_ratios import circle_fill_ratio
from ..image_filters.image_filters import crop_image


[docs]def target_size(contours: typing.List[np.ndarray]) -> float: """ Returns the sum of contour areas of a list of contours """ return sum(list(map(cv2.contourArea, contours)))
[docs]def open_arc_length(contour: np.ndarray) -> float: """ Returns the arc length of an "open" contour """ return cv2.arcLength(contour, False)
[docs]def open_contour_approximation(contour: np.ndarray, approximation_coefficient: float = 0.02): """ Returns the vertices of the approximation of the contour NOTE: for "open" (unclosed shape) contours only! :param contour: open contour (numpy array) :param approximation_coefficient: approximation coefficient :return: list of vertices """ arc = approximation_coefficient * cv2.arcLength(contour, False) return cv2.approxPolyDP(contour, arc, False)
[docs]def contour_center(contour: np.ndarray) -> Tuple[float, float]: """ Returns the coordinates center of a contour :param contour: a single contour :return: X coordinate of center, Y coordinate of center """ moments = cv2.moments(contour) try: area = float(moments['m00']) center_x, center_y = moments['m10'] / area, moments['m01'] / area except ZeroDivisionError: raise ValueError('Contour given is too small!,' 'try using area_filter to remove small contours (try min_area=20)') return center_x, center_y
def _contour_center_sum(point_sum, contour): """ Helper function for contour_average_center """ def _average_point_reduce(first_point, second_point): return first_point[0] + second_point[0], first_point[1] + second_point[1] current_contour_center = contour_center(contour) return _average_point_reduce(point_sum, current_contour_center)
[docs]def contour_average_center(contours) -> Tuple[float, float]: """ Calculates the average center of a list of contours :param contours: the list of contours :return: the average center (x,y) """ contour_amount = len(contours) point_sum = reduce(_contour_center_sum, contours, (0, 0)) average = (point_sum[0] / contour_amount) return average
[docs]def contour_approximation(contour, approximation_coefficient=0.02): """ Returns the approximation of the contour :param contour: a numpy array :param approximation_coefficient: the coefficient of the contour length approximation :return: """ perimeter = cv2.arcLength(contour, True) approximation = cv2.approxPolyDP(contour, approximation_coefficient * perimeter, True) return approximation
[docs]def contour_lengths_and_angles(contour, approximation_coefficient=0.02): """ Returns a list of lengths and angles of a given contour. :param contour: contour (numpy array) to find its :param approximation_coefficient: :return: returns the list of sides """ approximation = contour_approximation(contour, approximation_coefficient) vertices = [] lengths = [] angles = [] for points in approximation: vertex = points[0][1], points[0][0] vertices.append(vertex) for index, point in enumerate(vertices): vertex_index = index + 1 if index != len(approximation) - 1 else 0 current_length = distance_between_points(point, vertices[vertex_index]) current_angle = law_of_cosine(vertices[index - 1], point, vertices[vertex_index]) lengths.append(current_length) angles.append(current_angle) return vertices, lengths, angles
[docs]def calculate_normalized_screen_space(contours: typing.Union[typing.List[np.ndarray], np.ndarray], image): """ :param contours: :param image: :return: """ height, width, _ = image.shape x, y = contour_average_center(contours) x /= width / 2 y /= height / 2 return x - 1, y - 1
[docs]def circle_rating(contour, area_factor=0.9, radius_factor=0.8): """ Returns a rating of how close is the circle to being a circle :param contour: the contour that its rating is calculated :param area_factor: the factor (p-value) that separates an area ratio that is a circle and one that isn't :param radius_factor: the factor (p-value) that separates an radius ratio that is a circle and one that isn't :return: the circle rating for the given contour """ fill_ratio, radius = circle_fill_ratio(contour) _, _, width, height = cv2.boundingRect(contour) radius_ratio = ((((radius * 2) ** 2) / (float(width) * height)) ** 0.5) rating = (radius_ratio * radius_factor) * (fill_ratio * area_factor) return rating
[docs]def crop_contour_region(contour: np.ndarray, image: np.ndarray): """ Returns a region of the image where the contour (using its bounding box) :param contour: the object found in the image, a numpy array :param image: the image from which the contour was found :return: the image region that contains the contour """ corner_x, corner_y, width, height = cv2.boundingRect(contour) return crop_image(image, (corner_x, corner_y), (width, height))
[docs]def contour_average_color(contours: typing.List[np.ndarray], image: np.ndarray): """ Calculates the average color (in hsv) of all the pixels of a list of contours in the image. :param contours: a list of contour(s) detected in the image :param image: the image where the contours where detected, numpy array :return: hsv color (h, s, v) tuple of the average color """ hsv = cv2.cvtColor(image, cv2.COLOR_RGB2HSV) mask = np.zeros(image.shape, np.uint8) if isinstance(contours, np.ndarray): contours = [contours] cv2.drawContours(mask, contours, -1, 255, -1) return cv2.mean(hsv, mask)