How to sort contours of a grid using OpenCV python?

Issue

I’m trying to sort the following squares inside the checkers board grid.

enter image description here

I have the valid contours, inside a NumPy array.

Here’s a snippet of the code, on how I get the valid squares contours.

  # Find contours and find squares with contour area filtering + shape approximation
            cnts = cv2.findContours(invert, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
            r = 0
            cnts = cnts[0] if len(cnts) == 2 else cnts[1]
            sort_contours(cnts, "bottom-to-top")
            sort_contours(cnts, "left-to-right")
            valid_cnts = []
            v = []
            areas = []
            for c in cnts:
                area = cv2.contourArea(c)
                peri = cv2.arcLength(c, True)
                approx = cv2.approxPolyDP(c, 0.02 * peri, True)
                if len(approx) == 4 and area > 150 and area < 15000:
                    areas.append(area)
                    x, y, w, h = cv2.boundingRect(c)
                    s = img[y:y + h, x:x + w]
                    imgStr = "squares/square" + str(r) + ".png"
                    v.insert(r, [x, y, w, h])
                    cv2.imwrite(imgStr, s)
                    cv2.drawContours(original, [c], -1, (36, 255, 12), 2)
                    cv2.drawContours(mask, [c], -1, (255, 255, 255), -1)
                    valid_cnts.insert(r, c)
                    r = r + 1

My aim also is to sort them from left to right then bottom to up. So I can then recognize each piece on them. This is my current sorting function:

def sort_contours(cnts, method="left-to-right"):
    # initialize the reverse flag and sort index
    reverse = False
    i = 0
    # handle if we need to sort in reverse
    if method == "right-to-left" or method == "bottom-to-top":
        reverse = True
    # handle if we are sorting against the y-coordinate rather than
    # the x-coordinate of the bounding box
    if method == "top-to-bottom" or method == "bottom-to-top":
        i = 1
    # construct the list of bounding boxes and sort them from top to
    # bottom
    boundingBoxes = [cv2.boundingRect(c) for c in cnts]
    (cnts, boundingBoxes) = zip(*sorted(zip(cnts, boundingBoxes),
                                        key=lambda b: b[1][i], reverse=reverse))
    # return the list of sorted contours and bounding boxes
    return (cnts, boundingBoxes)

Unfortunately, it does not work, I think it has to do with the camera angle. Because when I crop the photo to 64 squares they do not appear in the order I desire. If anyone can guide me on how to sort them correctly and precisely it would be great!

Solution

enter image description here

The idea is after finding contours on the thresholded image, we utilize imutils.contours.sort_contours() to sort the contours from bottom-to-top. Next we take each row of 8 squares and sort this row from left-to-right. Here’s a visualization of the sorting:

enter image description here

import cv2
from imutils import contours

# Load image, grayscale, gaussian blur, Otsu's threshold
image = cv2.imread("1.jpg")
original = image.copy()
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (5,5), 0)
thresh = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]

# Find all contour and sort from top-to-bottom or bottom-to-top
cnts, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[-2:]
(cnts, _) = contours.sort_contours(cnts, method="bottom-to-top")

# Take each row of 8 and sort from left-to-right
checkerboard_row = []
row = []
for (i, c) in enumerate(cnts, 1):
    row.append(c)
    if i % 8 == 0:  
        (cnts, _) = contours.sort_contours(row, method="left-to-right")
        checkerboard_row.append(cnts)
        row = []

# Draw text
number = 0
for row in checkerboard_row:
    for c in row:
        M = cv2.moments(c)
        x = int(M['m10']/M['m00'])
        y = int(M['m01']/M['m00'])
        cv2.putText(original, "{}".format(number + 1), (x - 20,y), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255,50,10), 2)
        number += 1

cv2.imshow('original', original)
cv2.waitKey()

Note: You could also change the sort direction such as right-to-left or top-to-bottom and so on

Answered By – nathancy

Answer Checked By – David Marino (AngularFixing Volunteer)

Leave a Reply

Your email address will not be published.