Contour Detection
Join scattered edge pixels into ordered boundary loops, and decide up front whether holes and nested shapes appear or stay hidden.
Why Does This Exist?
Edge operators return a scatter of bright pixels with no notion of object: which pixels belong to the coin, which to its hole, which to the table scratch. Counting, measuring, and recognizing need closed loops with identity, and contour detection performs exactly that grouping through border following (Suzuki and Abe, 1985). It is the step that turns the pixel soup from edge filtering into the ordered loops that shape analysis measures.
The input must already be binary, so thresholding or Canny plus a threshold is the prerequisite. This page covers the tracing itself, the four retrieval modes, and the two classic setup mistakes.
Think of It Like This
Connecting dots, with dots inside dots
A puzzle page shows scattered dots that form two squares, one drawn inside the other. Tracing the outer square gives the frame; tracing the inner square gives the hole. A mode that returns only outer loops reports one shape, while a mode that returns everything reports two loops plus the knowledge that one sits inside the other.
It stops holding when dots go missing. Gaps in the dotted line leave the tracer guessing which way the boundary runs, the same way broken edges from weak thresholds produce open, unusable contours instead of closed loops.
How It Actually Works
Border following on binary images
The tracer scans the binary image for the next foreground pixel, then walks its border, recording each step, until it returns to the start. Each walk emits one ordered loop, and scanning resumes after it, so one pass collects every boundary in the image. Connected-component labeling runs underneath: the walk never crosses background, which is why the input must be strictly binary rather than grayscale.
The four retrieval modes
The mode decides which loops survive. RETR_EXTERNAL keeps only outermost boundaries, which is what part counting wants. RETR_LIST keeps every loop but records no nesting. RETR_CCOMP keeps all loops with two hierarchy levels, outer plus holes. RETR_TREE keeps everything with full ancestry, which assembly inspection wants when parts contain holes that contain islands.
Work the nested-squares case: two square frames, each with its own square hole, meaning four loops total (two outers, two holes). RETR_EXTERNAL returns contours, one per frame. RETR_TREE returns , with hierarchy recording each hole's parent frame. Same image, different answers, and both are correct for different jobs.
Approximation at extraction time
CHAIN_APPROX_SIMPLE compresses straight runs to their endpoints during tracing, so a rectangle stores points instead of hundreds. CHAIN_APPROX_NONE keeps every pixel for exact work like pixel-perfect masks. Further polygon simplification with approxPolyDP happens after, as the family page describes.
Code
The canonical call pattern with hierarchy inspection:
import cv2
_, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)contours, hierarchy = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)outer = [c for i, c in enumerate(contours) if hierarchy[0][i][3] == -1]# outer holds only loops with no parent; holes have a parent index insteadParent index means outermost loop. Counting parts means counting outer, not contours.
Watch Out For
Feeding grayscale instead of binary
Symptom: thousands of ragged micro-contours at every faint texture variation, with counts and areas that mean nothing. The tracer treats every nonzero pixel as foreground, so a grayscale photo becomes one giant textured foreground. Threshold or edge-detect to a clean binary mask first, and view the mask before tracing.
Objects touching the image border
Symptom: a part touching the frame edge merges with the border into one open shape, or vanishes under RETR_EXTERNAL because the frame swallows it. Borders are contours too. Pad the image with a background border via copyMakeBorder before tracing, and treat border-touching loops as truncated measurements rather than full parts.
The Quick Version
- Contour detection links binary edge pixels into ordered closed loops via border following.
- Retrieval modes decide the output: external only, flat list, two levels, or full nesting tree.
- Two nested square frames yield contours under external mode and under tree mode.
- Chain approximation compresses straight runs at extraction; polygon simplification follows after.
- Input must be cleanly binary, and border-touching objects need padding before tracing.