def crop_grid(img): """Identify the Sudoku grid, crop it from the image and skew it into a square to compensate for the camera angle.""" proc = pre_process_image(img) if 'basic' in classification_mode(): bbox = cv.get_four_corners(cv.find_largest_polygon(proc)) else: # Gets the four corners of the inner contours of the image. Compensates for the grid outline accurately. bbox, area, outer, outer_area = cv.find_four_corners_inner(proc) # If the inner contours are less than 95% of the outer contour we may be losing valuable information. # Image 8 is one such example of this occurring # In these cases, use a different algorithm to estimate the width of the Sudoku border if (area / outer_area) < 0.95: bbox = cv.get_four_corners(outer) bbox = inner_sudoku_bbox( proc, bbox) # Estimates the width of the grid outline area = outer_area # Some images have the Sudoku grid surrounded by a large rectangular box and the contour algorithm will pick that # instead of the Sudoku grid. In these cases, using the largest feature algorithm is more reliable. Identify when # the contour area meets a threshold ratio of the image size. if (area / (proc.shape[0] * proc.shape[1])) > 0.95: # Find the largest connected pixel structure, this should be the outer grid for Sudoku proc, bbox, seed = cv.find_largest_feature(proc) bbox = inner_sudoku_bbox( proc, bbox) # Estimates the width of the grid outline # Return a warped version of the image return cv.crop_and_warp(img, bbox, square=True)
def draw_numbers(self, numbers, colour=(50, 50, 255), thickness=3, crop=False, show_known=False, show=True, save=None): """ Draws numbers onto the cropped or original Sudoku image and shows it on the screen. Args: numbers (iterable): Array of size 81 with the numbers to display on the grid. colour (tuple): BGR (blue, green, red) values between 0 and 255. thickness (int): Thickness of the font in pixels. crop (bool): If True, will display the numbers on the cropped and warped image instead of the original. show_known (bool): If True, will display the predictions for the given numbers on the board instead of the empty cells. show (bool): If True, will show the image on the screen. save (str): If specified, will save the image to that location. Returns: np.array: Image with the missing numbers drawn on. """ if self.cropped_color is None: self.cropped_color, rect, matrix = cv.crop_and_warp(self.original, self.crop_rect, square=True) img = self.cropped_color.copy() scale = int((self.grid_squares[0][1][0] - self.grid_squares[0][0][0]) * 0.075) # Dynamic scale for font for i, square in enumerate(self.grid_squares): condition = self.board_int[ i] == 0 # Don't draw numbers given on the board if show_known: # Unless we want to see them condition = not condition if condition: fh, fw = cv2.getTextSize( str(numbers[i]), cv2.FONT_HERSHEY_PLAIN, scale, thickness)[0] # Get font height and width h_pad = int((square[1][0] - square[0][0] - fw) / 2) # Padding to centre the number v_pad = int((square[1][1] - square[0][1] - fw) / 2) h_pad -= self.border # No border on the original, so this compensates v_pad += self.border img = cv2.putText( img, str(numbers[i]), (int(square[0][0]) + h_pad, int(square[1][1]) - v_pad), cv2.FONT_HERSHEY_PLAIN, fontScale=scale, color=colour, thickness=thickness) # Display the cropped image and return if crop: if show: cv.show_image(img) if save is not None: cv2.imwrite(save, img) return img # Perform an inverse of the crop and warp transformation to put the image back onto the original height, width = self.original.shape[:2] img = cv2.warpPerspective(img, self.crop_matrix, (width, height), flags=cv2.WARP_INVERSE_MAP, dst=self.original, borderMode=cv2.BORDER_TRANSPARENT) if show: cv.show_image(img) if save is not None: cv2.imwrite(save, img) return img