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

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

실습 2 — 침식과 팽창의 크기와 횟수 바꿔 보기

도형 판(ch09_shapes.png)을 도장 크기 3, 5, 9 로 침식·팽창하고, 흰 칸 수와 흰 덩어리 수가 어떻게 바뀌는지 표로 봅니다. iterations 로 되풀이한 결과가 큰 도장 한 번과 같은지도 확인합니다.

1단계. 도형 판 읽고 덩어리 세기

Python
import cv2
import numpy as np
import pandas as pd

# 검은 바탕에 흰 도형을 그린 가상의 이진 그림
shapes = cv2.imread("ch09_shapes.png", cv2.IMREAD_GRAYSCALE)
print("모양:", shapes.shape, "/ 들어 있는 값:", [int(v) for v in np.unique(shapes)])
print("흰 칸 수:", int((shapes == 255).sum()))

# connectedComponents = connected components(이어진 덩어리들)
# 돌려주는 값: (덩어리 수 + 바탕 1, 칸마다 덩어리 번호를 적은 그림)
count, labels = cv2.connectedComponents(shapes)
print("흰 덩어리 수:", count - 1)
출력
모양: (200, 320) / 들어 있는 값: [0, 255]
흰 칸 수: 15931
흰 덩어리 수: 8

2단계. 도장 크기별 흰 칸과 덩어리 수 표

Python
def blobs(binary):
    """흰 덩어리 수를 돌려준다."""
    return cv2.connectedComponents(binary)[0] - 1

rows, outs = [], {}
for k in [3, 5, 9]:
    se = cv2.getStructuringElement(cv2.MORPH_RECT, (k, k))
    outs[("침식", k)] = cv2.erode(shapes, se)
    outs[("팽창", k)] = cv2.dilate(shapes, se)
    for op in ["침식", "팽창"]:
        out = outs[(op, k)]
        rows.append({"연산": op, "도장": f"{k}×{k}",
                     "흰 칸": int((out == 255).sum()), "흰 덩어리": blobs(out)})

# 원본 줄을 맨 위에 넣어 견주기 쉽게 한다
table = pd.DataFrame([{"연산": "원본", "도장": "-", "흰 칸": int((shapes == 255).sum()),
                       "흰 덩어리": blobs(shapes)}] + rows)
print(table.to_string(index=False))
출력
연산  도장   흰 칸  흰 덩어리
원본   - 15931      8
침식 3×3 14158      7
팽창 3×3 17779      8
침식 5×5 12664      5
팽창 5×5 19678      7
침식 9×9 10512      4
팽창 9×9 23568      7

3단계. iterations 와 큰 도장 견주기

Python
k3 = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
for n in [1, 2, 3, 4]:
    # iterations=n: 3×3 침식을 n 번 되풀이한다
    eroded = cv2.erode(shapes, k3, iterations=n)
    # 같은 세기의 큰 도장: 한 번에 n 칸씩 깎으려면 크기 2n + 1
    big = cv2.getStructuringElement(cv2.MORPH_RECT, (2 * n + 1, 2 * n + 1))
    same = np.array_equal(eroded, cv2.erode(shapes, big))
    print(f"3×3 을 {n}번: 흰 칸 {int((eroded == 255).sum()):5}, 덩어리 {blobs(eroded)}"
          f" / {2 * n + 1}×{2 * n + 1} 한 번과 같은가: {same}")
출력
3×3 을 1번: 흰 칸 14158, 덩어리 7 / 3×3 한 번과 같은가: True
3×3 을 2번: 흰 칸 12664, 덩어리 5 / 5×5 한 번과 같은가: True
3×3 을 3번: 흰 칸 11420, 덩어리 8 / 7×7 한 번과 같은가: True
3×3 을 4번: 흰 칸 10512, 덩어리 4 / 9×9 한 번과 같은가: True

4단계. 격자로 견주기

Python
import matplotlib.pyplot as plt

plt.rcParams["font.family"] = "Malgun Gothic"
plt.rcParams["axes.unicode_minus"] = False

# 윗줄은 침식, 아랫줄은 팽창. 맨 왼쪽은 원본
fig, axes = plt.subplots(2, 4, figsize=(13, 4.6))
for i, op in enumerate(["침식", "팽창"]):
    axes[i, 0].imshow(shapes, cmap="gray", vmin=0, vmax=255)
    axes[i, 0].set_title("원본")
    for j, k in enumerate([3, 5, 9], start=1):
        out = outs[(op, k)]
        axes[i, j].imshow(out, cmap="gray", vmin=0, vmax=255)
        axes[i, j].set_title(f"{op} {k}×{k} (덩어리 {blobs(out)})")
for ax in axes.flat:
    ax.axis("off")
plt.tight_layout()
plt.show()
도형 판의 원본과 3×3·5×5·9×9 침식, 팽창 결과를 2행 4열로 놓은 결과

코드 설명

  • 도형 판은 0 과 255 두 값만 있는 이진 그림이고, 흰 칸 15931개, 흰 덩어리 8개(네모, 원, 가는 선, 작은 점 둘, 두 원, 고리)입니다.
  • 침식은 도장이 클수록 흰 칸이 줄어듭니다(14158 → 12664 → 10512). 3×3 은 작은 점 하나만 지워 덩어리가 7개가 되고, 5×5 는 가는 선과 남은 점까지 지워 5개, 9×9 는 고리까지 지워 4개(네모, 원, 두 원)만 남깁니다.
  • 팽창은 흰 칸이 늘어납니다(17779 → 19678 → 23568). 3×3 은 덩어리 8개 그대로지만, 5×5 부터 4 칸 떨어진 두 원이 하나로 붙어 7개가 됩니다.
  • 3×3 침식을 n 번 되풀이한 결과는 (2n + 1)×(2n + 1) 침식 한 번과 네 경우 모두 같았습니다(True). 3번(7×7)에서 덩어리가 8개로 오히려 늘어난 것은 굵기 6 칸의 고리가 다 지워지기 전에 작은 조각 네 개로 끊겼기 때문입니다. 너무 센 침식은 덩어리를 지우기만 하는 게 아니라 쪼개기도 합니다.
← 이전 절9장 · 실습 1 — 구조 요소와 손계산 확인하기다음 절 →9장 · 실습 3 — 열기로 흰 잡티 지우기