The Canny Edge Detector: Signal Theory and Implementation
Understanding Canny edge detection through the lens of signal processing, optimal filter design, and the mathematics of non-maximum suppression.
The Canny edge detector (1986) remains one of the most elegant algorithms in computer vision. Its design criteria โ good detection, good localization, single response โ translate directly into mathematical constraints.
The Optimal Edge Filter
Canny formulated edge detection as an optimization problem. He derived that the optimal 1D filter maximizing the signal-to-noise ratio (SNR) and localization simultaneously is well-approximated by the first derivative of a Gaussian:
The 2D extension computes the gradient magnitude and direction:
where and .
Gaussian Scale and the Uncertainty Principle
The Gaussian standard deviation controls the trade-off between noise robustness and localization precision. This is a direct consequence of the Heisenberg uncertainty principle applied to signals:
A wider in the spatial domain (more smoothing) means narrower bandwidth in frequency โ better noise rejection but blurred edges. Typical values: pixels.
Non-Maximum Suppression
After gradient computation, edges are thinned by keeping only local maxima in the gradient direction. For a pixel at with gradient direction :
Bilinear interpolation between discrete neighbors is used for sub-pixel accuracy.
Hysteresis Thresholding
Two thresholds create three pixel classes:
- โ strong edge (keep)
- โ weak edge (keep if connected to a strong edge)
- โ suppressed
Connectivity is verified via 8-connected BFS/DFS. A typical ratio is .
Implementation
import cv2
def canny_detect(image_path: str, sigma: float = 1.4) -> np.ndarray:
img = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
# Gaussian smoothing โ kernel size must be odd
ksize = int(6 * sigma + 1) | 1
blurred = cv2.GaussianBlur(img, (ksize, ksize), sigma)
# Gradient โ Canny
# Thresholds derived from Otsu's method on gradient magnitude
sobelx = cv2.Sobel(blurred, cv2.CV_64F, 1, 0, ksize=3)
sobely = cv2.Sobel(blurred, cv2.CV_64F, 0, 1, ksize=3)
mag = np.hypot(sobelx, sobely)
t_high = 0.2 * mag.max()
t_low = 0.5 * t_high
edges = cv2.Canny(blurred, t_low, t_high, L2gradient=True)
return edges
Note:
L2gradient=Trueuses the exact norm instead of the approximation , improving angular accuracy at ~10% compute cost.
When to Use Canny vs. Alternatives
| Algorithm | Best For | |---|---| | Canny | General purpose, thin clean edges | | Sobel/Prewitt | Fast gradient estimation, not full edge detection | | Laplacian of Gaussian | Blob detection, scale-space analysis | | Structured Forests | Complex textures, learned features | | Segment Anything (SAM) | Semantic boundaries in natural images |
For industrial vision โ where edges correspond to physical object boundaries and SNR is high โ Canny remains the most reliable and interpretable choice.