cv2.findContours fails to detect black rectangle aligned with x-axis

Hi everyone

I’m working with OpenCV in Python to detect a large black rectangle in an image. Everything works fine when the rectangle is rotated at an angle, but when it’s aligned exactly with the x-axis (i.e., perfectly horizontal), it doesn’t get detected as a contour at all. As a result, cv2.minAreaRect() never even runs because there’s no contour to process.

I’ve tried converting the image to grayscale and applying both thresholding and edge detection with cv2.Canny(). Visually, the rectangle is clearly present and distinct from the background. I’m also using cv2.findContours() with the RETR_EXTERNAL and CHAIN_APPROX_SIMPLE flags.

This issue only occurs when the rectangle is perfectly horizontal. If I rotate the same shape slightly, it gets detected correctly. I suspect it might have something to do with how OpenCV approximates contours or handles horizontal/vertical edges.

Has anyone run into this before? I’d really appreciate any insights into why this happens and how to make findContours more reliable for this case. Could it be related to resolution, anti-aliasing, or internal filtering?

Thanks in advance for any help — I shared a code snippet if that helps!

def detect_tunnel(image, aruco, binary_matrix, grid_size=5):
    # Convert image to grayscale
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

    # Adaptive thresholding
    thresh = cv2.adaptiveThreshold(
        gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 11, 5
    )

    # Morphological operations to remove noise
    kernel = np.ones((5, 5), np.uint8)
    cleaned = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=2)

    # Find contours of the cleaned image
    contours, _ = cv2.findContours(cleaned, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    
    # Filter out small contours based on a minimum area
    min_area = 10000  # Adjust based on image size
    large_contours = [cnt for cnt in contours if cv2.contourArea(cnt) > min_area]
    length = len(large_contours)
    print('length',length)
    for cnt in large_contours:

        image_copy=image.copy()
        cv2.drawContours(image_copy, cnt, 0, (0, 255, 0), 3)
        cv2.imshow("Tunnel large", image_copy)
        cv2.waitKey(0)
        cv2.destroyAllWindows() 

    #more than one contour detected
    if length != 0: 
        # Initialize variables for tunnel detection
        tunnel = None

        tunnel_cand=[]
        # Search for the best candidate rectangle representing the tunnel
        for contour in large_contours:
            rect = cv2.minAreaRect(contour)  # Gets a rotated rectangle        
            center, (width, height), angle = rect       
            area = width * height
            aspect_ratio = max(width, height) / min(width, height)
            
            #test which rectangles are captured
            box = cv2.boxPoints(rect)
            box = np.intp(box)
            image_copy1 = image.copy()
            cv2.drawContours(image_copy1, [box], 0, (0, 255, 0), 3)
            cv2.imshow("Tunnel contour", image_copy1)
            cv2.waitKey(0)
            cv2.destroyAllWindows() 
            
            #filter the contours for black pixels
            # Create a mask for the rotated rectangle
            mask = np.zeros(binary_matrix.shape, dtype=np.uint8)
            cv2.fillPoly(mask, [box // grid_size], 1)

            # Calculate black pixel ratio inside the mask
            masked_black = binary_matrix[mask == 1]
            if masked_black.size == 0:
                return False
            black_ratio = np.sum(masked_black == 1) / masked_black.size
            print('black_ratio',black_ratio)
            if black_ratio > 0.6:
                tunnel_cand.append(contour)
                print('ik heb iter')
                image_copy2=image.copy()
                cv2.drawContours(image_copy2, [box], 0, (0, 255, 0), 3)
                cv2.imshow("Tunnel zwart", image_copy2)
                cv2.waitKey(0)
                cv2.destroyAllWindows()

You should reduce your code into just a couple of lines that test findCountours directly from what it returns, and not after lots of further processing, which, as far as we know, might as well - or actually more probably - cause the issue

1 Like

black = 0 = false = background. if you want to grasp that rectangle, it has to be foreground, which is the opposite.

threshold the image to obtain a mask (binary image), using appropriate flags (THRESH_BINARY_INV) to the threshold() call, such that the rectangle is foreground.

Canny is a newbie trap. avoid it. show the source image. then we can tell you if Canny is warranted.

1 Like

crosspost:

https://stackoverflow.com/questions/79615444/cv2-findcontours-fails-to-detect-black-rectangle-aligned-with-x-axis

1 Like

Can you provide the first picture, please? I can’t find it anywhere. I can only see the picture with the tilted black “tunnel”.