In my last article, I wrote about some basic image processing in OpenCV. Today, we will advance a little bit and work on the morphological operations which are commonly used in image processing. Morphological operations are used to extract the region, edges, shapes, etc.
What are Morphological Operations?
Morphological operations are performed on binary images. Binary images may contain a lot of imperfections. Especially the binary image that is produced by some simple thresholding operations (if you are not familiar with thresholding, don’t worry about it now) may contain a lot of noise and distortions. Different morphological operations are available in the OpenCV library to handle those noises and imperfections.
Morphological operations generate images of the same shape as the original images. Morphological operations apply a structuring element to the input image. The structuring element can be of any shape. In all the morphological operations we will work on today, each pixel of the input image is compared with the neighboring pixels to produce the output image.
For different morphological operations, comparison happens differently. We will discuss this in detail.
Before diving into the problem, here is the picture we will use for this tutorial:
Here we are importing the necessary packages, reading the image as an array, and converting it to a binary image as we mentioned earlier, morphological operations are applied to binary images:
import cv2 import matplotlib.pyplot as plt #Reading the image to an array image = cv2.imread('practice.jpg')#converting it to a binary image gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
Here is what the ‘gray’ looks like:
We will use this grayscale image to see how the morphological operations work.
Before diving into the examples, I want to create a 3×3 kernel and a 6×3 kernel:
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3)) kernel1 = cv2.getStructuringElement(cv2.MORPH_RECT, (6, 3))
We will use these two kernels on the morphological operations below. We will create more kernels later if necessary.
Kernels can be of any shape. Please try some other different shapes of kernels such as 1×4, 4×4, 5×5 7×18, or more as well. Depending on your project, kernel shapes and sizes can make a significant difference.
Erosion does what it sounds like. it erodes an image the way water erodes the river bank. In erosion operation, it slides a structural element from left to right and from top to bottom of an input image. If all the pixels inside the structural elements are greater than 0 it keeps the original pixel values. Otherwise, the pixels are set to 0.
Erosion is used to remove the small blobs that are considered noise.
Here is the syntax for erosion:
eroded1 = cv2.erode(gray.copy(), kernel, iterations = 1) cv2.imwrite('erode1.jpg', eroded1)
The erosion function cv2.erode() uses the image, structuring element, and the number of iterations. The ‘iterations’ parameter is optional here. It automatically does one iteration if you do not provide a ‘iterations’ value.
Here is what the ‘erode1’ looks like:
Compare it to the original image. It is eroded away. Also, the other little elements that were in the original image are removed.
In any OCR(Optical Character Recognition) project, we want to recognize the letters or digits only. But there might be other smaller letters and elements in the images that can confuse your algorithm. Erosion can remove those noises.
If we try for 2 or 3 iterations, it will be more eroded:
eroded2 = cv2.erode(gray.copy(), kernel, iterations = 2) cv2.imwrite('erode2.jpg', eroded2)eroded3 = cv2.erode(gray.copy(), kernel, iterations = 3) cv2.imwrite(‘erode3.jpg’, eroded3)
These are the results from 2 and 3 iterations respectively:
As you can see, with more iterations the image gets more and more eroded. So, if you need to extract letters that are bold and have a lot of noise around, the noises can be eliminated by eroding away the image.Dilation
Dilation does exactly the opposite of what erosion does. It increases the foreground and thus helps join the broken part. It can be used after erosion to join the broken parts. In dilation, the pixel values in the structuring element are set to white or 255 if any pixel in the structuring element is greater than 0. We are using the dilation for 1 and 3 iterations here to see the difference and to understand how it works.
dilated1 = cv2.dilate(gray.copy(), kernel, iterations=1) cv2.imwrite('dilate1.jpg', dilated1)dilated3 = cv2.dilate(gray.copy(), kernel, iterations=3) cv2.imwrite(‘dilate3.jpg’, dilated3)
These are the images after dilation of 1 iteration(2nd image) and 3 (third image)iterations respectively. The original gray image is on top.
If we compare the original image on top to the second image with one iteration of dilation, there is a slight difference and after 3 iterations, the difference becomes much more significant. Depending on your project you can use as many iterations as necessary.Opening
An opening is also another way to remove the noise from the images. It does an erosion followed by dilation in one iteration for us. Here are two examples where I used kernel and kernel1 we prepared in the beginning:
opening1 = cv2.morphologyEx(gray, cv2.MORPH_OPEN, kernel) cv2.imwrite('open1.jpg', opening1)opening2 = cv2.morphologyEx(gray, cv2.MORPH_OPEN, kernel1) ) cv2.imwrite(‘open2.jpg’, opening2)
Here I am putting the original image on top, then the image from the ‘opening1’ operation, and at the bottom the image of the ‘opening2’ operation.
As you can see, in the middle picture where we used a 3×3 kernel, the small text in the bottom left corner is gone and in the bottom picture where we used kernel1 of size 6×3, a black shade got added
Closing is the opposite of opening as it sounds. In closing, dilation happens first, and then erosion.
Let’s see some of the examples:
closing1 = cv2.morphologyEx(gray.copy(), cv2.MORPH_CLOSE, kernel, iterations=1) cv2.imwrite('close1.jpg', closing1)closing3 = cv2.morphologyEx(gray.copy(), cv2.MORPH_CLOSE, (3, 3), iterations=3) cv2.imwrite(‘close3.jpg’, closing3)
As before, I am keeping the original gray image on top for comparison and then the output from ‘closing1’ and ‘closing2’.Morphological Gradient
The morphological gradient is useful to detect the outline of an object. It can be used for edge detection. Basically, it is the difference between a dilation and an erosion operation.
These are two examples using kernel and kernel1:
grad1 = cv2.morphologyEx(gray.copy(), cv2.MORPH_GRADIENT, kernel) cv2.imwrite('grad1.jpg', grad1)grad2 = cv2.morphologyEx(gray.copy(), cv2.MORPH_GRADIENT, kernel1) cv2.imwrite(‘grad3.jpg’, grad2)
These are the output images from the ‘grad1’ and ‘grad2’:
As you can see, two different shapes of kernel provided us with two different types of output.Tophat / Whitehat
A tophat operation is a difference between the original binary image and the opening. It is helpful when you need to find a bright region from a dark background.
We will use a different input image for this one and the rest of the operations:
Here is the input image:
The number plate of this car is white brighter than the car. Let’s see how we can extract that white region. As usual, We should convert it to a gray image and we will define two different kernels.
image = cv2.imread('car.jpg') gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (23, 5)) kernel1 = cv2.getStructuringElement(cv2.MORPH_RECT, (35, 8))
I tried with a few different shapes of the kernel and then used these two kernel sizes. Please feel free to try some other kernel sizes and compare the results. Here is the tophat operation:
tophat1 = cv2.morphologyEx(gray.copy(), cv2.MORPH_TOPHAT, kernel1) cv2.imwrite('tophat1.jpg', tophat1)
Here is the output image:
Look, it detects the bright region of the number plate from the car itself.Blackhat
Blackhat operation does the opposite.
blackhat1 = cv2.morphologyEx(gray.copy(), cv2.MORPH_BLACKHAT, kernel) cv2.imwrite('blackhat1.jpg', blackhat1)
This is the output from the blackhat operation:
If you notice, it focuses on the letters on the number plate.
After tophat, the number plate region was detected and after blackhat the black letters from the white number plate got highlighted.
So, if we want to detect the numbers from the number plate, we will perform a tophat operation followed by a black hat.Conclusion
This article tried to explain some well-known morphological operations with examples. In some future articles, I will use them to solve some real problems. That will be more fun.