AI Compass
Compass

Edges and contours

Sobel, Canny and findContours: how to find, count and measure outlines without training a single model.

·2 min read·By Fachredaktion Technik
DETAIL
3 sections

The idea

Where an image jumps from light to dark there is an edge. A computer finds those places by subtracting neighbouring pixels: a small difference means a surface, a large one means an edge.

What it is good for

  • Counting parts on a conveyor.
  • Checking whether a component has the right outline.
  • Finding a document in a photo and rectifying it.
  • Measuring lines, circles and rectangles.

Canny with automatic thresholds

import cv2, numpy as np

def auto_canny(grey, sigma=0.33):
    v = float(np.median(grey))
    lower = int(max(0, (1.0 - sigma) * v))
    upper = int(min(255, (1.0 + sigma) * v))
    return cv2.Canny(grey, lower, upper)

img  = cv2.imread("parts.jpg")
grey = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
grey = cv2.GaussianBlur(grey, (5, 5), 0)     # without this: noise edges
edges = auto_canny(grey)

contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL,
                               cv2.CHAIN_APPROX_SIMPLE)

for c in contours:
    area = cv2.contourArea(c)
    if area < 800:
        continue
    perim = cv2.arcLength(c, True)
    corners = cv2.approxPolyDP(c, 0.02 * perim, True)
    circ = 4 * np.pi * area / (perim ** 2)         # 1.0 = perfect circle
    shape = "circle" if circ > 0.85 else f"{len(corners)}-gon"
    print(f"area {area:8.0f}  circularity {circ:.2f}  -> {shape}")

The contour features you need

FeatureFunctionUsed for
AreacontourAreaFiltering, size checks
PerimeterarcLengthCircularity, smoothness
Bounding boxboundingRectCropping, position
Rotated boxminAreaRectDetermining angle
Convex hullconvexHullSpotting missing pieces
MomentsmomentsComputing the centroid

The gradient

Sobel gradient

Gₓ = [[−1,0,1],[−2,0,2],[−1,0,1]] * I G_y = [[−1,−2,−1],[0,0,0],[1,2,1]] * I |G| = √(Gₓ² + G_y²) θ = arctan(G_y / Gₓ)

Magnitude and direction follow from the two directional derivatives as for a vector.

Gₓ, G_y
derivatives in x and y
|G|
gradient magnitude, that is edge strength
θ
edge direction

Canny's four steps

  1. 01

    Smooth

    Gaussian filter, so that noise does not create gradients.

  2. 02

    Compute the gradient

    Sobel in both directions, then magnitude and direction.

  3. 03

    Non-maximum suppression

    Only the strongest point across the edge direction survives, which makes the lines one pixel thin.

  4. 04

    Hysteresis

    Points above the upper threshold are certainly edges. Points between the two thresholds only if they connect to a certain edge.

The fourth step is the real gain: it joins broken lines while suppressing isolated noise points, which a single threshold cannot do simultaneously.

Measuring with a reference

Scale from a reference object

mm_per_pixel = l_ref / p_ref l_obj = p_obj · mm_per_pixel

Scale follows from the known object and transfers to every object in the same plane.

p_ref
length of the reference object in pixels
l_ref
its known length in millimetres
p_obj
measured pixel length of the object

The "same plane" restriction is decisive. Objects closer to the camera appear larger, and without rectification the measurement is wrong by the perspective factor. With a perpendicular shot and a reference next to the part, accuracies below one percent are attainable; with an angled shot and no rectification, ten percent errors are easy.

Related courses and sources

CourseFreeEN

OpenCV tutorials

The official guides to filters, edges, features and calibration, each with runnable Python code.

For anyone writing image processing themselves; every example runs.

Was this page helpful?
Edges and contours