Title: OpenCV Morphological Operations - PyImageSearch

URL Source: https://pyimagesearch.com/2021/04/28/opencv-morphological-operations/

Published Time: 2021-04-28T14:00:00+00:00

Markdown Content:
In this tutorial, you will learn about applying morphological operations with OpenCV.

![Image 1](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20700%20418'%3E%3C/svg%3E)

The morphological operations we’ll be covering include:

*   Erosion
*   Dilation
*   Opening
*   Closing
*   Morphological gradient
*   Black hat
*   Top hat (also called “White hat”)

These image processing operations are applied to grayscale or binary images and are used for preprocessing for OCR algorithms, detecting barcodes, detecting license plates, and more.

And sometimes a clever use of morphological operations can allow you to avoid more complicated (and computationally expensive) machine learning and deep learning algorithms.

As a serious computer vision practitioner, you _need_ to understand morphological operations.

**To learn how to apply morphological operations with OpenCV,** **_just keep reading._**

![Image 2](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20128%20128'%3E%3C/svg%3E)

#### Looking for the source code to this post?

[Jump Right To The Downloads Section](#download-the-code)

Morphological operations are simple transformations applied to binary or grayscale images. More specifically, we apply morphological operations to _shapes_ and _structures_ inside of images.

We can use morphological operations to _increase_ the size of objects in images as well as _decrease_ them. We can also utilize morphological operations to _close_ gaps between objects as well as _open_ them.

Morphological operations “probe” an image with a **structuring element.** This structuring element defines the neighborhood to be examined around each pixel. And based on the given operation and the size of the structuring element we are able to adjust our output image.

This explanation of a structuring element may sound vague — that’s because it is. There are many different morphological transformations that perform “opposite” operations from one another — just as addition is the “opposite” of subtraction, we can think of the erosion morphological operation as the “opposite” of dilation.

If this sounds confusing, don’t worry — we’ll be reviewing many examples of each of these morphological transformations, and by the time you are done reading through this tutorial, you’ll have a crystal clear view of morphological operations.

### **Why learn about morphological operations?**

![Image 3](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20700%20416'%3E%3C/svg%3E)

**Figure 1:** Morphological operations are a set of basic image processing techniques that you need to understand to be a successful computer vision practitioner.

**Morphological operations are one of my favorite topics to cover in image processing.**

Why is that?

Because these transformations are so powerful.

Oftentimes I see computer vision researchers and developers trying to solve a problem and immediately dive into advanced computer vision, machine learning, and deep learning techniques. It seems that once you learn to wield a hammer, every problem looks like a nail.

However, there are times where a more “elegant” solution can be found using less advanced techniques. Sure, these techniques may not be floating around on a cloud of buzzwords for the latest state-of-the-art algorithms, but they can get the job done.

For instance, I once wrote an article on the PyImageSearch blog on [detecting barcodes in images](https://pyimagesearch.com/2014/11/24/detecting-barcodes-images-python-opencv/). I didn’t use any fancy techniques. I didn’t use any machine learning. In fact, I was able to detect barcodes in images using nothing more than the introductory topics we’ve discussed so far in this series.

Crazy, isn’t it?

But seriously, pay attention to these transformations — there will be times in your computer vision career when you’ll be ready to swing your hammer down on a problem, only to realize that a more elegant, simple solution may already exist. And more than likely, you may find that elegant solution in morphological operations.

Let’s go ahead and get started by discussing the component that makes morphological operations possible: the _structuring element._

### **The concept of “structuring elements”**

Remember back in our tutorial on [image kernels and convolutions](https://pyimagesearch.com/2016/07/25/convolutions-with-opencv-and-python/)?

Well, you can (conceptually) think of a _structuring element_ as a type of _kernel_ or _mask_. However, instead of applying a convolution, we are only going to perform simple tests on the pixels.

And just like in image kernels, the structuring element slides from left-to-right and top-to-bottom for each pixel in the image. Also just like kernels, structuring elements can be of arbitrary neighborhood sizes.

For example, let’s take a look at the 4-neighborhood and 8-neighborhood of the central pixel red below:

![Image 4](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20700%20405'%3E%3C/svg%3E)

**Figure 2:** _Left:_ A 4-pixel neighborhood surrounding the center red pixel. _Right:_ An 8-pixel neighborhood surrounding the center pixel.

Here, we can see that the central pixel (i.e., the red pixel) is located at the center of the neighborhood:

*   The 4-neighborhood _(left)_ defines the region surrounding the central pixel as the pixels to the north, south, east, and west.
*   The 8-neighborhood _(right)_ extends this region to include the corner pixels as well

This is just an example of two simple structuring elements. But we could also make them arbitrary rectangle or circular structures as well — it all depends on your particular application.

In OpenCV, we can either use the `cv2.getStructuringElement` function or NumPy itself to define our structuring element. Personally, I prefer to use the `cv2.getStructuringElement` function since it gives you more control over the returned element, but again, that is a personal choice.

If the concept of structuring elements is not entirely clear, that’s okay. We’ll be reviewing many examples of them inside this lesson. For the time being, understand that a structuring element behaves similar to a kernel or a mask — but instead of convolving the input image with our structuring element, we’re instead only going to be applying simple pixel tests.

Now that we have a basic understanding of structuring elements, let’s configure our development environment, review the project directory structure, and then write some code.

### **Configuring your development environment**

To follow this guide, you need to have the OpenCV library installed on your system.

Luckily, OpenCV is pip-installable:

$ pip install opencv-contrib-python

**If you need help configuring your development environment for OpenCV, I _highly recommend_ that you read my** [**_pip install OpenCV_ guide**](https://pyimagesearch.com/2018/09/19/pip-install-opencv/) — it will have you up and running in a matter of minutes.

### **Having problems configuring your development environment?**

[![Image 5](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20500%20334'%3E%3C/svg%3E)](https://pyimagesearch.com/pyimagesearch-university/)

**Figure 3:** Having trouble configuring your dev environment? Want access to pre-configured Jupyter Notebooks running on Google Colab? Be sure to join [PyImageSearch University](https://pyimagesearch.com/pyimagesearch-university/) — you’ll be up and running with this tutorial in a matter of minutes.

All that said, are you:

*   Short on time?
*   Learning on your employer’s administratively locked system?
*   Wanting to skip the hassle of fighting with the command line, package managers, and virtual environments?
*   **Ready to run the code** **_right now_** **on your Windows, macOS, or Linux system?**

Then join [PyImageSearch University](https://pyimagesearch.com/pyimagesearch-university/) today!

**Gain access to Jupyter Notebooks for this tutorial and other PyImageSearch guides that are** **_pre-configured_** **to run on Google Colab’s ecosystem right in your web browser!** No installation required.

And best of all, these Jupyter Notebooks will run on Windows, macOS, and Linux!

### **Project structure**

Before we can start implementing morphological operations with OpenCV, let’s first review our project directory structure.

Start by accessing the **_“Downloads”_** section of this tutorial to retrieve the source code and example images:

$ tree . --dirsfirst
.
├── car.png
├── morphological\_hats.py
├── morphological\_ops.py
├── pyimagesearch\_logo.png
└── pyimagesearch\_logo\_noise.png

0 directories, 5 files

We have two Python scripts to to review today:

1.  `morphological_ops.py`: Applies OpenCV’s morphological operations, including erosion, dilation, opening, closing, and morphological gradient.
2.  `morphological_hats.py`: Applies a black hat and top hat/white hat operation with OpenCV.

The three .png images included in our project structure will be utilized by these two scripts to demonstrate various morphological operations.

### **Erosion**

Just like water rushing along a river bank erodes the soil, an **erosion** in an image “erodes” the foreground object and makes it smaller. Simply put, pixels near the boundary of an object in an image will be discarded, “eroding” it away.

Erosion works by defining a structuring element and then sliding this structuring element from left-to-right and top-to-bottom across the input image.

A foreground pixel in the input image will be kept **only if _all_ pixels** inside the structuring element are _\> 0_. Otherwise, the pixels are set to _0_ (i.e., background).

Erosion is useful for removing small blobs in an image or disconnecting two connected objects.

We can perform erosion by using the `cv2.erode` function. Let’s open a new file, name it `morphological.py_ops.py`, and start coding:

\# import the necessary packages
import argparse
import cv2

# construct the argument parser and parse the arguments
ap = argparse.ArgumentParser()
ap.add\_argument("-i", "--image", required=True,
	help="path to input image")
args = vars(ap.parse\_args())

**Lines 2 and 3** import `argparse` (for command line arguments) and `cv2` (our OpenCV bindings).

We only have a single command line argument to parse, our input `--image` that we’ll be applying erosions to.

In most examples in this lesson we’ll be applying morphological operations to the PyImageSearch logo, which we can see below:

![Image 6](https://b2633864.smushcdn.com/2633864/wp-content/uploads/2021/04/opencv_morphological_ops_pyimagesearch_logo.png?lossy=2&strip=1&webp=1)

**Figure 4:** The example PyImageSearch logo that we’ll be applying morphological operations to in this lesson.

As I mentioned earlier in this lesson, we typically (but not always) apply morphological operations to _binary_ images. As we’ll see later in this lesson, there are exceptions to that, especially when using the _black hat_ and _white hat_ operators, but for the time being, we are going to assume we are working with a binary image, where the background pixels are _black_ and the foreground pixels are _white_.

Let’s load our input `--image` from disk and then apply a series of erosions:

\# load the image, convert it to grayscale, and display it to our
# screen
image = cv2.imread(args\["image"\])
gray = cv2.cvtColor(image, cv2.COLOR\_BGR2GRAY)
cv2.imshow("Original", image)

# apply a series of erosions
for i in range(0, 3):
	eroded = cv2.erode(gray.copy(), None, iterations=i + 1)
	cv2.imshow("Eroded {} times".format(i + 1), eroded)
	cv2.waitKey(0)

**Line 13** loads our input `image` from disk while **Line 14** converts it to grayscale. Since our image is already pre-segmented, we are now working with a binary image.

Given our logo image, we apply a series of erosions on Lines **18-21**. The `for` loop controls the number of times, or _iterations_, we are going to apply the erosion. As the number of erosions increases, the foreground logo will start to “erode” and disappear.

We perform the actual erosion on **Line 19** by making a call to the `cv2.erode` function. This function takes two required arguments and a third optional one.

The first argument is the `image` that we want to erode — in this case, it’s our binary image (i.e., the PyImageSearch logo).

The second argument to `cv2.erode` is the structuring element. If this value is `None`, then a _3×3_ structuring element, identical to the 8-neighborhood structuring element we saw above will be used. Of course, you could supply your own custom structuring element here instead of `None` as well.

The last argument is the number of `iterations` the erosion is going to be performed. As the number of iterations increases, we’ll see more and more of the PyImageSearch logo eaten away.

Finally, **Line 20 and 21** show us our eroded image.

When you execute this script you’ll see the following output from our erosion operations:

![Image 7](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20408%20500'%3E%3C/svg%3E)

**Figure 5:** Applying erosion to our input image. As the number of iterations increases, more and more of the logo is eroded away.

On the very _top_ we have our original image. And then underneath the image, we have the logo being eroded a total of 1, 2, and 3 times, respectively. **Notice as the number of erosion iterations increases, more and more of the logo is eaten away.**

Again, erosions are most useful for removing small blobs from an image or disconnecting two connected components. With this in mind, take a look at the letter “p” in the PyImageSearch logo. Notice how the circular region of the “p” has disconnected from the stem after 2 erosions — this is an example of disconnecting two connected components of an image.

### **Dilation**

The opposite of an erosion is a **dilation**. Just like an erosion will eat away at the foreground pixels, a dilation will _grow_ the foreground pixels.

Dilations _increase_ the size of foreground objects and are especially useful for joining broken parts of an image together.

Dilations, just as an erosion, also utilize structuring elements — a center pixel _p_ of the structuring element is set to _white_ if **_ANY_** pixel in the structuring element is _\> 0_.

We apply dilations using the `cv2.dilate` function:

\# close all windows to cleanup the screen
cv2.destroyAllWindows()
cv2.imshow("Original", image)

# apply a series of dilations
for i in range(0, 3):
	dilated = cv2.dilate(gray.copy(), None, iterations=i + 1)
	cv2.imshow("Dilated {} times".format(i + 1), dilated)
	cv2.waitKey(0)

**Lines 24 and 25** simply close all open windows and display our original image to give us a fresh start.

**Line 28** then starts looping over the number of iterations, just as we did with the cv2.erode function.

The actual dilation is performed on **Line 29** by making a call to the `cv2.dilate` function, where the actual function signature is identical to that of `cv2.erode`.

The first argument is the `image` we want to dilate; the second is our structuring element, which when set to `None` is a _3×3_ 8-neighborhood structuring element; and the final argument is the number of dilation `iterations` we are going to apply.

The output of our dilation can be seen below:

![Image 8](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20397%20500'%3E%3C/svg%3E)

**Figure 6:** Applying a dilation to our input image. Notice how the foreground region has _grown_.

Again, at the very _top_ we have our original input image. And below the input image we have our image dilated 1, 2, and 3 times, respectively.

Unlike an erosion where the foreground region is slowly eaten away at, a dilation actually _grows_ our foreground region.

Dilations are especially useful when joining broken parts of an object — for example, take a look at the _bottom_ image where we have applied a dilation with 3 iterations. By this point, the gaps between **_all_** letters in the logo have been joined.

### **Opening**

**An opening is an** **_erosion_** **followed by a** **_dilation_****.**

Performing an opening operation allows us to remove small blobs from an image: first an erosion is applied to remove the small blobs, then a dilation is applied to regrow the size of the original object.

Let’s look at some example code to apply an opening to an image:

\# close all windows to cleanup the screen, then initialize a list of
# of kernels sizes that will be applied to the image
cv2.destroyAllWindows()
cv2.imshow("Original", image)
kernelSizes = \[(3, 3), (5, 5), (7, 7)\]

# loop over the kernels sizes
for kernelSize in kernelSizes:
	# construct a rectangular kernel from the current size and then
	# apply an "opening" operation
	kernel = cv2.getStructuringElement(cv2.MORPH\_RECT, kernelSize)
	opening = cv2.morphologyEx(gray, cv2.MORPH\_OPEN, kernel)
	cv2.imshow("Opening: ({}, {})".format(
		kernelSize\[0\], kernelSize\[1\]), opening)
	cv2.waitKey(0)

**Lines 35 and 36** perform cleanup by closing all open windows and re-displaying our original image.

Take a look at the new variable we are defining on **Line 37**, `kernelSizes`. This variable defines the width and height, respectively, of the structuring element we are going to apply.

We loop over each of these `kernelSizes` on **Line 40** and then make a call to `cv2.getStructuringElement` on **Line 43** to build our structuring element.

The `cv2.getStructuringElement` function requires two arguments: the first is the type of structuring element we want, and the second is the size of the structuring element (which we grab from the for loop on **Line 40**).

We pass in a value of `cv2.MORPH_RECT` to indicate that we want a rectangular structuring element. But you could also pass in a value of `cv2.MORPH_CROSS` to get a cross shape structuring element (a cross is like a 4-neighborhood structuring element, but can be of any size), or `cv2.MORPH_ELLIPSE` to get a circular structuring element.

Exactly which structuring element you use is dependent upon your application — and I’ll leave it as an exercise to the reader to play with each of these structuring elements.

The actual opening operation is performed on **Line 42** by making a call to the `cv2.morphologyEx` function. This function is abstract in a sense — it allows us to pass in whichever morphological operation we want, followed by our kernel/structuring element.

The first required argument of `cv2.morphologyEx` is the image we want to apply the morphological operation to. The second argument is the actual _type_ of morphological operation — in this case, it’s an _opening_ operation. The last required argument is the kernel/structuring element that we are using.

Finally, **Lines 45-47** display the output of applying our opening.

As I mentioned above, an opening operation allows us to remove small blobs in an image. I went ahead and added some blobs to the PyImageSearch logo (`pyimagesearch_logo_noise.png` in our project directory structure):

![Image 9](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20497%2087'%3E%3C/svg%3E)

**Figure 7:** The PyImageSearch logo with random blobs/noise added to it.

When you apply our opening morphological operations to this noisy image you’ll receive the following output:

![Image 10](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20493%20500'%3E%3C/svg%3E)

**Figure 8:** The results of applying an opening operation to our image — notice how the small, random blobs have been removed.

Notice how by the time we are using a kernel of size _5×5_, the small, random blobs are nearly completely gone. And by the time it reaches a kernel of size _7×7_, our opening operation has not only removed all the random blobs, but also “opened” holes in the letter “p” and the letter “a”.

### **Closing**

**The exact opposite to an opening would be a** **_closing_****. A closing is a** **_dilation_** **followed by an** **_erosion_****.**

As the name suggests, a closing is used to close holes inside of objects or for connecting components together.

The below code block contains the code to perform a closing:

\# close all windows to cleanup the screen
cv2.destroyAllWindows()
cv2.imshow("Original", image)

# loop over the kernels sizes again
for kernelSize in kernelSizes:
	# construct a rectangular kernel form the current size, but this
	# time apply a "closing" operation
	kernel = cv2.getStructuringElement(cv2.MORPH\_RECT, kernelSize)
	closing = cv2.morphologyEx(gray, cv2.MORPH\_CLOSE, kernel)
	cv2.imshow("Closing: ({}, {})".format(
		kernelSize\[0\], kernelSize\[1\]), closing)
	cv2.waitKey(0)

Performing the closing operation is again accomplished by making a call to `cv2.morphologyEx`, but this time we are going to indicate that our morphological operation is a closing by specifying the `cv2.MORPH_CLOSE` flag.

We’ll go back to using our original image (without the random blobs). The output for applying a closing operation with increasing structuring element sizes can be seen below:

![Image 11](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20403%20500'%3E%3C/svg%3E)

**Figure 9:** Applying a morphological closing operation to our input image.

Notice how the closing operation is starting to bridge the gap between letters in the logo. Furthermore, letters such as “e”, “s”, and “a” are practically filled in.

### **Morphological gradient**

**A morphological gradient is the difference between a _dilation_ and _erosion_.** It is useful for determining the outline of a particular object of an image:

\# close all windows to cleanup the screen
cv2.destroyAllWindows()
cv2.imshow("Original", image)

# loop over the kernels a final time
for kernelSize in kernelSizes:
	# construct a rectangular kernel and apply a "morphological
	# gradient" operation to the image
	kernel = cv2.getStructuringElement(cv2.MORPH\_RECT, kernelSize)
	gradient = cv2.morphologyEx(gray, cv2.MORPH\_GRADIENT, kernel)
	cv2.imshow("Gradient: ({}, {})".format(
		kernelSize\[0\], kernelSize\[1\]), gradient)
	cv2.waitKey(0)

The most important line to pay attention to is **Line 72**, where we make a call to `cv2.morphologyEx` — but this time we supply the `cv2.MORPH_GRADIENT` flag to indicate that we want to apply the morphological gradient operation to reveal the outline of our logo:

![Image 12](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20404%20500'%3E%3C/svg%3E)

**Figure 10:** A morphological gradient can be used to find the outline of an object in an image.

Notice how the outline of the PyImageSearch logo has been clearly revealed after applying the morphological gradient operation.

### **Top hat/white hat and black hat**

**A** **_top hat_** **(also known as a** **_white hat_****) morphological operation is the difference between the original (grayscale/single channel)** **_input image_** **and the** **_opening_****.**

A top hat operation is used to reveal **bright regions** of an image on **dark backgrounds.**

Up until this point we have only applied morphological operations to binary images. But we can also apply morphological operations to grayscale images as well. In fact, both the top hat/white hat and the black hat operators are more suited for grayscale images rather than binary ones.

To demonstrate applying morphological operations, let’s take a look at the following image where our goal is to detect the license plate region of the car:

![Image 13](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20600%20450'%3E%3C/svg%3E)

**Figure 11:** Our goal is to apply morphological operations to find the license plate region of the car.

So how are we going to go about doing this?

Well, taking a look at the example image above, we see that the license plate is _bright_ since it’s a white region against a _dark background_ of the car itself. An excellent starting point to finding the region of a license plate would be to use the top hat operator.

To test out the top hat operator, create a new file, name it `morphological_hats.py`, and insert the following code:

\# import the necessary packages
import argparse
import cv2

# construct the argument parser and parse the arguments
ap = argparse.ArgumentParser()
ap.add\_argument("-i", "--image", required=True,
	help="path to input image")
args = vars(ap.parse\_args())

**Lines 2 and 3** import our required Python packages while **Lines 6-9** parse our command line arguments. We only need a single argument, `--image`, the path to our input image (which we presume to be `car.png` in our project structure).

Let’s load our input `--image` from disk:

\# load the image and convert it to grayscale
image = cv2.imread(args\["image"\])
gray = cv2.cvtColor(image, cv2.COLOR\_BGR2GRAY)

# construct a rectangular kernel (13x5) and apply a blackhat
# operation which enables us to find dark regions on a light
# background
rectKernel = cv2.getStructuringElement(cv2.MORPH\_RECT, (13, 5))
blackhat = cv2.morphologyEx(gray, cv2.MORPH\_BLACKHAT, rectKernel)

**Lines 12 and 13** laid our input `image` from disk and convert it to grayscale, thereby preparing it for our black hat and white hat operations.

**Line 18** then defines a rectangular structuring element with a width of 13 pixels and a height of 5 pixels. As I mentioned earlier in this lesson, structuring elements can be of arbitrary size. And in this case, we are applying a rectangular element that is almost 3x wider than it is tall.

And why is this?

**Because a license plate is roughly 3x wider than it is tall!**

By having some basic _a priori_ knowledge of the objects you want to detect in images, we can construct structuring elements to better aid us in finding them.

**Line 19** applies the black hat operator.

In a similar fashion we can also apply a top hat/white hat operation:

\# similarly, a tophat (also called a "whitehat") operation will
# enable us to find light regions on a dark background
tophat = cv2.morphologyEx(gray, cv2.MORPH\_TOPHAT, rectKernel)

# show the output images
cv2.imshow("Original", image)
cv2.imshow("Blackhat", blackhat)
cv2.imshow("Tophat", tophat)
cv2.waitKey(0)

To specify a top hat/white hat operator instead of a blackhat, we simply change the type of operator to `cv2.MORPH_TOPHAT`.

Below you can see the output of applying the top hat operators:

![Image 14](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20700%20323'%3E%3C/svg%3E)

**Figure 12:** Applying a top hat operation reveals light regions on a dark background.

Notice how the _right_ (i.e., the top hat/white hat) regions that are _light_ against a _dark background_ are clearly displayed — in this case, we can clearly see that the license plate region of the car has been revealed.

But also note that the license plate characters themselves have not been included. This is because the license plate characters are _dark_ against a _light background._

To help remedy that, we can apply a black hat operator:

![Image 15](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20700%20321'%3E%3C/svg%3E)

**Figure 13:** Applying a black hat operation reveals dark regions on a light background.

To reveal our license plate characters you would first segment out the license plate itself via a top hat operator and then apply a black hat operator (or thresholding) to extract the individual license plate characters (perhaps using methods like contour detection).

### **Running our morphological operations demos**

To run our morphological operation demos, be sure to access the **_“Downloads”_** section of this tutorial to retrieve the source code and example images.

You can execute the `morphological_ops.py` script using this command:

$ python morphological\_ops.py --image pyimagesearch\_logo.png

And the `morphological_hats.py` script can be started by using this command:

$ python morphological\_hats.py --image car.png

The output of these scripts should match the images and figures I have provided above.

### What's next? We recommend [PyImageSearch University](https://pyimagesearch.com/pyimagesearch-university/?utm_source=blogPost&utm_medium=bottomBanner&utm_campaign=What%27s%20next%3F%20I%20recommend).

**Course information:**  
84 total classes • 114+ hours of on-demand code walkthrough videos • Last updated: February 2024  
★★★★★ 4.84 (128 Ratings) • 16,000+ Students Enrolled

**I strongly believe that if you had the right teacher you could _master_ computer vision and deep learning.**

Do you think learning computer vision and deep learning has to be time-consuming, overwhelming, and complicated? Or has to involve complex mathematics and equations? Or requires a degree in computer science?

That’s _not_ the case.

All you need to master computer vision and deep learning is for someone to explain things to you in _simple, intuitive_ terms. _And that’s exactly what I do_. My mission is to change education and how complex Artificial Intelligence topics are taught.

If you're serious about learning computer vision, your next stop should be PyImageSearch University, the most comprehensive computer vision, deep learning, and OpenCV course online today. Here you’ll learn how to _successfully_ and _confidently_ apply computer vision to your work, research, and projects. Join me in computer vision mastery.

**Inside PyImageSearch University you'll find:**

*   ✓ **84 courses** on essential computer vision, deep learning, and OpenCV topics
*   ✓ **84 Certificates** of Completion
*   ✓ **114+ hours** of on-demand video
*   ✓ **Brand new courses released _regularly_**, ensuring you can keep up with state-of-the-art techniques
*   ✓ **Pre-configured Jupyter Notebooks in Google Colab**
*   ✓ Run all code examples in your web browser — works on Windows, macOS, and Linux (no dev environment configuration required!)
*   ✓ Access to **centralized code repos for _all_ 536+ tutorials** on PyImageSearch
*   ✓ **Easy one-click downloads** for code, datasets, pre-trained models, etc.
*   ✓ **Access** on mobile, laptop, desktop, etc.

[Click here to join PyImageSearch University](https://pyimagesearch.com/pyimagesearch-university/?utm_source=blogPost&utm_medium=bottomBanner&utm_campaign=What%27s%20next%3F%20I%20recommend)

**Summary**
-----------

In this tutorial, we learned that morphological operations are image processing transformations applied to either grayscale or binary images. These operations require a **structuring element**, which is used to define the neighborhood of pixels the operation is applied to.

We also reviewed the most important morphological operations that you’ll use inside your own applications:

*   Erosion
*   Dilation
*   Opening
*   Closing
*   Morphological gradient
*   Top hat/white hat
*   Black hat

Morphological operations are commonly used as pre-processing steps to more powerful computer vision solutions such as OCR, Automatic Number Plate Recognition (ANPR), and barcode detection.

While these techniques are simple, they are actually extremely powerful and tend to be highly useful when pre-processing your data. _Do not overlook them._

**To download the source code to this post (and be notified when future tutorials are published here on PyImageSearch), _simply enter your email address in the form below!_**

![Image 16](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20410%20567'%3E%3C/svg%3E)

#### Download the Source Code and FREE 17-page Resource Guide

Enter your email address below to get a .zip of the code and a **FREE 17-page Resource Guide on Computer Vision, OpenCV, and Deep Learning.** Inside you'll find my hand-picked tutorials, books, courses, and libraries to help you master CV and DL!