OpenCV 비전 실무13장 · Shape 분석과 ROI 처리14 / 21← 이전목차다음 →MetaContents

13장. Shape 분석과 ROI 처리

실습 1 — approxPolyDP 로 꼭짓점 세기

도형 판(ch13_shapes.png)의 여덟 도형에서 윤곽선을 찾고, cv2.approxPolyDP 로 꼭짓점을 셉니다. epsilon 의 비율을 바꿔 가며 꼭짓점 수가 어떻게 바뀌는지 표로 보고, 근사 다각형을 그림 위에 그려 봅니다.

1단계. 윤곽선 찾고 자리 순서로 늘어놓기

Python
import cv2
import numpy as np
import pandas as pd

# 도형 판을 읽는다. 어두운 바탕에 도형 8개를 그린 연습용 가상 그림이다
img = cv2.imread("ch13_shapes.png")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 바탕(밝기 40)보다 밝은 곳을 흰색으로: 도형만 흰색인 이진 그림
_, binary = cv2.threshold(gray, 60, 255, cv2.THRESH_BINARY)

# 12차시에서 본 대로 바깥 윤곽선만, 곧은 변의 점은 줄여서 찾는다
contours, _ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

# 12차시에서 본 것처럼 찾은 차례는 위치 순서가 아니므로, 위 줄 → 아래 줄, 왼쪽 → 오른쪽 순서로 늘어놓는다
# boundingRect 의 y 를 100 으로 나눈 몫이 줄 번호(0 또는 1), x 가 줄 안의 순서
contours = sorted(contours, key=lambda c: (cv2.boundingRect(c)[1] // 100, cv2.boundingRect(c)[0]))
# 그림을 만들 때 놓은 자리 순서의 이름
names = ["원", "세모", "네모", "막대", "별", "초승달", "팔각형", "기운 네모"]
print("찾은 윤곽선:", len(contours))
for name, c in zip(names, contours):
    print(f"{name}: 윤곽선 점 {len(c)}개, 넓이 {int(cv2.contourArea(c))}")
출력
찾은 윤곽선: 8
원: 윤곽선 점 84개, 넓이 2212
세모: 윤곽선 점 83개, 넓이 1344
네모: 윤곽선 점 4개, 넓이 2704
막대: 윤곽선 점 4개, 넓이 1280
별: 윤곽선 점 112개, 넓이 1230
초승달: 윤곽선 점 84개, 넓이 1272
팔각형: 윤곽선 점 8개, 넓이 2558
기운 네모: 윤곽선 점 124개, 넓이 2074

2단계. 둘레의 1% 로 꼭짓점 세기

Python
rows = []
for name, c in zip(names, contours):
    # arcLength(윤곽선, 닫힌 선인가): 둘레 길이
    peri = cv2.arcLength(c, True)
    # approxPolyDP(윤곽선, epsilon, 닫힌 선인가): 둘레의 1% 까지 비뚤어져도 곧은 선으로 본다
    approx = cv2.approxPolyDP(c, 0.01 * peri, True)
    rows.append({"도형": name, "둘레": round(peri, 1), "epsilon": round(0.01 * peri, 2),
                 "윤곽선 점": len(c), "꼭짓점": len(approx)})

print(pd.DataFrame(rows).to_string(index=False))
출력
   도형    둘레  epsilon  윤곽선 점  꼭짓점
    원 178.5     1.79     84   12
   세모 175.2     1.75     83    3
   네모 208.0     2.08      4    4
   막대 168.0     1.68      4    4
    별 238.5     2.38    112   10
  초승달 208.3     2.08     84   12
  팔각형 184.2     1.84      8    8
기운 네모 196.9     1.97    124    4

3단계. 비율을 바꿔 가며 꼭짓점 수 견주기

Python
# epsilon = 비율 × 둘레. 비율 다섯 가지로 꼭짓점 수가 어떻게 바뀌는지 본다
ratios = [0.005, 0.01, 0.02, 0.04, 0.08]
table = {}
for name, c in zip(names, contours):
    peri = cv2.arcLength(c, True)
    table[name] = [len(cv2.approxPolyDP(c, r * peri, True)) for r in ratios]

# 줄은 도형, 칸은 비율인 표
sweep = pd.DataFrame(table, index=[f"비율 {r}" for r in ratios]).T
print(sweep.to_string())
출력
       비율 0.005  비율 0.01  비율 0.02  비율 0.04  비율 0.08
원            16       12        8        8        4
세모            3        3        3        3        3
네모            4        4        4        4        4
막대            4        4        4        4        4
별            10       10       10       10        5
초승달          17       12        9        7        5
팔각형           8        8        8        8        4
기운 네모         4        4        4        4        4

4단계. 근사 다각형을 그림 위에 그려 보기

Python
import matplotlib.pyplot as plt

# 그래프의 한글이 깨지지 않게 맑은 고딕을 쓰고, 빼기 기호가 네모로 나오지 않게 한다
plt.rcParams["font.family"] = "Malgun Gothic"
plt.rcParams["axes.unicode_minus"] = False

fig, axes = plt.subplots(1, 3, figsize=(11, 3.2))
for ax, r in zip(axes, [0.005, 0.02, 0.08]):
    # 원본을 지키려고 복사본에 그린다
    canvas = img.copy()
    for c in contours:
        approx = cv2.approxPolyDP(c, r * cv2.arcLength(c, True), True)
        # polylines(그림, [점들], 닫을까, 색, 두께): 근사 다각형을 흰 선으로 잇는다
        cv2.polylines(canvas, [approx], True, (255, 255, 255), 2)
        # 꼭짓점마다 작은 점을 찍는다
        for (x, y) in approx.reshape(-1, 2):
            cv2.circle(canvas, (int(x), int(y)), 3, (0, 0, 0), -1)
    ax.imshow(cv2.cvtColor(canvas, cv2.COLOR_BGR2RGB))
    ax.set_title(f"비율 {r}")
    ax.axis("off")
plt.tight_layout()
plt.show()
epsilon 비율 0.005, 0.02, 0.08 로 근사한 다각형을 도형 판 위에 흰 선으로 그린 세 그림

코드 설명

  • sorted(..., key=lambda c: (y // 100, x)) 는 윤곽선을 위 줄부터, 줄 안에서는 왼쪽부터 늘어놓습니다. 12장에서 본 것처럼 findContours 가 돌려주는 차례는 위치 순서가 아니어서(아래쪽부터 나오기도 합니다), 이름과 짝지으려면 12장처럼 (y // 줄 높이, x) 로 정렬합니다.
  • 비율 0.01 에서 꼭짓점은 원 12, 세모 3, 네모·막대·기운 네모 4, 별 10, 초승달 12, 팔각형 8개입니다. 네모와 막대, 팔각형은 CHAIN_APPROX_SIMPLE 로 찾은 윤곽선 점이 이미 4개, 8개라 바뀔 것이 없고, 원·세모·별·기운 네모는 칸의 계단 때문에 점이 83~124개였다가 크게 줄었습니다.
  • 비율을 0.02 로 올리면 원이 꼭짓점 8개가 되어 팔각형과 같아집니다. 0.08 에서는 원과 팔각형이 4개(네모), 별과 초승달이 5개가 됩니다. 세모, 네모, 막대, 기운 네모는 다섯 비율 모두에서 그대로였습니다. 곧은 변으로 된 모양은 epsilon 에 덜 흔들립니다.
  • cv2.polylines(그림, [점들], True, 색, 두께) 는 점들을 차례로 이은 선을 그립니다. poly lines(여러 선)라는 뜻이고, 셋째 인자 True 는 마지막 점과 첫 점도 이어 닫으라는 뜻입니다.
← 이전 절13장 · 실습 지도다음 절 →13장 · 실습 2 — 모양 특징 표 만들기