Monday, April 6th, 2026¶

Last week, we started looking at adding and removing salt and pepper noise from grayscale images.

In [1]:
import numpy as np
import matplotlib.pyplot as plt

Project 4: Image denoising¶

If you have a working sp_noise function, then you can use any grayscale image you'd like and add salt and pepper noise to generate a corresponding noisy_img array. Since I've not written such a function in class, I will use the pre-noised image (and the corresponding clean version) from the project page.

In [2]:
img = np.mean(plt.imread('everton.png')[:,:,:3], axis=2)
noisy_img = np.mean(plt.imread('noisy_img.png')[:,:,:3], axis=2)

We previously developed some code that would apply the mean filter.

In [3]:
nrows, ncols = noisy_img.shape

mean_filtered_img = noisy_img.copy()
for i in range(1,nrows-1):
    for j in range(1,ncols-1):
        grid = noisy_img[i-1:i+2, j-1:j+2]
        mean_filtered_img[i,j] = np.mean(grid)

The median filter works in much the same way.

In [4]:
nrows, ncols = noisy_img.shape

median_filtered_img = noisy_img.copy()
for i in range(1,nrows-1):
    for j in range(1,ncols-1):
        grid = noisy_img[i-1:i+2, j-1:j+2]
        median_filtered_img[i,j] = np.median(grid)

Exercise: Use the code above to write a function simple_mean_filter that applies the mean filter to non-edge pixels using a 3 by 3 grid.

In [ ]:
 

Exercise: Modify the simple_mean_filter function to write a simple_median_filter that instead applies the median filter.

In [ ]:
 

Note: We could combine these two functions into a single simple_filter function that takes in a noisy image array (noisy_img) along with some function (filter_func) that will be applied to each grid.

The filter_func input should be a function that takes in an 2D array and returns a float (e.g. np.mean or np.median).

In [ ]:
def simple_filter(noisy_img, filter_func=np.median):
    ...
    ...
    for ...
        for ...
            grid = ...
            filtered_img[i,j] = filter_func(grid)

    return filtered_img

Let's apply these filters to the noisy_img array and compare them. We can create a 2 by 2 grid of subplots that show the original image, the noisy version, and each filtered version. How do these filters perform?

In [8]:
fig = plt.figure(figsize=(10,8))

plt.subplot(2,2,1)
plt.imshow(img, cmap='gray', vmin=0, vmax=1)  # Show the original image
plt.axis('off')
plt.title('Original image')

plt.subplot(2,2,2)
plt.imshow(noisy_img, cmap='gray', vmin=0, vmax=1)  # Show the noisy image
plt.axis('off')
plt.title('Noisy image')

plt.subplot(2,2,3)
plt.imshow(mean_filtered_img, cmap='gray', vmin=0, vmax=1)  # Show the mean filtered image
plt.axis('off')
plt.title('Mean filter')

plt.subplot(2,2,4)
plt.imshow(median_filtered_img, cmap='gray', vmin=0, vmax=1)  # Show the median filtered image
plt.axis('off')
plt.title('Median filter')

plt.tight_layout()
No description has been provided for this image
In [ ]:
 

Note: It might be useful to write a function that produces figures like the one above. This way, we can use this plotting on several different examples of a noised and filtered image for comparison.

Dealing with edge pixels¶

With the simple filters written above, we have ignored filtering pixels along the edge of the image. Let's try to deal with them now.

The project page discusses adding extra rows/columns to our array so that we are able to construct a 3 by 3 grid centered at all pixels of our original image.

If we are using 3 by 3 grids, we need to add one extra row/column on all sides. Our strategy then is to:

  • Create an array padded_img that has two additional rows and two additional columns than the noisy_img array;
  • Place the contents of the noisy_img array into a slice of the padded_img array that skips the first and last rows and first and last columns;
  • Apply the the mean/median filter to each of the non-edge pixels of the padded_img (which corresponds to applying the filter to every pixel of the noisy_img array).
In [17]:
nrows, ncols = noisy_img.shape

padded_img = np.ones((nrows + 2, ncols + 2)) / 2   # Initialize padded array
#padded_img[:,:] = np.mean(noisy_img)
padded_img[1:-1, 1:-1] = noisy_img              # Set the noisy_img array into the center of padded_img

Let's take a closer look at one corner of the padded_img array:

In [11]:
plt.imshow(padded_img[:20, :20], cmap='gray', vmin=0, vmax=1)
Out[11]:
<matplotlib.image.AxesImage at 0x2177a01b890>
No description has been provided for this image

Now, we want to use the padded_img array to build our 3 by 3 grids for computing means/medians. Let's copy in our median filter code and make any necessary changes.

In [12]:
nrows, ncols = noisy_img.shape

#padded_img = get_padded_img(img, 1)

median_filtered_img = noisy_img.copy()
for i in range(nrows):
    for j in range(ncols):
        grid = padded_img[i:i+3, j:j+3]
        median_filtered_img[i,j] = np.median(grid)
In [16]:
plt.imshow(median_filtered_img, cmap='gray', vmin=0, vmax=1)
plt.axis('off')
Out[16]:
(np.float64(-0.5), np.float64(699.5), np.float64(494.5), np.float64(-0.5))
No description has been provided for this image

Note: We have added black border to the padded_img (since we used np.zeros), which propogates into the filtered_img when we take any mean that includes padded pixels. We could instead add a white border (by using np.ones), but that's not any better. We could split the difference by using np.ones(...)/2 to use a gray border. For the project, it might be a good idea to think of other ways that one could deal with filtering pixels along the edges.

Let's try to turn the sample code above into some functions that we can use more broadly.

Exercise: Write a function get_padded_img(img, pad) that takes in a 2D array img and returns a padded array that adds pad rows at the top, bottom, left, and right of the array. That is, the padded_img array should have 2*pad extra rows and 2*pad extra columns.

In [ ]:
 

Exercise: Write a function median_filter_with_edges that takes in a noisy image array and applies the mean/median filter to every pixel (including edges) using a 3 by 3 grid.

In [ ]: