13장. Shape 분석과 ROI 처리
실습 2 — 모양 특징 표 만들기
도형 판의 여덟 도형마다 넓이, 둘레, 원형도, 종횡비, 채움 비율, 볼록 비율을 재어 표 하나로 모읍니다. 숫자를 막대그래프로 견줘 어느 특징이 어떤 도형을 가르는지 봅니다.
1단계. 윤곽선 준비하기
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))윤곽선: 82단계. 특징을 재는 함수 만들기
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단계. 여덟 도형의 표 만들기
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단계. 특징을 막대그래프로 견주기
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()
코드 설명
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 으로 떨어집니다.