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
cv2.bitwise_not first so semantics match your intent.