Computer Vision Chapter 8

Morphological operations

Morphology slides a small shape—the structuring element—over the image and asks local questions: “Do all neighbors under the kernel belong to the foreground?” (erosion) or “Does any neighbor hit foreground?” (dilation). Composing these operations removes speckle, fills gaps, separates touching blobs, and extracts boundaries. Works on binary masks and can extend to grayscale (min/max filters).

Structuring elements

cv2.getStructuringElement(shape, ksize) builds the probe: MORPH_RECT, MORPH_ELLIPSE, or MORPH_CROSS. Larger kernels have stronger geometric effect—use odd sizes (3, 5, 7, …).

import cv2

k3 = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
k5e = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))
k7c = cv2.getStructuringElement(cv2.MORPH_CROSS, (7, 7))

Erosion

Shrinks bright regions, removes thin protrusions, breaks narrow bridges.

Dilation

Grows bright regions, fills small holes, reconnects broken strokes.

Iterations

Repeat erosion/dilation iterations=n for stronger effect without huge kernels.

Erosion and dilation

import cv2

# bw: uint8 binary, foreground white (255)
bw = cv2.imread("mask.png", cv2.IMREAD_GRAYSCALE)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))

er = cv2.erode(bw, kernel, iterations=1)
dl = cv2.dilate(bw, kernel, iterations=1)

er2 = cv2.erode(bw, kernel, iterations=2)
dl3 = cv2.dilate(bw, kernel, iterations=3)

Border pixels: default border type is BORDER_CONSTANT with value 0—large iterations can “eat” edges of the image.

Opening and closing

Opening = erosion then dilation—removes small bright noise and smooths boundaries without growing the main objects much. Closing = dilation then erosion—fills small dark holes inside foreground and bridges narrow gaps.

import cv2

kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))
opened = cv2.morphologyEx(bw, cv2.MORPH_OPEN, kernel, iterations=1)
closed = cv2.morphologyEx(bw, cv2.MORPH_CLOSE, kernel, iterations=1)

# Equivalent explicit opening:
# tmp = cv2.erode(bw, kernel); opened = cv2.dilate(tmp, kernel)

Noise vs broken strokes

k3 = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
k5e = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))

# Salt on black background: opening cleans white specks
clean_fg = cv2.morphologyEx(bw, cv2.MORPH_OPEN, k3, iterations=1)

# Gaps in text strokes: closing helps
solid_text = cv2.morphologyEx(bw, cv2.MORPH_CLOSE, k5e, iterations=1)

Gradient, top-hat, black-hat

Morphological gradient ≈ dilation minus erosion—outline of objects. Top-hat = image minus its opening—highlights small bright details on a dark background. Black-hat = closing minus image—dark details on bright background.

import cv2

grad = cv2.morphologyEx(bw, cv2.MORPH_GRADIENT, kernel)

gray = cv2.imread("texture.png", cv2.IMREAD_GRAYSCALE)
k = cv2.getStructuringElement(cv2.MORPH_RECT, (9, 9))
tophat = cv2.morphologyEx(gray, cv2.MORPH_TOPHAT, k)
blackhat = cv2.morphologyEx(gray, cv2.MORPH_BLACKHAT, k)

Hit-or-miss (shape matching)

MORPH_HITMISS finds pixels where a binary pattern and its complement align with a template kernel (hits, misses, and “don’t care” positions—see your OpenCV version’s rules for exact matrix values). Common on skeletonized text or thin structures to locate T-junctions or endpoints.

import cv2
import numpy as np

# Placeholder kernel — replace with a pattern from OpenCV hit-or-miss docs for your task
hitmiss_kernel = np.array([[0, 1, 0],
                           [1, 1, 0],
                           [0, 0, 0]], dtype=np.int8)
out = cv2.morphologyEx(bw, cv2.MORPH_HITMISS, hitmiss_kernel)

Encoding of 0/1/-1 differs by tutorial; always cross-check the official cv.morphologyEx hit-miss section for your build.

End-to-end: clean a binary scan

import cv2

gray = cv2.imread("scan.png", cv2.IMREAD_GRAYSCALE)
blur = cv2.GaussianBlur(gray, (3, 3), 0)
_, bw = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)

k = cv2.getStructuringElement(cv2.MORPH_RECT, (2, 2))
bw = cv2.morphologyEx(bw, cv2.MORPH_OPEN, k, iterations=1)
bw = cv2.morphologyEx(bw, cv2.MORPH_CLOSE, k, iterations=1)

Takeaways

  • Opening removes small foreground noise; closing fills holes in foreground.
  • Prefer several small iterations or moderate kernels over one huge kernel for smoother control.
  • Gradient / top-hat / black-hat extract boundaries and fine structure on binary or gray images.

Quick FAQ

OpenCV morphology treats high values as “set” in the usual binary convention. If your mask has objects as 0 and background as 255, invert with cv2.bitwise_not first so semantics match your intent.

Ellipses round off corners isotropically—often nicer for blob-like objects. Rectangles align with axis-aligned text or grid structures.