OpenCV 비전 실무9장 · Morphology: 침식, 팽창, 열기, 닫기17 / 21← 이전목차다음 →MetaContents

9장. Morphology: 침식, 팽창, 열기, 닫기

실습 4 — 닫기로 끊긴 글자와 구멍 메우기

획에 틈과 구멍이 난 글자 그림(ch09_letters_broken.png)을 닫기로 메우고, 본래 글자(ch09_letters_clean.png)와 견줍니다. 도장 크기와 모양을 바꿔 가며 덩어리 수와 다른 칸 수를 잽니다.

1단계. 두 그림 읽고 견주기

Python
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
다른 칸: 512

2단계. 도장 크기별 닫기

Python
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, 다른 칸 1310

3단계. 도장 모양 견주고 저장하기

Python
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.png

4단계. 격자로 견주기

Python
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()
끊긴 글자, 닫기 3×3·5×5·7×7·9×9, 본래 글자를 2행 3열로 놓은 결과

코드 설명

  • 끊긴 글자는 틈 때문에 흰 덩어리가 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 로 저장했습니다.
← 이전 절9장 · 실습 3 — 열기로 흰 잡티 지우기다음 절 →9장 · 실습 5 — 순서 비교와 그레이디언트로 테두리 보기