12장. Contour Detection 과 객체 분석
무게중심과 모멘트
두꺼운 종이를 물체 모양으로 오려 연필 끝 하나로 받쳐 본다고 해 봅시다. 기울지 않고 균형이 맞는 점이 하나 있습니다. 그 점이 무게중심입니다. 종이의 무게가 고르게 퍼져 있다면 무게중심은 모든 칸의 가로 위치 평균과 세로 위치 평균입니다.
그림의 세 걸음이 cv2.moments 로 무게중심을 구하는 순서입니다. m00 은 흰 칸의 수(넓이), m10 은 흰 칸들의 x 좌표를 모두 더한 값, m01 은 y 좌표를 모두 더한 값입니다. 더한 값을 칸의 수로 나누면 평균, 곧 무게중심이 됩니다. 여기서 오려 낸 종이가 흰 덩어리, 연필 끝으로 받친 점이 무게중심입니다.
ㄱ 모양으로 흰 칸 다섯 개를 칠했습니다. 칸 안의 글자가 그 칸의 (x, y) 좌표입니다. 다홍 X 가 무게중심 (1.6, 1.6) 으로, 흰 칸이 몰린 왼쪽 위로 치우쳐 있습니다.
손으로 계산해 보기
| 값 | 뜻 | 계산 | 결과 |
|---|---|---|---|
| m00 | 흰 칸의 수 | 1 + 1 + 1 + 1 + 1 | 5 |
| m10 | x 좌표의 합 | 1 + 2 + 3 + 1 + 1 | 8 |
| m01 | y 좌표의 합 | 1 + 1 + 1 + 2 + 3 | 8 |
| cx | x 의 평균 | 8 ÷ 5 | 1.6 |
| cy | y 의 평균 | 8 ÷ 5 | 1.6 |
이름의 숫자 두 개는 x 와 y 를 몇 번 곱했는지를 뜻합니다. m10 은 x 를 한 번, y 를 0 번 곱해 더한 값이고, m00 은 아무것도 곱하지 않고 1 씩 더한 값입니다.
짧은 예시
import cv2
import numpy as np
# 본문의 ㄱ 모양 그림
small = np.zeros((5, 5), np.uint8)
small[1, 1:4] = 255
small[2:4, 1] = 255
# binaryImage=True: 0 이 아닌 칸을 모두 1 로 보고 센다
m = cv2.moments(small, binaryImage=True)
print("m00, m10, m01:", m["m00"], m["m10"], m["m01"])
print("무게중심:", m["m10"] / m["m00"], m["m01"] / m["m00"])
# 윤곽선으로도 구할 수 있다. 도형 판의 곧은 네모
shapes = cv2.imread("ch12_shapes.png", cv2.IMREAD_GRAYSCALE)
contours, _ = cv2.findContours(shapes, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
mr = cv2.moments(contours[-1])
print("네모의 무게중심:", mr["m10"] / mr["m00"], mr["m01"] / mr["m00"])교안 PC 에서는 m00, m10, m01 이 5.0, 8.0, 8.0 이고 무게중심이 (1.6, 1.6) 입니다. 네모의 무게중심은 (59.5, 44.5) 로, 칠한 칸 x 20~99 의 평균 (20 + 99) ÷ 2 = 59.5 와 y 20~69 의 평균 44.5 와 같습니다.
주의할 점
- 윤곽선으로 구한 모멘트의 m00 은 contourArea 와 같습니다. 넓이가 0 인 윤곽선(점 하나, 한 줄짜리 선)은 m00 이 0 이라 나누면 오류가 납니다.
if m["m00"] > 0:으로 거릅니다. - 도넛이나 ㄷ 모양처럼 가운데가 빈 물체는 무게중심이 물체 바깥(빈 곳)에 놓일 수 있습니다. 무게중심은 "평균 위치"이지 "물체 위의 한 점"이 아닙니다.