OpenCV: Morphological Dilation and Erosion
In the previous articles, we have discussed the basics of image processing. Let’s move on to a section that is very important in image pre-processing.
In this article, we will dive into and discuss the basics of morphological operations. Morphological operations are a set of mathematical techniques used in image processing and computer vision. They are based on the principles of set theory and involve the manipulation of binary images, where pixels are either considered foreground (1) or background (0).
Imagine you have a drawing or a photograph. You can notice some dots or gaps in this image.
Let us take the above two images as an example. The first image has some kind of dots or drawings all over the image. The next image has cuts or gaps through the letters. This effect reduces the overall quality of the picture.
How can you treat this issue and improve the quality of the picture?
Morphological Operations
This is where morphological operations come into play. Morphological operations are like these special tools that we can use to change the shapes and structures in these pictures.
As we know now, these images are made up of tiny pixels. What we do with morphological operations is manipulate the pixels to either stick together or remove the unwanted pixels to alter the way things in the picture look.
Dilation and Erosion are the main morphological operations we are going to discuss and use in this article.
Morphological Dilation
Take this operation of dilation as a function. Dilation takes mainly two inputs.
1. The image that we need to apply the dilation in Black and White.
2. Kernel or the structural element.
Process of dilation
Let us have an image, converted to a black-and-white image (pixels have values either ‘1’ or ‘0’). We send a kernel, in most cases n-by-n matrix of ones over this image.
Example 1.1: Let our kernel be a 3x3 matrix of ones.
While this kernel is running over the image, if any element of the matrix come across with value ‘1’ of the image, the pixel which is overlapped with the kernel[1][1] element is turned to ‘1’.
Example 1.2: Let’s see how this is applied to a bigger image.
Dilation with OpenCV
First, let’s read the image, and convert it to black and white image.
#import the necessary libraries
import cv2
import numpy as np
import matplotlib.pyplot as plt
#read image
im=cv2.imread("open_cv.png")
#read grey scale image
gray_im=cv2.cvtColor(im,cv2.COLOR_BGR2GRAY)
#read BW image
thresh,BW_im = cv2.threshold(gray_im,150,255,cv2.THRESH_BINARY)
#visualize them
cv2.namedWindow("Original Image", cv2.WINDOW_NORMAL);
cv2.imshow("Original Image",im)
cv2.namedWindow("Gray scale image", cv2.WINDOW_NORMAL);
cv2.imshow("Gray scale image",gray_im)
cv2.namedWindow("BW Im", cv2.WINDOW_NORMAL);
cv2.imshow("BW Im",BW_im)
cv2.waitKey(0) & 0xFF
cv2.destroyAllWindows()
As we discussed earlier, in dilation, the kernel turns the necessary pixels from ‘0’ to ‘1’. Then, it is understandable that the pixels that need to be dilated (region of interest) should have the value ‘0’, meaning the gaps in this image should be ‘black’ coloured or should carry the value ‘0’. Hence we must use THRESH_BINARY_INV.
#read BW image
thresh,BW_im = cv2.threshold(gray_im,150,255,cv2.THRESH_BINARY_INV)
#visualizing
cv2.namedWindow("BW Im", cv2.WINDOW_NORMAL);
cv2.imshow("BW Im",BW_im)
cv2.waitKey(0) & 0xFF
cv2.destroyAllWindows()
Now let’s introduce our kernel, considering how much dilation we needed to do. We can always trial and error to find out the optimal kernel.
#defining kernel of 3-by-3 matrix of ones
kernel = np.ones((3,3),np.uint8)
#defining kernel of 5-by-5 matrix of ones
kernel2 = np.ones((5,5),np.uint8)
Now we use this kernel and apply the dilation to our black-and-white image using cv2.dilate.
cv2.dilate(src, kernel[, dst[, anchor[, iterations[, borderType[, borderValue]]]]])
- src - input image/array
- kernel - defined kernel
- anchor - matrix position to compare the value with the image. (In the above example we too the anchor as [1][1].
- iterations - How many times the procedure should repeat
#morphological dialation
#defining kernel
kernel = np.ones((3,3),np.uint8)
#getting dialated image
dilated_im=cv2.dilate(BW_im,kernel,anchor=(0,0),iterations=2)
#visualize
cv2.namedWindow("Dilated Image", cv2.WINDOW_NORMAL);
cv2.imshow("Dilated Image",dilated_im)
cv2.waitKey(0) & 0xFF
cv2.destroyAllWindows()
Now we can play with these values until we find the best values for kernel, anchor and iterations. And get the best possible answer to our desire.
Complete Code
import cv2
import numpy as np
import matplotlib.pyplot as plt
#read image
im=cv2.imread("open_cv.png") #your image name
#read grey scale image
gray_im=cv2.cvtColor(im,cv2.COLOR_BGR2GRAY)
#read BW image
thresh,BW_im = cv2.threshold(gray_im,150,255,cv2.THRESH_BINARY_INV)
#morphological dialation
#defining kernel
kernel = np.ones((3,3),np.uint8)
#getting dialated image
dilated_im=cv2.dilate(BW_im,kernel,anchor=(0, 0),iterations=5)
#visualize them
cv2.namedWindow("Dilated Image", cv2.WINDOW_NORMAL);
cv2.imshow("Dilated Image",dilated_im)
cv2.waitKey(0) & 0xFF
cv2.destroyAllWindows()
Morphological Erosion
Erosion is basically the inverse of dilation. It deletes the values of the pixels of the image we provide.
Same as above, Erosion takes mainly two inputs.
1. The image that we need to apply the erosion in Black and White.
2. Kernel / Structural element.
Same to the process of dilation, we must define a kernel. Let’s take the same example from above.
Example 2.1: 3x3 unit matrix. Let our anchor be (1,1).
Process of erosion
This kernel runs over every pixel of the image. If all the pixels that are overlapping with the kernel happen to be ‘1’s, no change occurs. But, if any of the overlapping pixels happen to be ‘0’ the pixel that is overlapping with the (1,1) element of the kernel is set to ‘0’.
Example 2.2: Applying erosion to a bigger image.
Erosion with OpenCV
First, read the images and visualize them. Notice that, here we want to convert the ‘1’ values to ‘0’ values. Hence our region of interest should be ‘white’ or ‘1’. There’s no need to use THRESH_BINARY_INV as previously. Just THRESH_BINARY is good.
import cv2
import numpy as np
import matplotlib.pyplot as plt
#read image
im=cv2.imread("generative_ai.png")
#read grey scale image
gray_im=cv2.cvtColor(im,cv2.COLOR_BGR2GRAY)
#read BW image
thresh,BW_im = cv2.threshold(gray_im,150,255,cv2.THRESH_BINARY)
#visualize them
cv2.namedWindow("Original Image", cv2.WINDOW_NORMAL);
cv2.imshow("Original Image",im)
cv2.namedWindow("Gray scale image", cv2.WINDOW_NORMAL);
cv2.imshow("Gray scale image",gray_im)
cv2.namedWindow("BW Im", cv2.WINDOW_NORMAL);
cv2.imshow("BW Im",BW_im)
cv2.waitKey(0) & 0xFF
cv2.destroyAllWindows()
Then we define the kernel and apply cv2.erode.
cv2.erode(src, kernel[, dst[, anchor[, iterations[, borderType[, borderValue]]]]])
#morphological erosion
#defining kernel
kernel = np.ones((3,3),np.uint8)
#getting eroded image
erod_im=cv2.dilate(BW_im,kernel,anchor=(0, 0),iterations=2)
#visualize resulted image
cv2.namedWindow("Eroded Image", cv2.WINDOW_NORMAL);
cv2.imshow("Eroded Image",erod_im)
cv2.waitKey(0) & 0xFF
cv2.destroyAllWindows()
We can see already the unnecessary pixel values have been removed. Let us change the kernel and iteration values and see better outputs.
Complete Code
import cv2
import numpy as np
import matplotlib.pyplot as plt
#read image
im=cv2.imread("generative_ai.png") #your image name
#read grey scale image
gray_im=cv2.cvtColor(im,cv2.COLOR_BGR2GRAY)
#read BW image
thresh,BW_im = cv2.threshold(gray_im,150,255,cv2.THRESH_BINARY)
#morphological erosion
#defining kernel
kernel = np.ones((3,3),np.uint8)
#getting eroded image
erod_im=cv2.dilate(BW_im,kernel,anchor=(0, 0),iterations=3)
#visualize resulted image
cv2.namedWindow("Eroded Image", cv2.WINDOW_NORMAL);
cv2.imshow("Eroded Image",erod_im)
cv2.waitKey(0) & 0xFF
cv2.destroyAllWindows()
Thanks For Reading, Follow For More.
Happy learning!
Connect with me on Linkedin