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

13장. Shape 분석과 ROI 처리

실습 2 — 모양 특징 표 만들기

도형 판의 여덟 도형마다 넓이, 둘레, 원형도, 종횡비, 채움 비율, 볼록 비율을 재어 표 하나로 모읍니다. 숫자를 막대그래프로 견줘 어느 특징이 어떤 도형을 가르는지 봅니다.

1단계. 윤곽선 준비하기

Python
import math
import cv2
import pandas as pd

img = cv2.imread("ch13_shapes.png")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
_, binary = cv2.threshold(gray, 60, 255, cv2.THRESH_BINARY)
contours, _ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# 실습 1과 같이 자리 순서로 늘어놓고 이름을 짝짓는다
contours = sorted(contours, key=lambda c: (cv2.boundingRect(c)[1] // 100, cv2.boundingRect(c)[0]))
names = ["원", "세모", "네모", "막대", "별", "초승달", "팔각형", "기운 네모"]
print("윤곽선:", len(contours))
출력
윤곽선: 8

2단계. 특징을 재는 함수 만들기

Python
def shape_features(c):
    """윤곽선 하나에서 모양 숫자를 재어 사전으로 돌려준다."""
    area = cv2.contourArea(c)                     # 넓이
    peri = cv2.arcLength(c, True)                 # 둘레
    x, y, w, h = cv2.boundingRect(c)              # 똑바로 선 감싸는 상자
    hull = cv2.convexHull(c)                      # 고무줄을 감은 모양(볼록 껍질)
    approx = cv2.approxPolyDP(c, 0.01 * peri, True)
    return {
        "넓이": int(area),
        "둘레": round(peri, 1),
        "꼭짓점": len(approx),
        # 원형도: 4π × 넓이 ÷ 둘레². 원이면 1 에 가깝다
        "원형도": round(4 * math.pi * area / peri ** 2, 3),
        # 종횡비: 가로 ÷ 세로
        "종횡비": round(w / h, 2),
        # 채움 비율: 넓이 ÷ 상자 넓이
        "채움비율": round(area / (w * h), 3),
        # 볼록 비율: 넓이 ÷ 볼록 껍질 넓이
        "볼록비율": round(area / cv2.contourArea(hull), 3),
    }

# 네모 하나로 먼저 시험해 본다
print(shape_features(contours[2]))
출력
{'넓이': 2704, '둘레': 208.0, '꼭짓점': 4, '원형도': 0.785, '종횡비': 1.0, '채움비율': 0.963, '볼록비율': 1.0}

3단계. 여덟 도형의 표 만들기

Python
rows = []
for name, c in zip(names, contours):
    # 이름 칸을 맨 앞에 두고 특징 사전을 이어 붙인다
    rows.append({"도형": name, **shape_features(c)})
features = pd.DataFrame(rows)
print(features.to_string(index=False))

# 특징마다 가장 큰 도형과 가장 작은 도형
for col in ["원형도", "종횡비", "채움비율", "볼록비율"]:
    top = features.loc[features[col].idxmax(), "도형"]
    low = features.loc[features[col].idxmin(), "도형"]
    print(f"{col}: 가장 큼 {top}, 가장 작음 {low}")
출력
   도형   넓이    둘레  꼭짓점   원형도  종횡비  채움비율  볼록비율
    원 2212 178.5   12 0.872 1.00 0.731 0.977
   세모 1344 175.2    3 0.550 1.16 0.481 0.971
   네모 2704 208.0    4 0.785 1.00 0.963 1.000
   막대 1280 168.0    4 0.570 3.10 0.938 1.000
    별 1230 238.5   10 0.272 1.03 0.342 0.507
  초승달 1272 208.3   12 0.368 0.90 0.379 0.583
  팔각형 2558 184.2    8 0.948 1.00 0.787 1.000
기운 네모 2074 196.9    4 0.673 1.00 0.596 0.966
원형도: 가장 큼 팔각형, 가장 작음 별
종횡비: 가장 큼 막대, 가장 작음 초승달
채움비율: 가장 큼 네모, 가장 작음 별
볼록비율: 가장 큼 네모, 가장 작음 별

4단계. 특징을 막대그래프로 견주기

Python
import matplotlib.pyplot as plt

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

# 0~1 사이의 세 특징만 한 그래프에 놓는다(종횡비는 3 이 넘어 따로 본다)
cols = ["원형도", "채움비율", "볼록비율"]
ax = features.set_index("도형")[cols].plot.bar(figsize=(10, 4), rot=0)
ax.set_ylim(0, 1.25)                         # 범례가 막대를 가리지 않게 위쪽에 여유
ax.axhline(0.9, color="gray", ls="--", lw=1)  # 볼록 비율의 경계로 쓸 0.9
ax.legend(loc="upper center", ncol=3)
ax.set_ylabel("값 (0~1)")
ax.set_title("도형마다의 원형도, 채움 비율, 볼록 비율")
plt.tight_layout()
plt.show()
여덟 도형의 원형도, 채움 비율, 볼록 비율을 묶은 막대그래프와 0.9 회색 점선

코드 설명

  • cv2.convexHull(c) 는 윤곽선을 고무줄로 감은 모양의 꼭짓점을 돌려줍니다. 그 넓이로 물체의 넓이를 나눈 것이 볼록 비율입니다.
  • {"도형": name, **shape_features(c)} 의 ** 는 사전의 칸을 풀어 새 사전에 이어 붙입니다. 이름 칸이 맨 앞에 오는 표가 됩니다.
  • 원형도가 가장 큰 것은 원(0.872)이 아니라 팔각형(0.948)입니다. 원은 칸의 계단 때문에 둘레가 길게 재어지기 때문입니다. 원형도 하나로는 원과 팔각형을 가를 수 없어서, 실습 3의 규칙은 꼭짓점 수로 둘을 가릅니다.
  • 볼록 비율은 별 0.507, 초승달 0.583 만 0.9 아래이고 나머지 여섯은 0.96 을 넘습니다. 그래프의 회색 점선 0.9 가 움푹한 두 모양을 깔끔하게 가릅니다. 채움 비율은 네모 0.963, 막대 0.938 이 크고, 같은 네모라도 기운 네모는 0.596 으로 떨어집니다.
← 이전 절13장 · 실습 1 — approxPolyDP 로 꼭짓점 세기다음 절 →13장 · 실습 3 — 규칙으로 모양 이름 붙이기