OpenCV 비전 실무12장 · Contour Detection 과 객체 분석16 / 21← 이전목차다음 →MetaContents

12장. Contour Detection 과 객체 분석

실습 3 — 면적, 둘레, 무게중심 재기

도형 판의 여섯 도형마다 면적, 둘레, 무게중심을 재서 표로 만들고, 네모와 원은 손계산과 견줍니다. 마지막에 무게중심에 점을 찍어 봅니다.

1단계. 도형마다 재서 표 만들기

Python
import cv2
import numpy as np
import pandas as pd

shapes = cv2.imread("ch12_shapes.png", cv2.IMREAD_GRAYSCALE)
contours, _ = cv2.findContours(shapes, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

rows = []
for c in contours:
    area = cv2.contourArea(c)                  # 면적: 점을 이은 도형의 넓이
    if area < 50:                              # 작은 점 둘은 이 실습에서 뺀다
        continue
    m = cv2.moments(c)                         # 모멘트: m00(넓이), m10, m01(좌표의 합)
    rows.append({"면적": area,
                 "둘레": round(cv2.arcLength(c, True), 1),   # True: 닫힌 둘레
                 "cx": round(m["m10"] / m["m00"], 1),        # x 좌표 평균
                 "cy": round(m["m01"] / m["m00"], 1)})       # y 좌표 평균
table = pd.DataFrame(rows)
# 위쪽 줄(cy 100 미만)부터, 같은 줄에서는 왼쪽부터 번호를 매긴다
table = table.sort_values(["cy", "cx"], key=lambda s: s // 100 if s.name == "cy" else s)
table.index = ["네모", "원", "기운 네모", "세모", "도넛", "타원"]
print(table)
출력
           면적     둘레     cx     cy
네모     3871.0  256.0   59.5   44.5
원      3754.0  230.8  170.0   55.0
기운 네모  2596.0  235.6  262.0   58.0
세모     2800.0  253.1   65.0  191.7
도넛     5406.0  277.4  170.0  170.0
타원     2770.5  210.4  265.0  180.0

2단계. 네모와 원을 손계산과 견주기

Python
# 네모는 가로 80칸, 세로 50칸으로, 원은 반지름 35 로 칠했다
checks = [
    ("네모 흰 칸 수", int((shapes[20:70, 20:100] > 0).sum()), 80 * 50),
    ("네모 면적", table.loc["네모", "면적"], 79 * 49),
    ("네모 둘레", table.loc["네모", "둘레"], 2 * (79 + 49)),
    ("원 흰 칸 수", int((shapes[15:96, 130:211] > 0).sum()), round(3.14 * 35 * 35, 1)),
    ("원 면적", table.loc["원", "면적"], round(3.14 * 35 * 35, 1)),
    ("원 둘레", table.loc["원", "둘레"], round(2 * 3.14 * 35, 1)),
]
for name, measured, by_hand in checks:
    print(f"{name:8}: 잰 값 {measured:7} / 손계산 {by_hand:7}")
출력
네모 흰 칸 수: 잰 값    4000 / 손계산    4000
네모 면적   : 잰 값  3871.0 / 손계산    3871
네모 둘레   : 잰 값   256.0 / 손계산     256
원 흰 칸 수 : 잰 값    3853 / 손계산  3846.5
원 면적    : 잰 값  3754.0 / 손계산  3846.5
원 둘레    : 잰 값   230.8 / 손계산   219.8

3단계. 아주 작은 점의 면적과 흰 칸 수

Python
# 작은 점 둘(반지름 2, 1)은 contourArea 와 흰 칸 수가 크게 다르다
for c in contours:
    area = cv2.contourArea(c)
    if area >= 50:
        continue
    x, y, w, h = cv2.boundingRect(c)
    pixels = int((shapes[y:y + h, x:x + w] > 0).sum())     # 상자 안의 흰 칸 수
    # moments(그림, binaryImage=True): 그림의 흰 칸으로 잰 모멘트. m00 이 흰 칸 수다
    m = cv2.moments(shapes[y:y + h, x:x + w], binaryImage=True)
    print(f"상자 {(x, y, w, h)}: contourArea {area}, 흰 칸 {pixels}, 그림 모멘트 m00 {m['m00']}")
출력
상자 (299, 227, 3, 3): contourArea 2.0, 흰 칸 5, 그림 모멘트 m00 5.0
상자 (126, 110, 5, 5): contourArea 8.0, 흰 칸 13, 그림 모멘트 m00 13.0

4단계. 무게중심에 점 찍기

Python
import matplotlib.pyplot as plt

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

canvas = cv2.cvtColor(shapes // 3, cv2.COLOR_GRAY2BGR)
cv2.drawContours(canvas, contours, -1, (0, 0, 255), 1)
for name, row in table.iterrows():
    center = (int(round(row["cx"])), int(round(row["cy"])))
    # circle(그림, 중심, 반지름, 색, 두께 -1 = 채우기)
    cv2.circle(canvas, center, 4, (0, 255, 255), -1)

plt.figure(figsize=(6, 4.5))
plt.imshow(cv2.cvtColor(canvas, cv2.COLOR_BGR2RGB))
plt.title("도형마다 무게중심(노란 점)")
plt.axis("off")
plt.show()
도형 판의 여섯 도형에 빨간 윤곽선과 노란 무게중심 점을 그린 그림

코드 설명

  • 여섯 도형의 면적은 네모 3871.0, 원 3754.0, 기운 네모 2596.0, 세모 2800.0, 도넛 5406.0, 타원 2770.5 입니다. 도넛은 EXTERNAL 로 찾아 구멍이 면적에 들어 있습니다.
  • 네모는 흰 칸 4,000 개, contourArea 3,871(79 × 49), 둘레 256(2 × (79 + 49))으로 손계산과 꼭 맞았습니다. 원은 흰 칸 3,853 개가 πr²(3,846.5)와 거의 같고, contourArea 는 3,754 로 조금 작으며, 둘레는 230.8 로 손계산(219.8)보다 깁니다. 테두리가 계단 모양이기 때문입니다.
  • 반지름 2 짜리 점은 contourArea 8.0, 흰 칸 13개이고, 반지름 1 짜리 점은 2.0 과 5개입니다. cv2.moments(그림, binaryImage=True) 의 m00 은 흰 칸 수와 같습니다. 작은 덩어리일수록 두 값의 차이가 큽니다.
  • 무게중심은 네모 (59.5, 44.5), 원 (170.0, 55.0) 처럼 칠할 때 정한 가운데와 같습니다. 세모는 (65.0, 191.7) 로, 꼭짓점 (65, 145) 와 밑변 y 215 사이에서 넓은 밑변 쪽으로 치우쳤습니다.
← 이전 절12장 · 실습 2 — 세 가지 찾는 방식과 hierarchy 읽기다음 절 →12장 · 실습 4 — 곧은 상자, 회전 상자, 최소 외접원