Sobel vs Canny Edge Detector

Push a test image through the full edge-detection pipeline behind robot vision — grayscale, Gaussian blur, Sobel gradients, non-max suppression, Canny hysteresis — and hover any step to inspect the math behind a single pixel.

Loading interactive simulation...

why blur first, and why two thresholds 🖖

A derivative amplifies whatever it's given, noise included — differentiate a raw image and every single-pixel fluctuation looks like an edge. Convolving with a Gaussian first (step 2) averages that noise away while barely touching the large-scale intensity changes that are real edges, which is why every practical gradient-based detector smooths before it differentiates. The Sobel kernels themselves are separable approximations of the partial derivatives ∂I/∂x and ∂I/∂y, weighted 1-2-1 across the perpendicular axis to bias the estimate toward the center row or column. A single threshold on the resulting magnitude forces an impossible choice: set it low and noise speckles the output, set it high and low-contrast edges break into dashed fragments. Hysteresis (step 6) escapes that trade-off by using two thresholds instead of one. The high threshold (Thigh) finds edges it is confident about. The low threshold (Tlow) applies only to pixels already touching a strong edge, so a real contour can continue through a temporarily faint stretch without letting noise start a contour of its own. This is the same double-threshold idea used in flood-fill segmentation and in schmitt-trigger circuits, wherever a single cutoff would flicker.

Two questions about one image 🖖

Sobel and Canny ask different questions about the same picture. Sobel produces a gradient map: every pixel gets a number for how sharply brightness changes there, so boundaries come out thick and grayscale, like a soft glow. Canny takes that same gradient and forces a decision — non-maximum suppression keeps only the single brightest pixel across each ridge, thinning the glow to a crisp one-pixel line, then labels it edge or not. Toggle the Gradient and Canny tabs to watch a fuzzy map collapse into a clean outline.

Canny's edges hide a built-in trade-off 🖖

John Canny didn't tinker his way to a recipe. In his 1986 paper he framed edge detection as an optimization with three goals — detect real edges, locate them accurately, and respond only once per edge — and solved it with the calculus of variations; the answer came out almost exactly as the first derivative of a Gaussian. The catch he also proved: detection and localization pull against each other as the blur widens, so a bigger σ finds fainter edges but smears their position, and no single σ wins at both. Slide σ and watch it happen.

Problem solved in full

  1. Gradient magnitude at pixel (31, 3) of the checkerboard test image 9 steps

    The checkerboard test image — pure black and white, squares 32 pixels wide — at σ = 1.0 with the 3×3 blur kernel, Tlo = 30 and Thi = 80. Derive the gradient magnitude at pixel (31, 3), the last white column before the first vertical boundary, and then work out how many pixels wide the edge it belongs to comes out.

    1. Grayscale first, and on this image it does nothing: the three weights add to exactly 1, so a pixel with equal red, green and blue keeps its value. White stays 255, black stays 0, so every number below comes from just those two values.

    2. The Gaussian weights fall off with the square of the distance. At σ = 1 the centre gets 1, the 4 pixels sharing an edge get e-0.5 = 0.6065 and the 4 corners get e-1 = 0.3679; dividing through by the total 4.8976 makes them a weighted average.

    3. Row 3 lies well inside a horizontal band, so the 3 rows of any window there are identical and the kernel's vertical structure cancels out. Only its column totals matter, and there are just 2 of them.

    4. Blur across the boundary. At x = 31 the window covers 2 white columns and 1 black; at x = 32 it covers 1 white and 2 black. The hard step from 255 to 0 has become the ramp 255, 185, 70, 0 — those middle 2 values, rounded, are the ones the inspector shows.

    5. Sobel weights the left column by -1, -2, -1 and the right by +1, +2, +1, and the centre column by nothing at all. With all 3 rows equal those weights collapse to a single factor of 4, so the whole convolution is 4 times the difference between the columns either side.

    6. The vertical gradient vanishes for the mirror-image reason: the row above the pixel and the row below it are the same 3 numbers, so the +1, +2, +1 sum cancels the -1, -2, -1 sum term by term. This edge is purely horizontal in gradient, which is what makes the next step easy.

    7. Combine the two, and the magnitude is just the horizontal one.

    8. Compare with what the same edge would give unblurred: a bare step of 255 across the same kernel. Smoothing has spent 280 counts, just over 27% of the available response, and that is the price of the noise immunity — not a rounding loss but a deliberate trade.

    9. Now repeat one column to the right. At x = 32 the window reads 185, 70, 0, so the difference is again -185 and the magnitude is again 740. One column further out on either side the difference collapses to about -70 and the magnitude to 280.

    Answer

    740 — and the edge comes out 2 pixels wide. Non-maximum suppression keeps a pixel when its magnitude is ≥ both neighbours along the gradient. Here the gradient points along x, so pixel 31 is compared with 280 on its left and 740 on its right, pixel 32 with 740 and 280. Each ties with the other, and a tie passes ≥, so both survive; at 740 both are more than 9 times Thi = 80, so both come out white. No pair of thresholds can separate them, because thresholds are not the cause. The real boundary lies at x = 31.5, exactly halfway between the last white pixel and the first black one, and a symmetric blur leaves the gradient symmetric about that half-integer — there is no single maximum to find. That is worth knowing before you tune anything: a doubled line in an edge map is sometimes a thresholding failure and sometimes arithmetic, and here it is 740 = 740 on an image whose squares happen to be a whole number of pixels wide.

References (1)

Example problems

  • Clean shapes - The default state: σ = 1.4 with thresholds 30 and 80, on the Canny tab. Clean geometry is where a one-pixel outline looks most like a drawing.
  • Checkerboard gradient - The only preset that stops at the gradient map, so you get the thick grey glow instead of a decision. It is also the worked problem's setup: σ = 1.0, Tlo = 30, Thi = 80.
  • Circuit traces - σ = 0.8, the least blur here, which is what keeps thin copper traces intact. Identical thresholds to Face, so σ is the only thing that differs between the two.
  • Smoothed face - σ = 2.5 with a 5×5 kernel, three times the blur of Circuit board at the same thresholds. Texture goes and structure stays, and edge positions smear with it.