Apply Median Filter Using NumPy in Python (Noise Reduction)
In this tutorial, we’ll cover several key aspects: explaining what a median filter is, creating a kernel using NumPy, padding the image array, and iterating through the image to apply the filter.
We’ll work with different kernel sizes to observe their effects on image smoothing to enhance your skills in image processing applications.
What is the Median Filter?
In image processing and data analysis, a median filter operates by replacing the value of a pixel with the median value of the intensities in the neighborhood of that pixel.
Let’s break it down: Imagine a grid of pixels, where each pixel has a certain intensity value.
The median filter looks at each pixel and considers a small surrounding area, or a ‘window’, around it.
This window can be of various sizes, such as 3×3, 5×5, etc., depending on the level of detail you wish to preserve or remove in the image.
For each pixel, the filter sorts the values of all the pixels in its window and finds the median value.
This median value is then used as the new value for the central pixel. This process is repeated for every pixel in the image.
The key benefit of using a median filter is its ability to remove noise from images.
Create Kernel
Creating a kernel is an essential step in applying a median filter. The kernel defines the neighborhood of each pixel that will be sampled to find the median value.
You can create a kernel using NumPy:
import numpy as np kernel_size = 3 # 3x3 kernel kernel = np.ones((kernel_size, kernel_size), dtype=np.uint8) print(kernel)
Output:
[[1 1 1] [1 1 1] [1 1 1]]
In this code, we created a 3×3 kernel. The np.ones
function generates an array where all values are 1s, and the size of the array is determined by kernel_size
.
This kernel signifies that each pixel and its eight immediate neighbors will be considered for finding the median.
Pad Image Array
Padding the image array is a necessary step before applying the median filter.
This is because when the kernel reaches the borders of the image, it lacks neighboring pixels outside the image boundaries.
To address this, we add rows and columns of zeros (0s) around the edges of the image, creating a border that allows the kernel to operate at the edges.
Here’s how you can do this using NumPy:
import numpy as np # Sample image array image = np.array([[10, 20, 30], [40, 50, 60], [70, 80, 90]]) padded_image = np.pad(image, pad_width=1, mode='constant', constant_values=0) print(padded_image)
Output:
[[ 0 0 0 0 0] [ 0 10 20 30 0] [ 0 40 50 60 0] [ 0 70 80 90 0] [ 0 0 0 0 0]]
In this example, we start with a simple 3×3 image array. Using np.pad
, we add a padding of 1 pixel width around the image.
The mode='constant'
and constant_values=0
arguments ensure that the padding is filled with zeros.
The resulting padded_image
is larger than the original, with a border of zeros that facilitates the median filtering process at the edges.
Iterate Through Image
Once the image array is padded, the next step is to iterate through the image and apply the median filter at each position.
This involves moving the kernel over each pixel, considering the pixel values under the kernel, and replacing the central pixel with the median of these values.
We can do this using nested for loops:
import numpy as np # Example padded image array padded_image = np.array([[ 0, 0, 0, 0, 0], [ 0, 10, 20, 30, 0], [ 0, 40, 50, 60, 0], [ 0, 70, 80, 90, 0], [ 0, 0, 0, 0, 0]]) kernel_size = 3 half_k = kernel_size // 2 # Empty array to store the filtered image filtered_image = np.zeros_like(padded_image) # Iterate through the image for i in range(half_k, padded_image.shape[0] - half_k): for j in range(half_k, padded_image.shape[1] - half_k): # Extract the window window = padded_image[i - half_k:i + half_k + 1, j - half_k:j + half_k + 1] # Compute the median and assign it to the filtered image filtered_image[i, j] = np.median(window) # Removing the padding filtered_image = filtered_image[half_k:-half_k, half_k:-half_k] print(filtered_image)
Output:
[[ 0 20 0] [20 50 30] [ 0 50 0]]
In this code, we iterate over the padded image with the for loops.
The window
variable extracts the neighborhood defined by the kernel at each position.
We then calculate the median of this window and assign it to the corresponding position in the filtered_image
.
After iterating through the entire image, we remove the padding to get the final, filtered image.
Apply Median Filter
Applying the median filter at each position involves extracting a region of the image corresponding to the kernel size and then computing the median of the values within this region.
This step is important in median filtering as it determines the new value of each pixel based on its local neighborhood.
Here’s how you can implement this using NumPy:
import numpy as np padded_image = np.array([[ 0, 0, 0, 0, 0], [ 0, 10, 20, 30, 0], [ 0, 40, 50, 60, 0], [ 0, 70, 80, 90, 0], [ 0, 0, 0, 0, 0]]) kernel_size = 3 half_k = kernel_size // 2 # Empty array to store the filtered image filtered_image = np.zeros_like(padded_image) # Applying the median filter for i in range(half_k, padded_image.shape[0] - half_k): for j in range(half_k, padded_image.shape[1] - half_k): # Extract the kernel-sized region window = padded_image[i - half_k:i + half_k + 1, j - half_k:j + half_k + 1] # Find the median value filtered_image[i, j] = np.median(window) # Removing the padding filtered_image = filtered_image[half_k:-half_k, half_k:-half_k] print(filtered_image)
Output:
[[ 0 20 0] [20 50 30] [ 0 50 0]]
In this snippet, the code extracts a region (window
) of the image for each pixel that matches the size of the kernel.
The np.median
function then calculates the median of this region.
This median value replaces the original pixel value in the filtered_image
. After processing the entire image, we remove the added padding to return the image to its original dimensions.
Save Filtered Image
After applying the median filter and obtaining the filtered image, the final step is to save this processed image.
You can save the filtered image using libraries like OpenCV or PIL (Python Imaging Library):
from PIL import Image import numpy as np # Assuming 'filtered_image' is the result obtained from the median filter filtered_image = np.array([[0, 20, 0], [20, 50, 30], [0, 50, 0]], dtype=np.uint8) # Convert the NumPy array to an Image object filtered_image_pil = Image.fromarray(filtered_image) filtered_image_pil.save('filtered_image.png') print("Image saved successfully.")
Output:
Image saved successfully.
In this example, we first convert the NumPy array filtered_image
to an image object using PIL’s Image.fromarray
method.
Tweak Kernel Size
Adjusting the kernel size in median filtering impacts the level of smoothing in the image.
By experimenting with different kernel sizes, you can observe how it affect the image’s noise reduction and edge preservation.
Let’s see how changing the kernel size impacts the filtering process:
import numpy as np from PIL import Image # Function to apply median filter def apply_median_filter(image, kernel_size): half_k = kernel_size // 2 padded_image = np.pad(image, pad_width=half_k, mode='constant', constant_values=0) filtered_image = np.zeros_like(padded_image) for i in range(half_k, padded_image.shape[0] - half_k): for j in range(half_k, padded_image.shape[1] - half_k): window = padded_image[i - half_k:i + half_k + 1, j - half_k:j + half_k + 1] filtered_image[i, j] = np.median(window) return filtered_image[half_k:-half_k, half_k:-half_k] # Sample image image = np.array([[10, 20, 30], [40, 50, 60], [70, 80, 90]], dtype=np.uint8) # Apply median filter with different kernel sizes kernel_sizes = [3, 5, 7] for k_size in kernel_sizes: filtered_img = apply_median_filter(image, k_size) Image.fromarray(filtered_img).save(f'filtered_image_{k_size}x{k_size}.png') print("Filtered images saved with different kernel sizes.")
Output:
Filtered images saved with different kernel sizes.
In this example, we define a function apply_median_filter
that takes an image and a kernel size as inputs and applies the median filter.
We then test this function with different kernel sizes (3×3, 5×5, 7×7). Larger kernels encompass a wider area around each pixel, leading to the consideration of more neighboring pixels in the median calculation.
The effect of increasing the kernel size is more pronounced smoothing.
A larger kernel size tends to blur the image more, reducing noise more effectively but also potentially blurring edges and fine details.
Smaller kernel preserves more detail but is less effective at noise reduction.
Mokhtar is the founder of LikeGeeks.com. He is a seasoned technologist and accomplished author, with expertise in Linux system administration and Python development. Since 2010, Mokhtar has built an impressive career, transitioning from system administration to Python development in 2015. His work spans large corporations to freelance clients around the globe. Alongside his technical work, Mokhtar has authored some insightful books in his field. Known for his innovative solutions, meticulous attention to detail, and high-quality work, Mokhtar continually seeks new challenges within the dynamic field of technology.