Change shapes using a small neighbourhood
Morphological operations examine a pixel and its nearby pixels. They can remove noise, fill gaps, separate touching objects, and repair broken shapes.
Usually represent the foreground object we care about.
Usually represent the background.
| Operation | Simple effect | Useful for |
|---|---|---|
| Erosion | Shrinks white areas | Removing tiny white noise and separating weak connections |
| Dilation | Grows white areas | Filling small breaks and joining nearby parts |
| Opening | Erosion, then dilation | Removing small white noise while restoring the main object |
| Closing | Dilation, then erosion | Filling small black holes and gaps while restoring the main object |
Opening removes outside noise. Closing closes inside holes.
Which operation makes white objects thinner?
Create the morphology project
Use a high-contrast image containing white objects on a black background. Small white dots and small black holes make the effects easy to see.
morphology-project/
├── main.py
└── noisy_binary.pngpip install opencv-python numpyimport cv2
import numpy as np| Line | Meaning |
|---|---|
import cv2 | Load OpenCV for reading images and applying morphology. |
import numpy as np | Load NumPy and use the short name np. We need it to create the kernel. |
Use a binary-looking PNG. The shapes should be white and the background black. PNG avoids the extra compression marks that JPEG can add.
Convert the input into pure black and white
Morphology can work on grayscale images, but its effect is clearest on a binary image containing only 0 and 255.
image = cv2.imread("noisy_binary.png", cv2.IMREAD_GRAYSCALE)| Part | Meaning |
|---|---|
cv2.imread(...) | Read an image file. |
"noisy_binary.png" | The file to read from the project folder. |
cv2.IMREAD_GRAYSCALE | Read one brightness value per pixel instead of three colour values. |
image = | Store the result in the variable image. |
if image is None:
raise FileNotFoundError("Could not find noisy_binary.png")_, binary = cv2.threshold(image, 127, 255, cv2.THRESH_BINARY)| Argument or output | Meaning |
|---|---|
image | The grayscale input. |
127 | The dividing value. Pixels above 127 become white. |
255 | The value used for white pixels. |
THRESH_BINARY | Use normal binary thresholding. |
_ | The returned threshold value is not needed, so we intentionally ignore it. |
binary | The new image containing only black and white pixels. |
What values does our binary image contain?
Build a 5 × 5 kernel
The kernel is a small matrix that slides over the image. It decides which nearby pixels take part in each operation.
kernel = np.ones((5, 5), np.uint8)| Part | Meaning |
|---|---|
kernel = | Create a variable named kernel. |
np.ones(...) | Create an array in which every value is 1. |
(5, 5) | Give the array 5 rows and 5 columns. |
np.uint8 | Store each value as an unsigned 8-bit integer, a normal OpenCV image type. |
1 1 1 1 1
1 1 1 1 1
1 1 1 1 1
1 1 1 1 1
1 1 1 1 1np.ones((5, 5), np.uint8).A gentle effect. Good for very small noise.
A stronger effect. Useful for a clear classroom demonstration.
A much stronger effect that may damage small objects.
A larger kernel changes a thicker area around object boundaries. Bigger is not always better.
What does the kernel define?
Erode the foreground
Erosion keeps a white pixel only when the kernel fits sufficiently inside the white region. Boundary pixels disappear, so white shapes become smaller.
eroded = cv2.erode(binary, kernel, iterations=1)| Part | Meaning |
|---|---|
cv2.erode(...) | Apply erosion. |
binary | The source image to process. |
kernel | The 5 × 5 neighbourhood shape. |
iterations=1 | Apply erosion once. |
eroded = | Store the result without changing the original binary image. |
Become thinner and smaller.
May completely disappear.
May break and separate connected objects.
Erosion gently cuts pixels away from the outer boundary of every white region.
What usually happens if iterations becomes 2?
Dilate the foreground
Dilation adds white pixels around white regions. Shapes become thicker, holes become smaller, and nearby white parts may join.
dilated = cv2.dilate(binary, kernel, iterations=1)| Part | Meaning |
|---|---|
cv2.dilate(...) | Apply dilation. |
binary | The source binary image. |
kernel | The neighbourhood used to grow regions. |
iterations=1 | Apply the growth one time. |
dilated = | Store the grown result. |
Become thicker and larger.
Become smaller and may disappear.
May touch and join together.
Erosion removes white boundary pixels. Dilation adds white boundary pixels. Neither one understands objects; both follow the kernel.
Which operation can join a small break in a white line?
Open the image
Opening performs erosion first and dilation second. Erosion removes small white dots; dilation then restores the main white shapes closer to their original size.
opened = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel)| Part | Meaning |
|---|---|
cv2.morphologyEx(...) | Run a combined or advanced morphological operation. |
binary | The input image. |
cv2.MORPH_OPEN | Select opening: erosion followed by dilation. |
kernel | The 5 × 5 neighbourhood. |
opened = | Store the cleaned result. |
Small white dots disappear; objects shrink
Main objects grow back
Cleaner outside background
If erosion completely removes a tiny white dot, dilation has nothing at that location to grow back.
Which order creates opening?
Close the image
Closing performs dilation first and erosion second. Dilation fills small black gaps; erosion then restores the white object closer to its original size.
closed = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel)| Part | Meaning |
|---|---|
cv2.morphologyEx(...) | Run a combined morphological operation. |
binary | The input image. |
cv2.MORPH_CLOSE | Select closing: dilation followed by erosion. |
kernel | The neighbourhood used by both parts. |
closed = | Store the repaired result. |
Fills tiny holes; objects grow
Objects shrink back
Cleaner inside object
You see unwanted small white dots in the black background.
You see unwanted small black holes or cracks inside white objects.
Which operation is usually best for tiny black holes inside a white shape?
Open six result windows
Display the input beside every operation. Look at object borders, white dots, black holes, and small gaps.
cv2.imshow("Original Grayscale", image)
cv2.imshow("Binary", binary)
cv2.imshow("Erosion", eroded)
cv2.imshow("Dilation", dilated)
cv2.imshow("Opening", opened)
cv2.imshow("Closing", closed)Each cv2.imshow(title, image) call creates a window. The text is only the window title. The second argument is the actual image array shown inside it.
cv2.waitKey(0)
cv2.destroyAllWindows()| Line | Meaning |
|---|---|
cv2.waitKey(0) | Wait for any key forever. Without it, the windows may close immediately. |
cv2.destroyAllWindows() | Close every OpenCV window cleanly. |
cv2.imwrite("opened.png", opened)
cv2.imwrite("closed.png", closed)imwrite() stores the arrays as image files in the project folder.
Complete morphology program
Copy the program into main.py. Every operation uses the same source image and kernel, making the comparison fair.
import cv2
import numpy as np
# Read the input as a one-channel grayscale image.
image = cv2.imread("noisy_binary.png", cv2.IMREAD_GRAYSCALE)
# Stop with a clear message if the file was not read.
if image is None:
raise FileNotFoundError("Could not find noisy_binary.png")
# Convert every pixel into either black (0) or white (255).
_, binary = cv2.threshold(image, 127, 255, cv2.THRESH_BINARY)
# Create a 5-by-5 neighbourhood filled with ones.
kernel = np.ones((5, 5), np.uint8)
# Shrink white regions once.
eroded = cv2.erode(binary, kernel, iterations=1)
# Grow white regions once.
dilated = cv2.dilate(binary, kernel, iterations=1)
# Remove small white noise: erosion followed by dilation.
opened = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel)
# Fill small black gaps: dilation followed by erosion.
closed = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel)
# Show the input and every result.
cv2.imshow("Original Grayscale", image)
cv2.imshow("Binary", binary)
cv2.imshow("Erosion", eroded)
cv2.imshow("Dilation", dilated)
cv2.imshow("Opening", opened)
cv2.imshow("Closing", closed)
# Keep the windows open until a key is pressed.
cv2.waitKey(0)
# Close all OpenCV windows.
cv2.destroyAllWindows()Practical experiments
- Start with a
(3, 3)kernel. - Change it to
(5, 5)and compare the boundaries. - Try
iterations=2for erosion and dilation. - Add small white dots to the background and test opening.
- Add small black holes inside a white object and test closing.
- Explain why opening and closing do not give the same output.
Common mistakes
The foreground is black, not white
These explanations assume white foreground. Use cv2.THRESH_BINARY_INV to invert a dark object on a light background, or call cv2.bitwise_not(binary).
The object disappears after erosion
Your kernel or iteration count is too large for the object. Try a 3 × 3 kernel and one iteration.
Opening does not remove the noise
The noise may be larger than the kernel. Increase the kernel carefully, because a large kernel can also remove useful small objects.
Closing joins separate objects
The objects are close enough for dilation to connect them. Reduce the kernel size or use fewer iterations.
Completion checklist
- I know that these explanations assume white foreground.
- I can explain the role of the kernel.
- I know that erosion shrinks white regions.
- I know that dilation grows white regions.
- I remember opening as erosion followed by dilation.
- I remember closing as dilation followed by erosion.
- I can select opening for white noise and closing for black holes.
- I can change kernel size and iterations safely.
Codelab complete! 🎉
You can now clean and repair binary images using erosion, dilation, opening, and closing.