Blurring and convolution filters

OpenCV · Practical · 12 min

Denoising is the first step before thresholding, edges or features, and each filter trades detail for noise differently.

A filter replaces each pixel with a function of its neighbourhood. Linear filters compute a weighted sum defined by a kernel.

  • cv2.blur(img, (5, 5)): box filter, the plain average
  • cv2.GaussianBlur(img, (5, 5), sigmaX): weights fall off with distance; the go-to smoother. Kernel sizes must be odd; pass (0, 0) with a sigma to let OpenCV pick the size.
  • cv2.medianBlur(img, 5): takes the median, which removes salt-and-pepper noise completely while keeping edges sharper than averaging. Not linear.
  • cv2.bilateralFilter(img, d, sigmaColor, sigmaSpace): averages only pixels that are close and similar in colour, so it smooths flat areas but preserves edges. Much slower.

cv2.filter2D(img, ddepth, kernel) applies any kernel you define, such as sharpening or embossing. ddepth=-1 keeps the input dtype; use cv2.CV_32F when results can go negative or above 255. Strictly it computes correlation, not convolution, which only matters for asymmetric kernels.

At the borders the kernel overhangs the image. The borderType argument decides how missing pixels are invented; the default BORDER_REFLECT_101 mirrors the image without repeating the edge pixel.

Separable kernels like the Gaussian are computed as a row pass then a column pass, so even large blurs are cheap.

Example

import cv2
import numpy as np

img = np.full((50, 50), 128, np.uint8)
noisy = img.copy()
rng = np.random.default_rng(0)
ys, xs = rng.integers(0, 50, (2, 100))
noisy[ys, xs] = 255                              # salt noise

box = cv2.blur(noisy, (5, 5))
gauss = cv2.GaussianBlur(noisy, (5, 5), 0)
median = cv2.medianBlur(noisy, 5)
print(box.max(), gauss.max())                   # still above 128: spikes smeared
print(median.max())                             # 128 -- median removed them

sharpen = np.array([[0, -1, 0], [-1, 5, -1], [0, -1, 0]], np.float32)
step = np.zeros((5, 5), np.uint8); step[:, 2:] = 100
print(cv2.filter2D(step, -1, sharpen)[2])       # [  0   0 200 100 100] clipped
print(cv2.filter2D(step, cv2.CV_32F, sharpen)[2])  # [   0. -100.  200.  100.  100.]

Remember

  • Gaussian blur is the default smoother; kernel sizes are odd
  • Median blur removes salt-and-pepper noise and keeps edges
  • Bilateral filter preserves edges at a high cost in speed
  • filter2D applies custom kernels; use a float ddepth for signed results

Watch out for

  • Using filter2D with ddepth=-1 on uint8 when the result can be negative, silently clipping it to 0.

Check yourself

  1. An image has scattered pure white and black pixels. Which filter removes them best?

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

Official docs: docs.opencv.org/4.x/d4/d13/tutorial_py_filtering.html