def __init__(self, corners: geometry_utils.Polygon, horizontal_cells: int, vertical_cells: int, image: np.ndarray, save_path: typing.Optional[pathlib.PurePath] = None): """Initiate a new Grid. Corners should be clockwise starting from the top left - if not, the grid will have unexpected behavior. If `save_path` is provided, will save the resulting image to this location as "grid.jpg". Used for debugging purposes.""" self.corners = corners self.horizontal_cells = horizontal_cells self.vertical_cells = vertical_cells self._to_grid_basis, self._from_grid_basis = geometry_utils.create_change_of_basis( corners[0], corners[3], corners[2]) self.horizontal_cell_size = 1 / self.horizontal_cells self.vertical_cell_size = 1 / self.vertical_cells self.image = image if save_path: image_utils.save_image(save_path / "grid.jpg", self.draw_grid())
def find_corner_marks(image: np.ndarray, save_path: typing.Optional[pathlib.PurePath] = None ) -> geometry_utils.Polygon: all_polygons: typing.List[ geometry_utils.Polygon] = image_utils.find_polygons( image, save_path=save_path) # Even though the LMark and SquareMark classes check length, it's faster to # filter out the shapes of incorrect length despite the increased time # complexity. hexagons: typing.List[geometry_utils.Polygon] = [] quadrilaterals: typing.List[geometry_utils.Polygon] = [] for poly in all_polygons: if len(poly) == 6: hexagons.append(poly) elif len(poly) == 4: quadrilaterals.append(poly) for hexagon in hexagons: try: l_mark = LMark(hexagon) except WrongShapeError: continue to_new_basis, _ = geometry_utils.create_change_of_basis( l_mark.polygon[0], l_mark.polygon[5], l_mark.polygon[4]) nominal_to_right_side = 50 - 0.5 nominal_to_bottom = ((64 - 0.5) / 2) tolerance = 0.1 * nominal_to_right_side top_right_squares = [] bottom_left_squares = [] bottom_right_squares = [] for quadrilateral in quadrilaterals: try: square = SquareMark(quadrilateral, l_mark.unit_length) except WrongShapeError: continue centroid = geometry_utils.guess_centroid(square.polygon) centroid_new_basis = to_new_basis(centroid) if math_utils.is_within_tolerance( centroid_new_basis.x, nominal_to_right_side, tolerance) and math_utils.is_within_tolerance( centroid_new_basis.y, 0.5, tolerance): top_right_squares.append(square) elif math_utils.is_within_tolerance( centroid_new_basis.x, 0.5, tolerance) and math_utils.is_within_tolerance( centroid_new_basis.y, nominal_to_bottom, tolerance): bottom_left_squares.append(square) elif math_utils.is_within_tolerance( centroid_new_basis.x, nominal_to_right_side, tolerance) and math_utils.is_within_tolerance( centroid_new_basis.y, nominal_to_bottom, tolerance): bottom_right_squares.append(square) if len(top_right_squares) == 0 or len(bottom_left_squares) == 0 or len( bottom_right_squares) == 0: continue # TODO: When multiple, either progressively decrease tolerance or # choose closest to centroid top_left_corner = l_mark.polygon[0] top_right_corner = geometry_utils.get_corner( top_right_squares[0].polygon, geometry_utils.Corner.TR) bottom_right_corner = geometry_utils.get_corner( bottom_right_squares[0].polygon, geometry_utils.Corner.BR) bottom_left_corner = geometry_utils.get_corner( bottom_left_squares[0].polygon, geometry_utils.Corner.BL) return [ top_left_corner, top_right_corner, bottom_right_corner, bottom_left_corner ] raise RuntimeError("Couldn't find document corners.")