Unit 03.00: Blurring as a decision about what to discard
Blurring is not a cleaning step. It is a decision about what to throw away.
Noise and edges fall together
Increasing the kernel size reduces noise and softens edges, in the same proportion.
The code measures both across four kernel sizes.
import cv2
import numpy as np
image = np.zeros((32, 32), dtype="uint8")
image[8:24, 8:24] = 255
noise = (np.random.default_rng(0).normal(0, 30, image.shape)).astype(int)
noisy = np.clip(image.astype(int) + noise, 0, 255).astype("uint8")
print(f"{'kernel':>7} {'noise std':>10} {'edge sharpness':>15}")
for k in (1, 3, 7, 15):
blurred = cv2.GaussianBlur(noisy, (k, k), 0)
flat_std = blurred[2:6, 2:6].std()
edge = int(abs(int(blurred[16, 7]) - int(blurred[16, 9])))
print(f"{k:>7} {flat_std:>10.1f} {edge:>15}")
# Bigger kernels remove more noise and more edge. There is no setting that
# removes only the noise -- blurring is a decision about what to discard, and
# the two columns move together.
The two columns move together and there is no setting where only one moves. That is the whole nature of a low-pass filter: it removes small, fast changes, and both noise and edge detail are small fast changes.
So the right kernel is the largest one that still leaves your feature distinguishable - which means you have to know how large your feature is in pixels.
The mistake this prevents
The mistake is blurring by reflex because the images look noisy. If the thing you are detecting is fine-grained, blurring removes the signal faster than the noise, and the pipeline gets worse in a way that looks like a modelling problem.
Takeaway
Blurring discards small fast changes, which includes both noise and detail. Choose the kernel from the size of your feature, and measure both effects.
