BEHIND THE SCENES OF IMAGE PROCESSING (3 OF 9)

Image Processing using Python — Spatial Filters and Morphological Operations

Francis Camarao
8 min readJun 12, 2023

Picture this: I stumbled upon a stunning vase brimming with preserved flowers, and I couldn’t resist the urge to capture its beauty through my lens. But here’s the exciting part: I decided to unleash my inner magician and experiment with spatial filters and morphological operations to take its allure to the next level. Join me on this adventurous journey as we dive into the world of image manipulation and witness the transformation of my floral masterpiece!

Spatial Filters

Imagine wielding a digital paintbrush that can magically enhance or transform images pixel by pixel. Spatial filters are precisely that enchanting tool in the realm of image processing. These filters possess the power to selectively modify pixel values based on the company they keep in the image’s neighborhood. With these mathematical sorceries, we can perform feats like sharpening edges, smoothening textures, detecting intricate patterns, and even banishing noise. By convolving spatial filters across the image canvas, we unravel hidden visual gems and unlock the true potential of digital imagery.

Lets load the preserved flowers!

import matplotlib.pyplot as plt
from skimage.io import imread
from skimage.color import rgb2gray

flower_original = imread('flower_1.png')
flower_gray = rgb2gray(flower_original)

# Create a 1x2 subplot grid
fig, axs = plt.subplots(1, 2, figsize=(12, 6))

# Plot the original image in the first subplot
axs[0].imshow(flower_original)
axs[0].set_title('Original Image')
axs[0].axis('off')

# Plot the grayscale image in the second subplot
axs[1].imshow(flower_gray, cmap='gray')
axs[1].set_title('Grayscale')
axs[1].axis('off')

plt.tight_layout()

# Show the plot
plt.show()

In this code snippet, we begin by loading a colored image (flower_original) using the imread function from the skimage.io module. To make the image compatible with spatial filters and the provided kernels, we then convert it to grayscale using the rgb2gray function from the skimage.color module, resulting in the grayscale image (flower_gray).

Spatial filters, such as the kernels in this example, operate on individual pixels in an image and analyze the intensity variations or gradients in the spatial domain. By converting the image to grayscale, we eliminate the color channels and retain only the intensity information, allowing the kernels to effectively detect edges and perform filtering operations based on intensity changes.

import numpy as np

# Sobel Operators
# Horizontal Sobel Filter
kernel1 = np.array([[1, 2, 1],
[0, 0, 0],
[-1, -2, -1]])

# Vertical Sobel Filter
kernel2 = np.array([[1, 0, -1],
[2, 0, -2],
[1, 0, -1]])

# Left Diagonal Filter
kernel3 = np.array([[1, -1, -1],
[-1, 1, -1],
[-1, -1, 1]])

# Right Diagonal Filter
kernel4 = np.array([[-1, -1, 1],
[-1, 1, -1],
[1, -1, -1]])

# Edge Detection
kernel5 = np.array([[-1, -1, -1],
[-1, 8, -1],
[-1, -1, -1]])

# Sharpen
kernel6 = np.array([[0, -1, 0],
[-1, 5, -1],
[0, -1, 0]])

# Box Blur
kernel7 = (1 / 9.0) * np.array([[1., 1., 1.],
[1., 1., 1.],
[1., 1., 1.]])

# Gaussian Blur
kernel8 = (1 / 16.0) * np.array([[1., 2., 1.],
[2., 4., 2.],
[1., 2., 1.]])

In this section, we will showcase the captivating effects of the kernels provided above by displaying their results. Through this visual exploration, we can witness the transformative power of these kernels and observe the profound impact they have on the image.

import matplotlib.pyplot as plt
from scipy.signal import convolve2d

# Create a list of kernel names for labeling
kernel_names = ['Original Image', 'Grayscale', 'Horizontal Sobel',
'Vertical Sobel', 'Left Diagonal', 'Right Diagonal',
'Edge Detection', 'Sharpen', 'Box Blur', 'Gaussian Blur']

# Create a 2x5 subplot grid
fig, axs = plt.subplots(2, 5, figsize=(16, 8))

# Plot the original image in the first subplot
axs[0, 0].imshow(flower_original)
axs[0, 0].set_title(kernel_names[0])
axs[0, 0].axis('off')

# Plot the grayscale image in the second subplot
axs[0, 1].imshow(flower_gray, cmap='gray')
axs[0, 1].set_title(kernel_names[1])
axs[0, 1].axis('off')

# Loop over the kernels and plot them in the remaining subplots
for i, kernel in enumerate([kernel1, kernel2, kernel3, kernel4,
kernel5, kernel6, kernel7, kernel8]):
# Apply the current kernel to the image
conv_im = convolve2d(flower_gray, kernel, 'valid')

# Compute the subplot indices
row = (i + 2) // 5
col = (i + 2) % 5

# Plot the convolved image with the current kernel
axs[row, col].imshow(abs(conv_im), cmap='gray')
axs[row, col].set_title(kernel_names[i+2])
axs[row, col].axis('off')

plt.tight_layout()
# Display the plot
plt.show()

When we look at the world, our eyes are drawn to the edges and boundaries that define objects and shapes. Similarly, the horizontal Sobel, vertical Sobel, and edge detection filters in image processing mimic this visual instinct. By convolving these filters with an image, we can identify regions where there are abrupt changes in intensity or color, which are like the striking contours that catch our attention.

Morphological Operations

Welcome to the enchanting realm of morphological operations, where images come to life in delightful and whimsical ways! These operations are like magical tools that can shrink, expand, twist, and twirl the pixels of an image, creating fantastical visual effects. Imagine erasing unwanted imperfections, stretching and morphing shapes, and even connecting distant elements to form new patterns. It’s like being a digital sorcerer, conjuring up captivating transformations and unleashing your creativity on the canvas of pixels. Get ready to embark on a joyous adventure of image metamorphosis, where every click and command brings a playful burst of magic and fun to the visual world. Let the morphological magic begin!

import matplotlib.pyplot as plt
from skimage.io import imread
from skimage.morphology import dilation, erosion

# Load the flower image
flower_image = imread('flower_1.png')

# Perform dilation
dilated_image = dilation(flower_image)

# Perform erosion
eroded_image = erosion(flower_image)

# Create a 1x3 subplot grid
fig, axs = plt.subplots(1, 3, figsize=(12, 4))

# Plot the original image in the first subplot
axs[0].imshow(flower_image)
axs[0].set_title('Original Image')
axs[0].axis('off')

# Plot the dilated image in the second subplot
axs[1].imshow(dilated_image)
axs[1].set_title('Dilated Image')
axs[1].axis('off')

# Plot the eroded image in the third subplot
axs[2].imshow(eroded_image)
axs[2].set_title('Eroded Image')
axs[2].axis('off')

# Adjust the spacing between subplots
plt.tight_layout()

# Display the plot
plt.show()

The change in color is a result of how the dilation and erosion operations are applied to the image.

Dilation is a morphological operation that expands the boundaries of objects in an image. It achieves this by replacing each pixel in the image with the maximum pixel value in its neighborhood. In the case of a colored image, dilation affects each color channel independently, potentially leading to changes in color.

Erosion, on the other hand, shrinks the boundaries of objects by replacing each pixel with the minimum pixel value in its neighborhood. Similar to dilation, erosion also operates on each color channel individually, potentially causing alterations in color.

Therefore, when applying dilation and erosion to a colored image, changes in color can occur due to the manipulation of pixel values in each channel. It is essential to consider these color transformations when using morphological operations on colored images.

To better showcase the effects of double dilation and double erosion, let’s apply these operations to different images, allowing us to observe their enhanced impacts. By utilizing distinct images, we can easily discern the transformative influence of these morphological operations when repeated. Prepare to witness the fascinating results of double dilation and double erosion on a new set of images.

import matplotlib.pyplot as plt
from skimage.io import imread
from skimage.color import rgb2gray

# Load the images
art1 = rgb2gray(imread('art1.png'))
art2 = rgb2gray(imread('art2.png'))
art3 = rgb2gray(imread('art3.png'))

# Create a 1x3 subplot grid
fig, axs = plt.subplots(1, 3, figsize=(12, 4))

# Plot the first image in the first subplot
axs[0].imshow(art1, cmap='gray')
axs[0].set_title('Art 1')
axs[0].axis('off')

# Plot the second image in the second subplot
axs[1].imshow(art2, cmap='gray')
axs[1].set_title('Art 2')
axs[1].axis('off')

# Plot the third image in the third subplot
axs[2].imshow(art3, cmap='gray')
axs[2].set_title('Art 3')
axs[2].axis('off')

# Adjust the spacing between subplots
plt.tight_layout()

# Display the plot
plt.show()

The objective now for each art is:

Art 1 — remove the straight lines

Art 2 — remove the horizontal lines

Art 3 — connect the gaps

import numpy as np
import matplotlib.pyplot as plt
from skimage.morphology import erosion, dilation, opening, closing, disk
from skimage.color import rgb2gray

# Define the structuring elements
horizontal_selem = np.array([[0, 0, 0],
[1, 1, 1],
[0, 0, 0]])

vertical_selem = np.array([[0, 1, 0],
[0, 1, 0],
[0, 1, 0]])

diamond_selem = np.array([[0, 1, 0],
[1, 1, 1],
[0, 1, 0]])

x_pattern_selem = np.array([[1, 0, 1],
[0, 1, 0],
[1, 0, 1]])

# Create a circular structuring element
circ_selem = disk(6)

# Define helper functions for morphological operations
def apply_morphological_operation(img, operation, num, selem=None):
for i in range(num):
img = operation(img, selem)
return img

# Perform morphological operations on the artworks
artwork1 = apply_morphological_operation(artwork1, erosion, 2, horizontal_selem)
artwork1 = apply_morphological_operation(artwork1, erosion, 2, vertical_selem)
artwork1 = apply_morphological_operation(artwork1, dilation, 1, circ_selem)
artwork1 = apply_morphological_operation(artwork1, erosion, 1, diamond_selem)
artwork1 = apply_morphological_operation(artwork1, erosion, 2, x_pattern_selem)

artwork2 = apply_morphological_operation(artwork2, erosion, 2, horizontal_selem)

artwork3 = apply_morphological_operation(artwork3, closing, 2, vertical_selem)
artwork3 = apply_morphological_operation(artwork3, closing, 2, horizontal_selem)

# Create a 1x3 subplot grid
fig, axs = plt.subplots(1, 3, figsize=(12, 4))

# Plot the transformed artworks
artworks = [artwork1, artwork2, artwork3]
art_names = ['Artwork 1', 'Artwork 2', 'Artwork 3']

for i, ax in enumerate(axs):
ax.imshow(artworks[i], cmap='gray')
ax.set_title(art_names[i])
ax.axis('off')

# Adjust the spacing between subplots
plt.tight_layout()

# Display the plot
plt.show()

Closing Thoughts

I focused on the process of removing and connecting elements. My objective was to achieve a sense of perfection in the final results. In Art 1, I observed that the circles became slightly smaller, adding a subtle visual impact to the composition. However, in Art 3, I encountered disconnected elements, particularly at the intersections, indicating the need for further refinement. By experimenting with various combinations of structuring elements and morphological operations, I aimed to improve these outcomes. Additionally, in Art 2, I successfully eliminated vertical lines, showcasing the effectiveness of image processing techniques. This journey allowed me to uncover the possibilities of creating captivating transformations in art through deliberate image manipulation.

References

Benjur Emmanuel L. Borja. 2023. Image Processing(MSDS2023). Asian Institute of Management.

Did you like this article?

Feel free to connect with me on LinkedIn. Let’s establish a professional network and stay in touch. Additionally, you can explore my GitHub profile for the complete implementation of this project and discover more fascinating projects I have worked on.

--

--