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).
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.
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).
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?