Contours turn a mask into countable objects with positions and sizes, the core of classic detection and inspection.
cv2.findContours(mask, mode, method) traces the boundaries of white regions in a binary image and returns (contours, hierarchy) in OpenCV 4 and later. Each contour is an int32 array of points with shape (n, 1, 2).
mode:cv2.RETR_EXTERNALreturns only outer boundaries (usually what you want);RETR_TREEalso returns holes, with parent/child links inhierarchy.method:cv2.CHAIN_APPROX_SIMPLEcompresses straight runs to their end points, saving memory;CHAIN_APPROX_NONEkeeps every pixel.
With contours you can measure:
cv2.contourArea(c)andcv2.arcLength(c, closed=True)cv2.boundingRect(c)gives an upright(x, y, w, h);cv2.minAreaRect(c)a rotated boxcv2.moments(c): centroid at(m10/m00, m01/m00)cv2.approxPolyDP(c, eps, True)simplifies to a polygon; 4 vertices suggests a rectanglecv2.convexHull, and circularity4*pi*area/perimeter**2(1 for a circle)
Filter by area to drop noise, sort by area to find the largest object. cv2.drawContours(img, contours, -1, color, thickness) draws them; index -1 means all.
Feed it a clean binary mask (threshold, Canny, morphology first). Old OpenCV 3 returned three values, which is why older tutorials unpack _, contours, _.
Example
import cv2
import numpy as np
mask = np.zeros((100, 160), np.uint8)
cv2.rectangle(mask, (10, 10), (60, 40), 255, -1)
cv2.circle(mask, (110, 60), 25, 255, -1)
mask[90, 5] = 255 # one-pixel noise
contours, hierarchy = cv2.findContours(mask, cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)
print(len(contours)) # 3
objs = [c for c in contours if cv2.contourArea(c) > 20]
for c in sorted(objs, key=cv2.contourArea, reverse=True):
area = cv2.contourArea(c)
x, y, w, h = cv2.boundingRect(c)
m = cv2.moments(c)
cx, cy = m["m10"] / m["m00"], m["m01"] / m["m00"]
peri = cv2.arcLength(c, True)
corners = len(cv2.approxPolyDP(c, 0.02 * peri, True))
circ = 4 * np.pi * area / peri ** 2
print(int(area), (x, y, w, h), (round(cx), round(cy)), corners, round(circ, 2))
# 1890 (85, 35, 51, 51) (110, 60) 8 0.87 circle: many corners, round
# 1500 (10, 10, 51, 31) (35, 25) 4 0.74 rectangle: 4 cornersRemember
- findContours needs a binary mask and returns (contours, hierarchy)
- RETR_EXTERNAL for outer outlines, RETR_TREE for nested holes
- Measure with contourArea, boundingRect, moments, approxPolyDP
- Filter tiny contours by area to ignore noise
Watch out for
- Running findContours on a noisy, un-thresholded grayscale image and getting thousands of tiny contours.