Exploring Noise Filtering in Image Processing Part 2: Noise Filters — Mean, Median, Gaussian, K-Closest Filters

Pasindu Chamod Madusha
7 min readDec 24, 2023

--

In the previous article, I have discussed noises and the distribution of them. In this, I'll explain how we can reduce the impact of noise by using noise filters. Link to the first article https://medium.com/@epcm18/exploring-noise-filtering-in-image-processing-a-deep-dive-into-four-methods-d0df3961a9e3

Frame Averaging

Let’s uncover the magic behind a simple yet powerful noise reduction technique frame averaging. Imagine taking a series of pictures of a still scene with a slightly shaky hand, resulting in some blurriness. Frame averaging comes to the rescue by blending these pictures, creating a clear and stable image. However, for this method to work effectively, we need to consider a few things about the scene, like whether it’s a dynamic or static setting and the frame rate used when capturing.

Frame averaging isn’t a one-size-fits-all solution. It shines in scenarios where the scene remains mostly still. Think of it as a digital stabilizer for your photos. This technique isn’t just for photography; it’s also handy in video processing, where it can make images sharper at the cost of some smoothness. It’s like turning a bunch of slightly shaky frames into one clear and stable picture.

The cool part about frame averaging is that it processes images one after another without complicated loops. But, like any superhero, it has its limitations. It struggles when dealing with a bunch of frames or dynamic scenes, leading to potential blurriness in the final result.

Mean Filter

So, the mean filter is kind of like getting the average value, but where do we get this average value from? There is a property called a kernel in an image, which is a matrix representing the neighborhood around a certain pixel. Since image features are correlated, the neighborhood pixels should contain almost the same values. We use this property to reduce noise, calling it the mean filter.

Here’s what it does: it gets average values from an introduced kernel and applies that value to the current pixel. We go through all the pixels until we update all of them. This works very well for noise distributions with a zero mean.

But what if I apply this to uniform or salt and pepper noise? For uniform noise, it’s fine; it will do the job. But in salt and pepper noise, we can still avoid issues by going for extreme values, although it depends on the selected neighborhood. For a larger neighborhood, it’s good, but blurriness will be high. For a small neighborhood, this won’t work.

Mean Filter Equation

Here is an example code that we can use for Mean Filter.

import cv2 as cv
import numpy as np

def mean_filter(image, kernal_size):
# pad the image with zeros to avoid border issues
padded_img = cv.copyMakeBorder(image, kernal_size//2, kernal_size//2, kernal_size//2, kernal_size//2, cv.BORDER_CONSTANT)

# create an empty image to store the filtered image
filtered_img = np.zeros_like(image)

# iterate over the image
for row in range(image.shape[0]):
for col in range(image.shape[1]):
# get the current window
window = padded_img[row:row+kernal_size, col:col+kernal_size]
# apply the mean filter
filtered_img[row, col] = np.mean(window)

return filtered_img

Blurring Effect by Mean Filter

Here, we primarily leverage the locality property of noise. However, pixel variations may not only result from noise. Consequently, if we take the mean value from surrounding pixels, it may be unrelated to the current pixel. This can cause blurriness in the image. To mitigate this blurriness effect, we employ additional techniques by introducing new noise filters. These include the Gaussian filter, threshold averaging, and k-closest averaging methods.

Gaussian Filter

When applying the Gaussian Filter to an image, the initial step involves defining the size of the kernel or matrix used for smoothing the image. Typically, these sizes are odd numbers, allowing the computation of results centered around the central pixel. Additionally, the kernels are symmetric, meaning they have an equal number of rows and columns. The values within the kernel are determined by the Gaussian function(mathematical function in statistics), represented as follows:

Gaussian Function

Instead of directly applying mean value for the pivot pixel, here we use a mathematical function to check the correlation of the neighborhood pixels.

Threshold Averaging Filter

In this method, before updating the pivot value of the kernel, we introduce a condition. When the neighborhood exhibits minimal pixel variations, the difference should only reflect noise, resulting in less blurriness. If the difference is significant, it may lead to blurring. Therefore, before updating the pivot pixel value, a post-condition is applied as follows.

Threshold Averaging

Here is a sample code snippet, of how to use threshold averaging in an image.

import cv2 as cv
import numpy as np

def threshold_averaging_filter(image, kernal_size, threshold):
# pad the image with zeros to avoid border issues
padded_img = cv.copyMakeBorder(image, kernal_size//2, kernal_size//2, kernal_size//2, kernal_size//2, cv.BORDER_CONSTANT)

# create an empty image to store the filtered image
filtered_img = np.zeros_like(image)

# iterate over the image
for row in range(image.shape[0]):
for col in range(image.shape[1]):
center_pixel = image[row, col]
# threshold = 200 we need to calculate the optimal threshold or can be set manually as well
# _, optimal_threshold = cv.threshold(padded_img[row:row+kernal_size, col:col+kernal_size], 0, 255, cv.THRESH_BINARY + cv.THRESH_OTSU)

# print((optimal_threshold[0,0]))
# if center pixel is less than the optimal threshold then apply the mean filter
if center_pixel < (threshold):
# get the current window
window = padded_img[row:row+kernal_size, col:col+kernal_size]
# apply the mean filter
filtered_img[row, col] = np.mean(window)

return filtered_img

K-Closest Averaging Filter

Here, we select k number of pixels with values closest to the pivot pixel (pre-selection). We first sort the pixel values and then choose the appropriate pixels for applying this filter. The goal here is to avoid any outliers that could distort image features. While this approach is superior to threshold averaging, its only drawback lies in its higher computational demands, involving the search for k values and the sorting of pixel values.

Here is the code example for K-Closest Averaging Filter

import cv2 as cv
import numpy as np

def k_closest_averaging_filter(image, kernal_size, k):

image = image.astype(np.float32) # convert the image to float32, as there can be overflow issues with uint8

# pad the image with zeros to avoid border issues
padded_img = cv.copyMakeBorder(image, kernal_size//2, kernal_size//2, kernal_size//2, kernal_size//2, cv.BORDER_CONSTANT)

# create an empty image to store the filtered image
filtered_img = np.zeros_like(image)

# iterate over the image
for row in range(image.shape[0]):
for col in range(image.shape[1]):
center_pixel = image[row, col]

# list to store the pixel values and their distances from the center pixel
pixel_distance_list = []

# iterate over the window
for i in range(row, row+kernal_size):
for j in range(col, col+kernal_size):
neighbor_pixel = padded_img[i, j]
distance = np.linalg.norm(center_pixel - neighbor_pixel) # calculate the distance between the center pixel and the neighbor pixel
pixel_distance_list.append((neighbor_pixel, distance)) # append the pixel and its distance from the center pixel

# sort the list based on the distance
pixel_distance_list.sort(key=lambda x: x[1])
k_nearest_values = [i[0] for i in pixel_distance_list[:k]] # get the k nearest values
# number of closest elements that you want k = (kernal_size*kernal_size). Depends on situation you can change this k value

# apply the mean filter
filtered_img[row, col] = np.mean(k_nearest_values)

return filtered_img.astype(np.uint8) # convert the image back to uint8

Median Filter

This is the same as applying the mean filter. Instead of getting the mean value from the kernel, here we get the median value. This is best for salt pepper noise distribution, as this will reduce the extreme values that the image has.

import cv2 as cv
import numpy as np

def median_filter(image, kernal_size):
# pad the image with zeros to avoid border issues
padded_img = cv.copyMakeBorder(image, kernal_size//2, kernal_size//2, kernal_size//2, kernal_size//2, cv.BORDER_CONSTANT)

# create an empty image to store the filtered image
filtered_img = np.zeros_like(image)

# iterate over the image
for row in range(image.shape[0]):
for col in range(image.shape[1]):
# get the current window
window = padded_img[row:row+kernal_size, col:col+kernal_size]
# apply the median filter
filtered_img[row, col] = np.median(window)

return filtered_img

So following are the outputs that I have obtained by using all of the noise filters mentioned above by applying to a noisy image.

Source Image
Outputs

It is our task to choose the best filtering method according to the source image. As given above all of the noise filtering methods may not work for every image.

Let’s meet in another article.

--

--