1

I have to find the contours of boxes.

Some boxes have diagonal inside of them. I try to remove diagonal but I think it isn't answer.

Here are the images those I preprocessing and contour result. Only the contour of the box with diagonal can't be found.

Edge filtered

overlay of detection

detection only

(Edit. I attach original image, Thanks for comment)

original image

Here is the code

def extract_blocks(img, debug_mode: bool = False):

    if debug_mode:
        img = cv2.imread(img)
    
    new_w = 710
    new_h = 710
    img = cv2.resize(img, (new_w, new_h))

    gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

    gau = cv2.GaussianBlur(gray_img, (5, 5), 0)

    laplacian_gau = cv2.Laplacian(gau,cv2.CV_8U,ksize=5)


    blank = np.ones_like(img) * 255  
    contours, hierarchy = cv2.findContours(laplacian_gau, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)
    print(f"Number of Contours: {len(contours)}")

    blocks = []

    for cnt in contours:
        area = cv2.contourArea(cnt)
        if area < 1900: # Skip too small contour
            continue
        
        rect = cv2.minAreaRect(cnt)
        box = cv2.boxPoints(rect)
        box = np.int32(box)
        w, h = rect[1]  # width, height

        if 50 < min(w, h) < 80 and 50 < max(w, h) < 80:
            blocks.append(box)
            cv2.drawContours(img, [box], 0, (0, 255, 0), 2)
            cv2.drawContours(blank, [box], 0, (0, 0, 255), 2)

    cv2.imshow('laplacian_gau', laplacian_gau)
    cv2.imshow("Detected Blocks", img)
    cv2.imshow("Detected Blocks (White Background)", blank)
    
    cv2.waitKey(0)
    cv2.destroyAllWindows()

I tried to remove red colors to white, but I can't remove diagonal. I tried to make morphology, but it isn't answer.

The box in the image can be rotated.

2
  • 1
    Please include the original image in order for us to reproduce and improve your detection. Commented Sep 23 at 8:03
  • 1
    @André I attach original image now. Thank you for your comment. Commented Sep 24 at 0:20

1 Answer 1

4

The following approach is keeping most of your image processing pipeline, i.e. the Laplacian laplacian_gau as a pre-processing, finding contours using cv2.findContours with connected components.

Instead of looking for boxes in the contours, I do a polygonal approximation and search for any two axes that (a) are roughly perpendicular and (b) satisfy your length criterion. If such a pair is found, I construct a square by adding the missing point. This makes use of the three corner points of the triangle that is found for boxes with a diagonal and also retrieves them.

Feel free to experiment with the code and adapt it to your needs. You might, for example, want to add a check whether the edges pt4-pt1 and pt4-pt3 with constructed point pt4 are supported by features in your edge detection laplacian_gau to avoid false positives.

for cnt in contours:

    area = cv2.contourArea(cnt)
    if area < 1900:  # Skip too small contour
        continue

    # Polygonal approximation of the contour
    epsilon = 0.02 * cv2.arcLength(cnt, True)
    approx  = cv2.approxPolyDP(cnt, epsilon, True)

    # Detect any two axes of similar length and roughly perpendicular of the polygonal approximation
    for i in range(len(approx)):
        pt1 = approx[i][0]
        pt2 = approx[(i + 1) % len(approx)][0]
        pt3 = approx[(i + 2) % len(approx)][0]

        vec1 = pt2 - pt1
        vec2 = pt3 - pt2

        len1 = np.linalg.norm(vec1)
        len2 = np.linalg.norm(vec2)

        if 50 < len1 < 80 and 50 < len2 < 80:  # Both axes have roughly the size of the block to detect
            # Now get the angle between the two vectors
            cos_angle = np.dot(vec1, vec2) / (len1 * len2)
            angle     = np.arccos(cos_angle) * 180 / np.pi

            if 80 < angle < 100:  # roughly perpendicular -> We found a square
                print(f"Found square with side lengths {len1:.2f} and {len2:.2f} and angle {angle:.2f}")

                # Construct a square from the two axes, any of those two options should work
                pt4      = pt3 - vec1
                pt4      = pt1 + vec2

                pt4      = pt4.astype(int)
                square   = np.array([pt1, pt2, pt3, pt4])

                blocks.append(square)
                cv2.drawContours(img, [square], 0, (0, 255, 0), 2)
                cv2.drawContours(blank, [square], 0, (0, 0, 255), 2)

                break  # Avoid detecting the same square multiple times

Output:

Number of Contours: 3982
Found square with side lengths 63.20 and 68.03 and angle 87.15
Found square with side lengths 68.12 and 65.19 and angle 91.03
Found square with side lengths 63.63 and 67.74 and angle 90.92
Found square with side lengths 66.76 and 65.80 and angle 89.75
Number of Blocks: 4

Detection with axis method

Sign up to request clarification or add additional context in comments.

2 Comments

Cool answer! Wanted to mention that another alternative would be the cv2.minAreaRect to also get the angle and the points. Although the angle will need some adjusting as always with opencv stuff.
Thanks for help me. It was very big help to me. Thanks a lot.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.