Convolution & Image Filtering
Needs: Images as Arrays
What you’ll learn
- What a kernel is and how it slides over an image
- Why a box blur and a Gaussian blur look different
- How the Sobel kernel turns brightness changes into edges
Intuition
Every output pixel is a weighted sum of its neighbors. The weights form a small grid called a kernel. Change those nine numbers and the same operation can blur, sharpen or highlight edges.
The math
For an input image and a kernel , the output is
Strictly, this is cross-correlation. True convolution flips the kernel first, , which makes no difference for symmetric kernels such as a blur.
A blur kernel’s weights sum to , so flat regions keep their brightness. An edge kernel’s weights sum to , so flat regions become and only changes survive.
Code
From scratch with NumPy:
import numpy as np
def conv2d(img, K):
"""img: (H, W) float32, K: (3, 3). Edge-replicated borders."""
H, W = img.shape
p = np.pad(img, 1, mode="edge")
out = np.zeros_like(img)
for i in range(3):
for j in range(3):
out += K[i, j] * p[i:i + H, j:j + W]
return out
The same thing with OpenCV:
import cv2
import numpy as np
img = cv2.imread("cameraman.png", cv2.IMREAD_GRAYSCALE).astype(np.float32) / 255
sobel_x = np.array([[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]], np.float32)
edges = cv2.filter2D(img, -1, sobel_x)
Try it
Pick a preset, then edit individual cells. Try making the Sobel kernel detect horizontal edges instead of vertical ones.
Results and pitfalls
Drag the slider to compare the input with a Gaussian blur (). The fine stripes in the bottom right disappear first, because blurring is a low-pass filter.
Common bugs:
- Integer overflow. Filtering a
uint8image with a sharpening kernel clips negative values to 0. Work infloat32. - Border handling. Zero padding darkens the edges of a blurred image. Use
mode="edge"orcv2.BORDER_REFLECT.
Exercises
- Design a kernel that detects only horizontal edges.
- Why must a blur kernel’s weights sum to 1? What happens if they sum to 2?
- Show that applying a box blur twice equals one kernel. What does that kernel look like?