Morphology Practical
STEP 1 · THE BIG IDEA

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.

By the end: You will implement erosion, dilation, opening, and closing with OpenCV and understand every line of the program.
White pixels

Usually represent the foreground object we care about.

Black pixels

Usually represent the background.

OperationSimple effectUseful for
ErosionShrinks white areasRemoving tiny white noise and separating weak connections
DilationGrows white areasFilling small breaks and joining nearby parts
OpeningErosion, then dilationRemoving small white noise while restoring the main object
ClosingDilation, then erosionFilling small black holes and gaps while restoring the main object
MEMORY TRICK

Opening removes outside noise. Closing closes inside holes.

Which operation makes white objects thinner?

STEP 2 · SET UP

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.

Project structure
morphology-project/
├── main.py
└── noisy_binary.png
Install packages
pip install opencv-python numpy
import cv2
import numpy as np
LineMeaning
import cv2Load OpenCV for reading images and applying morphology.
import numpy as npLoad NumPy and use the short name np. We need it to create the kernel.
Best input for this lesson

Use a binary-looking PNG. The shapes should be white and the background black. PNG avoids the extra compression marks that JPEG can add.

STEP 3 · PREPARE THE MASK

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)
PartMeaning
cv2.imread(...)Read an image file.
"noisy_binary.png"The file to read from the project folder.
cv2.IMREAD_GRAYSCALERead 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")
Stop with a clear message if OpenCV could not read the file.
_, binary = cv2.threshold(image, 127, 255, cv2.THRESH_BINARY)
Argument or outputMeaning
imageThe grayscale input.
127The dividing value. Pixels above 127 become white.
255The value used for white pixels.
THRESH_BINARYUse normal binary thresholding.
_The returned threshold value is not needed, so we intentionally ignore it.
binaryThe new image containing only black and white pixels.

What values does our binary image contain?

STEP 4 · CHOOSE THE NEIGHBOURHOOD

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)
PartMeaning
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.uint8Store 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 1
Conceptually, this is the matrix created by np.ones((5, 5), np.uint8).
3 × 3

A gentle effect. Good for very small noise.

5 × 5

A stronger effect. Useful for a clear classroom demonstration.

9 × 9

A much stronger effect that may damage small objects.

Kernel size matters

A larger kernel changes a thicker area around object boundaries. Bigger is not always better.

What does the kernel define?

STEP 5 · SHRINK WHITE AREAS

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)
PartMeaning
cv2.erode(...)Apply erosion.
binaryThe source image to process.
kernelThe 5 × 5 neighbourhood shape.
iterations=1Apply erosion once.
eroded =Store the result without changing the original binary image.
White objects

Become thinner and smaller.

Small white dots

May completely disappear.

Thin white bridges

May break and separate connected objects.

THINK OF IT AS

Erosion gently cuts pixels away from the outer boundary of every white region.

What usually happens if iterations becomes 2?

STEP 6 · GROW WHITE AREAS

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)
PartMeaning
cv2.dilate(...)Apply dilation.
binaryThe source binary image.
kernelThe neighbourhood used to grow regions.
iterations=1Apply the growth one time.
dilated =Store the grown result.
White objects

Become thicker and larger.

Small black holes

Become smaller and may disappear.

Nearby white parts

May touch and join together.

Erosion and dilation are opposites

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?

STEP 7 · REMOVE WHITE NOISE

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)
PartMeaning
cv2.morphologyEx(...)Run a combined or advanced morphological operation.
binaryThe input image.
cv2.MORPH_OPENSelect opening: erosion followed by dilation.
kernelThe 5 × 5 neighbourhood.
opened =Store the cleaned result.
Erosion

Small white dots disappear; objects shrink

→
Dilation

Main objects grow back

→
Opening

Cleaner outside background

Why does the noise stay gone?

If erosion completely removes a tiny white dot, dilation has nothing at that location to grow back.

Which order creates opening?

STEP 8 · FILL BLACK GAPS

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)
PartMeaning
cv2.morphologyEx(...)Run a combined morphological operation.
binaryThe input image.
cv2.MORPH_CLOSESelect closing: dilation followed by erosion.
kernelThe neighbourhood used by both parts.
closed =Store the repaired result.
Dilation

Fills tiny holes; objects grow

→
Erosion

Objects shrink back

→
Closing

Cleaner inside object

Choose opening when...

You see unwanted small white dots in the black background.

Choose closing when...

You see unwanted small black holes or cracks inside white objects.

Which operation is usually best for tiny black holes inside a white shape?

STEP 9 · COMPARE

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()
LineMeaning
cv2.waitKey(0)Wait for any key forever. Without it, the windows may close immediately.
cv2.destroyAllWindows()Close every OpenCV window cleanly.
Optional: save the useful results
cv2.imwrite("opened.png", opened)
cv2.imwrite("closed.png", closed)

imwrite() stores the arrays as image files in the project folder.

STEP 10 · PUT IT TOGETHER

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

  1. Start with a (3, 3) kernel.
  2. Change it to (5, 5) and compare the boundaries.
  3. Try iterations=2 for erosion and dilation.
  4. Add small white dots to the background and test opening.
  5. Add small black holes inside a white object and test closing.
  6. 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.