Skip to main content...
Python + Classical CV — the Catalog Tool
25 min

Day 9: OpenCV II: thresholding & edge detection (Canny, Sobel)

Finding boundaries: thresholding and edges

Thresholding

Binary thresholding converts a grayscale image into pure black/white by a single cutoff: pixels above the threshold become white, below become black. It's the simplest possible way to separate 'foreground-ish' from 'background-ish' pixels, and the conceptual ancestor of the binary masks you'll build all week. Adaptive thresholding computes a different local threshold per region instead of one global cutoff — necessary when lighting varies across a photo (a garment lit unevenly by a studio light, say).

Global vs adaptive thresholding
gray = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2GRAY)

_, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)

adaptive = cv2.adaptiveThreshold(
    gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, blockSize=11, C=2
)

What a convolution kernel does

Edge detection is your first hands-on encounter with a convolution kernel — a small matrix slid across every position of an image, at each position multiplying overlapping values and summing them into one output pixel. A Sobel kernel is built to respond strongly where brightness changes sharply in one direction (an edge) and near-zero where it's flat — the kernel's numbers are literally a hand-designed 'detector' for a gradient. This exact operation, convolution, is what Stage 1's CNNs learn the kernel values for automatically instead of hand-designing them.

A Sobel kernel, applied by hand — the same sliding-window operation a CNN layer performs
sobel_x = np.array([[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]])   # responds to vertical edges

grad_x = cv2.filter2D(gray.astype(np.float32), -1, sobel_x)
grad_y = cv2.filter2D(gray.astype(np.float32), -1, sobel_x.T)
edge_strength = np.sqrt(grad_x**2 + grad_y**2)

Payoff, later

A CNN convolutional layer (Stage 1) is exactly this sliding-window multiply-and-sum operation — the only difference is the kernel's numbers are learned from data by gradient descent instead of hand-designed like sobel_x above. If today's filter2D call makes sense, Stage 1's 'convolutional layer' will not be a new concept, just a new way to choose the kernel.

Canny: a full edge-detection pipeline

Canny edge detection chains four steps into one robust pipeline: Gaussian blur (reduce noise so it isn't mistaken for edges), Sobel-style gradients (find where brightness changes), non-maximum suppression (thin thick gradient responses down to single-pixel-wide lines — you'll meet 'NMS' again, applied to bounding boxes, in Stage 2's YOLO), and hysteresis thresholding (link strong edges through weak-but-connected ones, drop isolated weak responses).

Canny — Sobel plus noise reduction, thinning, and edge linking
blurred = cv2.GaussianBlur(gray, (5, 5), 0)
edges = cv2.Canny(blurred, threshold1=50, threshold2=150)

Key terms

Thresholding
Converting a grayscale image to binary by classifying each pixel above/below a cutoff value.
Convolution kernel
A small matrix slid across an image; at each position, overlapping values are multiplied and summed into one output value.
Non-maximum suppression (NMS)
Thinning a broad response down to its strongest, most precise points — used here for edge lines, later for detection bounding boxes.
Canny edge detector
A multi-step pipeline: blur, gradient (Sobel-like), non-max suppression, hysteresis thresholding.

Stage 0 exit criterion: you should now be able to

What does a convolution kernel actually compute at each image position?

We use cookies

We use cookies to enhance your browsing experience, serve personalized content, and analyze our traffic. By clicking "Accept All", you consent to our use of cookies. Learn more

    Day 9: OpenCV II: thresholding & edge detection (Canny, Sobel) | RBTechIconX