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()