Gradients and Canny edges

OpenCV · Practical · 12 min

Edges outline objects and feed contour finding, line detection and many classic measurement pipelines.

An edge is where intensity changes sharply. cv2.Sobel(gray, ddepth, dx, dy, ksize) estimates the derivative in x or y. Use a signed output depth such as cv2.CV_16S or cv2.CV_64F: with uint8 output, negative slopes (light-to-dark edges) are clipped to zero and lost. Convert back for display with cv2.convertScaleAbs. cv2.Laplacian gives the second derivative and is a quick focus or blur measure: its variance drops for blurry images.

The Canny detector, cv2.Canny(gray, low, high), turns gradients into thin, connected edge lines:

  • it smooths and computes gradient magnitude and direction,
  • keeps only local maxima across the edge (non-maximum suppression), giving one-pixel-wide lines,
  • applies hysteresis: pixels above high are edges, pixels between low and high survive only if connected to a strong edge.

A ratio of about 1:2 or 1:3 between low and high is typical. A common heuristic derives both from the median intensity of the image. Blur first if the image is noisy, or Canny will report texture as edges.

Canny returns a binary uint8 image (0 or 255), ready for findContours or cv2.HoughLinesP to detect straight segments.

Example

import cv2
import numpy as np

img = np.zeros((60, 60), np.uint8)
img[:, 30:] = 200                         # vertical step edge at x = 30

gx8 = cv2.Sobel(img[:, ::-1].copy(), cv2.CV_8U, 1, 0)   # dark-to-light reversed
print(gx8.max())                          # 0  -- negative slope clipped away!
gx = cv2.Sobel(img[:, ::-1].copy(), cv2.CV_16S, 1, 0)
print(gx.min())                           # -800  kept with a signed depth
print(cv2.convertScaleAbs(gx).max())      # 255 for display

edges = cv2.Canny(img, 50, 150)
print(np.unique(edges))                   # [  0 255]
print(np.nonzero(edges[30])[0])           # one-pixel wide edge near x = 30

sharp_var = cv2.Laplacian(img, cv2.CV_64F).var()
blurry_var = cv2.Laplacian(cv2.GaussianBlur(img, (9, 9), 0), cv2.CV_64F).var()
print(sharp_var > blurry_var)             # True: a simple blur detector

Remember

  • Use a signed ddepth (CV_16S, CV_64F) for Sobel so negative gradients survive
  • Canny gives thin binary edges using two hysteresis thresholds
  • Blur noisy images before edge detection
  • Variance of the Laplacian is a quick sharpness score

Watch out for

  • Running Sobel with a uint8 output and losing every light-to-dark edge.

Check yourself

  1. In `cv2.Canny(img, 50, 150)`, a pixel with gradient 100 becomes an edge when...

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.opencv.org/4.x/da/d22/tutorial_py_canny.html