9장. Morphology: 침식, 팽창, 열기, 닫기
실습 4 — 닫기로 끊긴 글자와 구멍 메우기
획에 틈과 구멍이 난 글자 그림(ch09_letters_broken.png)을 닫기로 메우고, 본래 글자(ch09_letters_clean.png)와 견줍니다. 도장 크기와 모양을 바꿔 가며 덩어리 수와 다른 칸 수를 잽니다.
1단계. 두 그림 읽고 견주기
import cv2
import numpy as np
clean = cv2.imread("ch09_letters_clean.png", cv2.IMREAD_GRAYSCALE)
broken = cv2.imread("ch09_letters_broken.png", cv2.IMREAD_GRAYSCALE)
def blobs(binary):
return cv2.connectedComponents(binary)[0] - 1
def diff(out):
"""본래 글자와 값이 다른 칸의 수"""
return int((out != clean).sum())
print("본래 글자: 흰 칸", int((clean == 255).sum()), "/ 흰 덩어리", blobs(clean))
print("끊긴 글자: 흰 칸", int((broken == 255).sum()), "/ 흰 덩어리", blobs(broken))
print("다른 칸:", diff(broken))본래 글자: 흰 칸 6375 / 흰 덩어리 5
끊긴 글자: 흰 칸 5863 / 흰 덩어리 9
다른 칸: 5122단계. 도장 크기별 닫기
closed = {}
for k in [3, 5, 7, 9, 11]:
se = cv2.getStructuringElement(cv2.MORPH_RECT, (k, k))
# MORPH_CLOSE: 팽창한 뒤 같은 도장으로 침식
closed[k] = cv2.morphologyEx(broken, cv2.MORPH_CLOSE, se)
print(f"닫기 {k:2}×{k:<2}: 흰 덩어리 {blobs(closed[k])}, 다른 칸 {diff(closed[k])}")닫기 3×3 : 흰 덩어리 7, 다른 칸 456
닫기 5×5 : 흰 덩어리 5, 다른 칸 151
닫기 7×7 : 흰 덩어리 5, 다른 칸 205
닫기 9×9 : 흰 덩어리 1, 다른 칸 814
닫기 11×11: 흰 덩어리 1, 다른 칸 13103단계. 도장 모양 견주고 저장하기
kinds = {"RECT": cv2.MORPH_RECT, "CROSS": cv2.MORPH_CROSS, "ELLIPSE": cv2.MORPH_ELLIPSE}
by_shape = {}
for name, shape in kinds.items():
se = cv2.getStructuringElement(shape, (5, 5))
by_shape[name] = cv2.morphologyEx(broken, cv2.MORPH_CLOSE, se)
print(f"닫기 5×5 {name:8}: 흰 덩어리 {blobs(by_shape[name])}, 다른 칸 {diff(by_shape[name])}")
# 다른 칸이 가장 적은 모양을 골라 저장한다
best = min(by_shape, key=lambda name: diff(by_shape[name]))
cv2.imwrite("out_ch09_closed.png", by_shape[best])
print("가장 좋은 모양:", best)
print("저장: out_ch09_closed.png")닫기 5×5 RECT : 흰 덩어리 5, 다른 칸 151
닫기 5×5 CROSS : 흰 덩어리 5, 다른 칸 334
닫기 5×5 ELLIPSE : 흰 덩어리 5, 다른 칸 178
가장 좋은 모양: RECT
저장: out_ch09_closed.png4단계. 격자로 견주기
import matplotlib.pyplot as plt
plt.rcParams["font.family"] = "Malgun Gothic"
plt.rcParams["axes.unicode_minus"] = False
panels = [("끊긴 글자", broken)] + [(f"닫기 {k}×{k}", closed[k]) for k in [3, 5, 7, 9]]
panels.append(("본래 글자", clean))
fig, axes = plt.subplots(2, 3, figsize=(12, 4.2))
for ax, (title, im) in zip(axes.flat, panels):
ax.imshow(im, cmap="gray", vmin=0, vmax=255)
ax.set_title(f"{title} (덩어리 {blobs(im)}, 다른 칸 {diff(im)})")
ax.axis("off")
plt.tight_layout()
plt.show()
코드 설명
- 끊긴 글자는 틈 때문에 흰 덩어리가 9개(본래 5개)이고, 틈과 구멍 자리 512칸이 본래 글자와 다릅니다.
- 닫기 3×3 은 굵기 2 칸의 틈 일부만 메워 덩어리 7개가 남습니다. 5×5 에서 덩어리 5개, 다른 칸 151개로 가장 좋고, 7×7 도 덩어리는 5개지만 다른 칸이 205개로 늘었습니다.
- 닫기 9×9 와 11×11 은 덩어리가 1개입니다. 글자 사이 틈까지 메워 다섯 글자가 하나로 붙었습니다. 너무 센 닫기의 모습입니다.
- 같은 5×5 에서 모양을 바꾸면 네모(RECT)가 다른 칸 151개로 가장 적고, 타원 178개, 십자 334개 순입니다. 십자는 1 인 칸이 9개뿐이라 같은 5×5 라도 메우는 힘이 약합니다. 가장 좋은 네모의 결과를
out_ch09_closed.png로 저장했습니다.