Exemple #1
0
    def find_grid(self, image):
        """ Extract the sudoku grid from the black/white image. """
        if ENABLE_DEBUG:
            print("DEBUG -- Attempting to extract the sudoku grid"
                  " from the image.")
        # Find all the closed shapes in the thresholded image
        contours, hierarchy = cv2.findContours(image=image,
                                               mode=cv2.RETR_LIST,
                                               method=cv2.CHAIN_APPROX_SIMPLE)

        # The minimal area required for a closed shape to be considered as
        # a possible grid.
        min_viable_area = 500
        # Keeps track of the highest area that was found
        max_area_found = 0
        biggest_contour_found = None  # Saves the biggest contour found
        # Iterate over every contour to find the one with the largest area
        for i, cnt in enumerate(contours):
            # NL: Oppervlakte
            cur_area = cv2.contourArea(cnt)
            # Don't waste calculations on contours that are too small
            # to be considered the largest anyway.
            if cur_area > min_viable_area:
                # NL: Omtrek
                perimeter = cv2.arcLength(cnt, True)
                # More info: http://docs.opencv.org/2.4/modules/imgproc/
                # doc/structural_analysis_and_shape_descriptors.html#approxpolydp
                # Approximates the curves of a contour based on the given
                # precision.
                approx = cv2.approxPolyDP(curve=cnt,
                                          epsilon=0.02 * perimeter,
                                          closed=True)
                # Length refers to the amount of corners.
                # 4 corners = square/rectangle
                if (cur_area > max_area_found and len(approx) == 4):
                    # If the current contour (with approximated curves)
                    # is bigger than the one known, save that contour
                    # and replace the currently known largest area with the
                    # new area (since it is now the biggest one found)
                    biggest_contour_found = approx
                    max_area_found = cur_area
                    # contour_index_found = i

        if ENABLE_PREVIEW or ENABLE_PREVIEW_ALL:
            # To show the biggest contour in the image, it needs
            # to be drawn. So a copy is made of the original image.
            # That way, the original image does not have to be modified
            # and a copied version can be drawn instead.
            _ = deepcopy(self.original_image)
            cv2.drawContours(image=_,
                             contours=[biggest_contour_found],
                             contourIdx=0,
                             color=(255, 0, 0))
            image_preview(_)
        if ENABLE_DEBUG:
            print("DEBUG -- Succesfully found the sudoku grid in"
                  " the image.")
        return biggest_contour_found
Exemple #2
0
 def to_grayscale(self, image):
     """ Transform the given image to grayscale """
     if ENABLE_DEBUG:
         print("DEBUG -- Attempting to transform image to grayscale.")
     try:
         grayscale = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
     except:
         if VERBOSE_EXIT:
             print("ERROR -- Could not convert image to grayscale.")
         exit()
     if ENABLE_PREVIEW_ALL:
         image_preview(grayscale)
     if ENABLE_DEBUG:
         print("DEBUG -- Image succesfully converted to grayscale.")
     return grayscale
Exemple #3
0
    def calc_square_borders(self, image):
        """ Given a extracted sudoku grid, calculate the borders of
            each individual square of that grid. """
        if ENABLE_DEBUG:
            print("DEBUG -- Attempting to calculate sudoku square borders.")

        crop_width = image.shape[1]
        crop_height = image.shape[0]

        square_borders = []
        x_pointer = 0
        y_pointer = 0

        square_width, square_height = crop_width / 9, crop_height / 9
        # Let's find 81 squares and save the top left and bottom right
        # coordinates for each square
        for row in xrange(0, 9):
            y_start = y_pointer
            y_end = y_start + square_width
            y_pointer = y_end
            if y_pointer == crop_width:
                y_pointer += square_width
                y_pointer = 0

            for col in xrange(0, 9):
                x_start = x_pointer
                x_end = x_start + square_height
                x_pointer = x_end
                if x_pointer == crop_height:
                    x_pointer += square_height
                    x_pointer = 0
                t = (xs, ys, xe, ye) = x_start, y_start, x_end, y_end
                square_borders.append(t)
        if ENABLE_DEBUG:
            print("DEBUG -- Succesfully found sudoku square borders.")

        if ENABLE_PREVIEW or ENABLE_PREVIEW_ALL:
            _ = deepcopy(image)
            for i, b in enumerate(square_borders):
                x, y, x2, y2 = b
                cv2.rectangle(_, (x, y), (x2, y2), (255, 0, 0))
            image_preview(_)

        return square_borders
Exemple #4
0
 def apply_blur(self, image):
     """ Adds a blur to the given image, using the kernel size
         defined in settings. """
     if ENABLE_DEBUG:
         print("DEBUG -- Attempting to apply gaussian blur to"
               " grayscale image.")
     try:
         blurred = cv2.GaussianBlur(src=image,
                                    ksize=BLUR_KERNEL_SIZE,
                                    sigmaX=0)
     except:
         if VERBOSE_EXIT:
             print("ERROR -- Could not apply blur filter. Please check"
                   " settings and consider changing the blur kernel size.")
         exit()
     if ENABLE_PREVIEW_ALL:
         image_preview(blurred)
     if ENABLE_DEBUG:
         print("DEBUG -- Gaussian Blur succesfully applied.")
     return blurred
Exemple #5
0
 def apply_filters(self, image, denoise=False):
     """ This method is used to apply required filters to the
         to extracted regions of interest. Every square in a
         sudoku square is considered to be a region of interest,
         since it can potentially contain a value. """
     # Convert to grayscale
     source_gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
     # Denoise the grayscale image if requested in the params
     if denoise:
         denoised_gray = cv2.fastNlMeansDenoising(source_gray, None, 9, 13)
         source_blur = cv2.GaussianBlur(denoised_gray, BLUR_KERNEL_SIZE, 3)
         # source_blur = denoised_gray
     else:
         source_blur = cv2.GaussianBlur(source_gray, (3, 3), 3)
     source_thresh = cv2.adaptiveThreshold(source_blur, 255, 0, 1, 5, 2)
     kernel = cv2.getStructuringElement(cv2.MORPH_CROSS, (3, 3))
     source_eroded = cv2.erode(source_thresh, kernel, iterations=1)
     source_dilated = cv2.dilate(source_eroded, kernel, iterations=1)
     if ENABLE_PREVIEW_ALL:
         image_preview(source_dilated)
     return source_dilated
Exemple #6
0
 def to_binary(self, image):
     """ This method uses Adaptive Thresholding to convert
         a blurred grayscale image to binary (only black and white).
         The binary image is required to extract the full sudoku grid
         from the image. """
     if ENABLE_DEBUG:
         print("DEBUG -- Attempting to apply adaptive threshold"
               " and convert image to black and white.")
     try:
         thresh = cv2.adaptiveThreshold(image, 255, 1, 1, 11, 2)
     except:
         if VERBOSE_EXIT:
             print("ERROR -- Unable to convert the image to black/white."
                   " Please contact the developer at %s and include this"
                   " error and the image you are using." % DEV_EMAIL)
         exit()
     if ENABLE_PREVIEW or ENABLE_PREVIEW_ALL:
         image_preview(thresh)
     if ENABLE_DEBUG:
         print("DEBUG -- Image succesfully converted to binary.")
     return thresh
Exemple #7
0
    # Attempt to load the image
    try:
        image_container = ImagePrepper(settings.FILE_NAME)
    except:
        if settings.VERBOSE_EXIT:
            print("ERROR -- Failed to load image. Does the image "
                  "exist? Do you have a typo? Note: only .png, .jpg and "
                  ".jpeg files are supported.")
        exit()
    if settings.ENABLE_DEBUG:
        print("DEBUG GLOBAL -- Image succesfully loaded.")
        print("DEBUG GLOBAL -- Attempting to extract values from image.")
    # Show preview if enabled in the settings
    if settings.ENABLE_PREVIEW or settings.ENABLE_PREVIEW_ALL:
        image_preview(image_container.image)

    extracted_info = ImageExtractor(image_container.image)
    sudoku_solver = SudokuSolver(extracted_info.starting_grid)
    board_is_valid = sudoku_solver.board_is_valid()

    if not board_is_valid:
        if settings.VERBOSE_EXIT:
            print("ERROR -- Starting values found in sudoku were not valid.")
            print("The following board was found: ")
            sudoku_solver.print_sudoku()
        exit()

    if settings.ENABLE_DEBUG:
        print("DEBUG -- Following starting board was found: ")
        sudoku_solver.print_sudoku()
Exemple #8
0
    def extract_grid(self, contour, image):
        if ENABLE_DEBUG:
            print("DEBUG -- Attempting to extract the sudoku grid from"
                  " the image.")
        # The corners of the contour (including the curve approximation)
        # need to be put in clock-wise order (top-left -> top-right
        # bottom-right -> bottom-left). This is not yet the case so
        # new corners must be calculated.

        # Reshape the array to look as follows:
        # array([[0, 0],
        #        [0, 0],
        #        [0, 0],
        #        [0, 0]])
        points = contour.reshape(4, 2)
        # Initialize an empty array with zeros,
        # so that the corner points can be stored later
        rectangle_corners = np.zeros((4, 2), dtype="float32")

        points_sum = points.sum(axis=1)
        # If we look at the SUM of all corners (x, y) pairs, assuming the full
        # image starts at position 0, 0 at the top-left, we can sum all the
        # coordinate pairs. Afterward, the most top-left corner will have the
        # LOWEST sum (closest to 0, 0) and the bottom-right corner will have
        # the HIGHEST sum (farthest from 0, 0). With this knowledge, we can
        # already place the x, y coordinates for the top-left and bottom-right
        # corner in the corner array
        rectangle_corners[0] = points[np.argmin(points_sum)]
        rectangle_corners[2] = points[np.argmax(points_sum)]

        # We can sortof do the same for the remaining two corners (top-right
        # and bottom-left) but by using the DIFFERENCE between (x,y) coordinate
        # pairs
        points_difference = np.diff(points, axis=1)
        rectangle_corners[1] = points[np.argmin(points_difference)]
        rectangle_corners[3] = points[np.argmax(points_difference)]

        # Perspective warping and calculations. Calculates a destination size
        # for the warped image. Code loosely based on the official OpenCV
        # documentation and based on some default OpenCV examples provided with
        # the library itself.

        # First, extract the rectangle corner points
        top_right, top_left, bot_right, bot_left = rectangle_corners
        # Note: for each corner there is an x ([0]) and a y ([1]) coordinate
        # The width of the bottom side of the new image
        bot_width = np.sqrt(((bot_right[0] - bot_left[0])**2) +
                            ((bot_right[1] - bot_left[1])**2))
        # The width of the top side of the new image
        top_width = np.sqrt(((top_right[0] - top_left[0])**2) +
                            ((top_right[1] - top_left[1])**2))
        # The right side height of the new image
        right_height = np.sqrt(((top_right[0] - bot_right[0])**2) +
                               ((top_right[1] - bot_right[1])**2))
        # The left side height of the new image
        left_height = np.sqrt(((top_left[0] - bot_left[0])**2) +
                              ((top_left[1] - bot_left[1])**2))

        # The hight width and height that were found will be the
        # dimensions of our new image
        max_width = max(int(bot_width), int(top_width))
        max_height = max(int(right_height), int(left_height))

        # With the values found above, a new image can be constructed using the
        # previously calculated dimensions. Image corners are once again in
        # clockwise order, starting at 0,0/x,y(top-left) -> top-right ->
        # bottom-right -> bottom-left.
        destination_image = np.array(
            [
                [0, 0],  # Top left corner
                [max_width, 0],  # Top right corner
                [max_width, max_height],  # Bottom right corner
                [0, max_height]
            ],  # Bottom left corner
            dtype="float32")  # Datatype that the array is constructed with

        # With the targeted destination image as well as the original
        # coordinates found, the image can be warped towards the other,
        # essentially stretching out the pixels and making them fit the
        # targeted size/dimension
        pt = cv2.getPerspectiveTransform(rectangle_corners, destination_image)
        warp = cv2.warpPerspective(image, pt, (max_width, max_height))
        warp = cv2.resize(warp, (450, 450), interpolation=cv2.INTER_AREA)
        if ENABLE_DEBUG:
            print("DEBUG -- Succesfully extracted the sudoku grid from"
                  " the image.")
        if ENABLE_PREVIEW or ENABLE_PREVIEW_ALL:
            image_preview(warp)
        return warp