A blur filter smooths noise by averaging neighbours
An image filter changes every pixel using the pixels around it. A blur filter replaces each pixel with the average of its neighbours, which smooths out the random speckle called noise. Smoothing first makes thresholds and edges far more reliable.
In this lesson you apply a kernel by hand, then compare a blur with a median filter.
By the end of this lesson
Note
I can smooth noise in a picture with a blur or a median filter.
Key words
| Word | What it means | Example |
|---|---|---|
| Noise | random speckle that is not part of the scene | |
| Kernel | a small grid of weights slid over a picture | 3 by 3 |
| Convolution | sliding a kernel over a picture and adding the products | |
| Blur | a filter that averages each pixel with its neighbours | |
| Median | the middle value when numbers are put in order |
The cells students fill in are left blank in this preview.
A kernel slides over a picture, adding products
A kernel is a small grid of weights, often 3 by 3. Place it over a patch of nine pixels. Multiply each pixel by the weight above it, and add the nine products. That sum becomes one pixel of the new picture.
Slide the kernel one pixel along and repeat. This slide-and-add is called convolution.
Worked example: a box blur on a grid
| Place | The nine pixels | Sum with all weights 1 | Divide by 9 |
|---|---|---|---|
| Columns 0 to 2 | 0 0 9 on each of three rows | 27 | 3 |
| Columns 1 to 3 | 0 9 9 on each of three rows | 54 | 6 |
The cells students fill in are left blank in this preview.
Check the two sums with NumPy
docker exec jupyter python3 -c "import numpy as np; a = np.array([[0, 0, 9, 9], [0, 0, 9, 9], [0, 0, 9, 9]]); print(a[:, 0:3].mean(), a[:, 1:4].mean())"Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Slide a box blur over a grid
grid = [
[0, 0, 9, 9],
[0, 0, 9, 9],
[0, 0, 9, 9],
]
kernel = [[1, 1, 1], [1, 1, 1], [1, 1, 1]]
row = []
for column in range(2):
total = 0
for a in range(3):
for b in range(3):
total = total + grid[a][column + b] * kernel[a][b]
row.append(total / 9)
print(row)
# Change the kernel to [[0, 0, 0], [0, 1, 0], [0, 0, 0]] and divide by 1.
# What does that kernel do to the picture?
A blur removes noise and softens detail together
Each new pixel is an average of its neighbours, so a lone bright speck is pulled towards its surroundings. The picture becomes smoother.
The cost is that real detail is averaged too. A sharp line becomes a soft one. A wider kernel smooths more and softens more.
Blur the page scan and measure the noise
docker exec jupyter python3 steps/blur.pyShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
The spread of an empty patch is noise
The lab's page scan has speckle. The script picks a patch with no writing in it, so any variation there is noise. It measures its spread, the standard deviation of its grey levels.
Before the blur it prints 25.3. After a Gaussian blur it prints 4.9. A Gaussian blur is an average that counts close neighbours more than far ones.
A median filter ignores a lone bright speck
A blur is thrown by an extreme pixel, because an average moves towards it. The median does not. Put the nine pixels in order and take the middle one.
The median filter replaces each pixel with the median of its patch. A single speck of 255 sits at the end of the order and is never picked.
Worked example: one speck among nine pixels
| Measure | Working | Result |
|---|---|---|
| The nine pixels | 10, 12, 11, 255, 9, 10, 12, 11, 10 | |
| In order | 9, 10, 10, 10, 11, 11, 12, 12, 255 | |
| Mean | 340 divided by 9 | 37.8 |
| Median | the fifth in order | 11 |
The cells students fill in are left blank in this preview.
Compare a median filter with a box blur
docker exec jupyter python3 -c "import numpy as np; from PIL import Image, ImageFilter; a = Image.open('images/scan.png'); b = a.filter(ImageFilter.MedianFilter(3)); c = a.filter(ImageFilter.BoxBlur(1)); print(round(float(np.array(a)[10:50, 10:90].std()), 1), round(float(np.array(b)[10:50, 10:90].std()), 1), round(float(np.array(c)[10:50, 10:90].std()), 1))"Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
A median beats a same-size box blur
The Run prints three spreads for the same empty patch: 25.3 6.0 9.1. They are the original, the median filter and the box blur.
The scan has black and white speckles, which a median handles well. At the same small size it cut the spread to 6.0, and the box blur only to 9.1.
A common mistake
Warning
Many people think a blur only removes noise. It also removes real detail, such as thin lines and sharp edges. Blur just enough to calm the noise, and no more, or the edges you want to find next are gone.
Going further
In a notebook, blur scan.png with ImageFilter.GaussianBlur(5) and with GaussianBlur(1). Print the spread of the empty patch after each.
Then look at the writing in both. Where does extra blur stop helping?