13장. Shape 분석과 ROI 처리
모양으로 가려내기
분리수거장의 플라스틱 통에 페트병과 플라스틱 컵이 섞여 있다고 해 봅시다. 둘 다 투명하고 크기도 비슷합니다. 그래도 사람은 금방 가려냅니다. 병은 길쭉하고 목이 좁고, 컵은 위가 넓게 벌어졌기 때문입니다. 색과 크기가 같아도 생김새가 다르면 가려낼 수 있습니다. 컴퓨터에게는 이 "생김새"를 숫자로 바꿔 줘야 합니다.
그림의 세 상자는 물체를 가려내는 세 가지 단서입니다. 크기는 넓이 하나로, 색은 3장의 HSV 로 가려낼 수 있습니다. 그런데 크기와 색이 같으면 앞의 두 단서는 쓸모가 없고, 셋째 상자의 모양 숫자가 필요합니다. 여기서 페트병과 컵이 크기와 색이 같은 두 물체이고, "길쭉하다, 위가 넓다"가 모양 특징입니다.
이 장의 실습에 쓰는 도형 판입니다. 원과 팔각형은 같은 빨강, 세모와 별은 같은 노랑, 네모와 기운 네모는 같은 파랑, 긴 막대와 초승달은 같은 초록입니다. 색으로는 짝을 가를 수 없고, 넓이도 원(2,212칸)과 기운 네모(2,074칸)처럼 비슷한 것이 많습니다. 이 장에서는 이 여덟 개를 모양 숫자만으로 가려냅니다.
12차시에서 본 윤곽선 함수 되짚기
모양 숫자는 모두 윤곽선에서 시작합니다. 12장에서 본 함수를 한 줄씩만 되짚습니다.
| 함수 | 돌려주는 것 | 이 장에서 쓰는 곳 |
|---|---|---|
cv2.findContours(이진, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) | 바깥 윤곽선 목록 | 모든 실습의 시작 |
cv2.contourArea(c) | 면적(칸 수에 가까운 값) | 원형도, 채움 비율, 볼록 비율 |
cv2.arcLength(c, True) | 둘레 길이 | 원형도, epsilon |
cv2.drawContours(그림, [c], -1, 색, 두께) | 윤곽선 그리기(두께 -1 은 속까지 채우기) | 이름 그림, 모양 ROI 마스크 |
cv2.boundingRect(c) | 바운딩 박스 (x, y, 가로, 세로) | 종횡비, 채움 비율, 사각형 ROI |
cv2.minAreaRect(c) | 회전 상자(기울어진 채로 감싸는 가장 작은 네모) | 기운 물체의 종횡비 |
모양 특징 한눈에 보기
| 특징 | 구하는 법 | 원 | 네모 | 긴 막대 | 별 |
|---|---|---|---|---|---|
| 꼭짓점 수 | approxPolyDP | 많다 | 4 | 4 | 10 |
| 원형도 | 4π×넓이 ÷ 둘레² | 1 에 가깝다 | 약 0.785 | 작다 | 아주 작다 |
| 종횡비 | 가로 ÷ 세로 | 1 | 1 | 크다(3 쯤) | 1 쯤 |
| 채움 비율 | 넓이 ÷ 상자 넓이 | 약 0.785 | 1 | 1 에 가깝다 | 작다 |
| 볼록 비율 | 넓이 ÷ 볼록 껍질 넓이 | 1 | 1 | 1 | 작다 |
짧은 예시
import cv2
# 도형 판을 읽고 흑백으로 바꾼 뒤, 바탕(밝기 40)보다 밝은 곳을 흰색으로
img = cv2.imread("ch13_shapes.png")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
_, binary = cv2.threshold(gray, 60, 255, cv2.THRESH_BINARY)
# 12차시에서 본 대로 바깥 윤곽선만 찾는다
contours, _ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
print(len(contours)) # 결과: 8 → 도형 여덟 개
# 넓이만으로는 원과 기운 네모가 잘 갈리지 않는다
areas = sorted(int(cv2.contourArea(c)) for c in contours)
print(areas) # 결과: [1230, 1272, 1280, 1344, 2074, 2212, 2558, 2704]넓이를 작은 것부터 늘어놓으면 1,230 부터 1,344 까지 네 개가 몰려 있고, 2,074 부터 2,704 까지 네 개가 몰려 있습니다. 넓이로는 "작은 무리, 큰 무리" 정도만 나뉩니다.
주의할 점
- 모양 숫자는 윤곽선이 잘 찾아졌을 때만 믿을 수 있습니다. 이진화가 잘못돼 물체 둘이 붙거나 하나가 쪼개지면 숫자도 엉뚱하게 나옵니다. 8장의 이진화와 9장의 모폴로지로 먼저 깨끗한 마스크를 만듭니다.
- 12장에서는 밝은 바탕의 어두운 부품을
cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU로 이진화했습니다. 이 장의 도형 판은 거꾸로 바탕이 어둡고(밝기 40) 도형이 밝으므로(105 이상) 뒤집지 않는cv2.THRESH_BINARY를 쓰고, 기준값도 그 사이의 60 으로 정했습니다. 실제 사진은 바탕이 고르지 않을 수 있습니다. 잡티가 없는 그림이라 12장의 "면적 100 이상만 남기기"도 생략했습니다.