안녕하세요. 코딩산책입니다.
컴퓨터 비전 분야에서 외곽선 검출과 레이블링은 이미지 분석과 객체 인식에 있어 핵심적인 기술입니다. 이 두 가지 기술을 사용하면 이미지에서 객체의 경계와 형태를 추출하고, 각 객체를 구분하여 분석할 수 있습니다. 특히, OpenCV와 Python을 활용한 외곽선 검출은 다양한 산업과 연구 분야에서 폭넓게 사용되고 있으며, 이를 통해 자동화된 시스템이 보다 정확하고 효율적으로 동작할 수 있습니다.
이 글에서는 cv2.findContours, cv2.approxPolyDP, cv2.convexHull, cv2.moments 등의 OpenCV 함수들을 통해 외곽선 검출과 레이블링 기법을 자세히 설명합니다. 또한, 실제 활용 사례와 이러한 기법의 한계 및 개선점을 다루어, 외곽선 검출 기술의 실질적인 응용 방안을 제시하고자 합니다. 컴퓨터 비전 초보자부터 전문가까지, 이 글을 통해 외곽선 검출의 기초부터 고급 활용까지 폭넓은 정보를 얻을 수 있습니다.
1. 기초 개념: OpenCV 외곽선 검출
외곽선(Contour)은 이미지에서 객체의 경계를 정의하는 곡선입니다. 외곽선은 동일한 색이나 강도의 연속적인 픽셀로 구성되어 있기 때문에, 객체의 윤곽을 찾는 데 유용합니다. 외곽선을 잘 찾으면 물체의 모양, 크기, 위치를 파악할 수 있습니다. 이를 통해 이미지 분석과 물체 인식 등의 컴퓨터 비전 작업에서 외곽선은 중요한 역할을 합니다.
외곽선 검출의 기본 개념
외곽선은 이진화된 이미지에서 탐지됩니다. 그 이유는 외곽선 검출이 주로 흑백 이미지에서 이루어지기 때문입니다. 이진화는 이미지를 흑과 백으로 변환하는 과정이며, 이는 외곽선 검출에서 중요한 전처리 과정입니다.
외곽선 검출의 주요 과정
- 이미지 이진화: 외곽선 검출 전에 이미지의 배경을 제거하고 대상 객체를 분리하기 위해 이진화(Thresholding) 또는 에지 검출을 사용합니다.
- 외곽선 탐색: 이진화된 이미지에서 객체의 윤곽을 탐지하는 과정입니다. 이때 외곽선은 계층적 구조를 가질 수 있으며, 객체의 내부 외곽선과 외부 외곽선으로 나뉩니다.
- 외곽선 특징 분석: 검출된 외곽선으로부터 면적, 중심, 길이 등의 특징을 추출할 수 있습니다. 이는 객체 인식이나 분석에서 활용됩니다.
외곽선 검출의 활용
외곽선 검출은 이미지 분할, 객체 인식, 형태 분석, 움직임 추적 등 다양한 컴퓨터 비전 작업에서 사용됩니다. 예를 들어, 물체의 외형을 기반으로 물체를 식별하거나, 이미지 속에서 특정 영역을 추출할 수 있습니다.
2. 외곽선 찾기: cv2.findContours
cv2.findContours() 함수는 OpenCV에서 외곽선을 검출하는 데 사용됩니다. 이 함수는 이진화된 이미지에서 외곽선을 찾아 계층 구조를 반환합니다. 이를 통해 객체의 외부 경계뿐 아니라, 객체 내부의 경계도 찾을 수 있습니다.
cv2.findContours의 함수 구조
contours, hierarchy = cv2.findContours(image, mode, method)
- image: 외곽선을 검출할 이진화된 이미지. 이미지를 사전에 cv2.threshold() 또는 cv2.Canny() 등을 사용해 이진화해야 합니다.
- mode: 외곽선 검색 방법을 설정합니다. 주요 옵션은 다음과 같습니다:
- cv2.RETR_EXTERNAL: 가장 바깥쪽 외곽선만 검출합니다.
- cv2.RETR_LIST: 모든 외곽선을 계층 관계 없이 검출합니다.
- cv2.RETR_CCOMP: 모든 외곽선을 2단계 계층 구조로 검출합니다.
- cv2.RETR_TREE: 모든 외곽선을 계층 구조로 검출합니다.
- method: 외곽선 근사화 방법을 설정합니다. 주요 옵션은 다음과 같습니다:
- cv2.CHAIN_APPROX_NONE: 모든 외곽선 점을 저장합니다.
- cv2.CHAIN_APPROX_SIMPLE: 외곽선의 꼭짓점만 저장하여 메모리를 절약합니다.
- cv2.CHAIN_APPROX_TC89_L1: Teh & Chin L1 거리 기반으로 외곽선 점을 근사화하여 저장합니다.
- cv2.CHAIN_APPROX_TC89_KCOS: Teh & Chin K-Cosine 거리 기반으로 외곽선 점을 근사화하여 저장합니다.
외곽선 계층 구조 (Hierarchy)
cv2.findContours() 함수는 외곽선과 함께 계층 구조(hierarchy)를 반환합니다. 계층 구조는 객체의 내부 외곽선과 외부 외곽선을 구분하는 데 유용합니다. 예를 들어, 내부에 구멍이 있는 도넛 모양의 객체는 두 개의 외곽선을 가지며, 하나는 바깥쪽 경계, 다른 하나는 안쪽 구멍의 경계입니다.
- 계층 구조의 4가지 값
- [Next, Previous, First_Child, Parent]
- Next: 같은 계층의 다음 외곽선
- Previous: 같은 계층의 이전 외곽선
- First_Child: 하위 계층의 첫 번째 외곽선
- Parent: 상위 계층의 외곽선
외곽선 근사화 (Contour Approximation)
cv2.findContours()는 기본적으로 객체의 경계선을 따라 모든 좌표를 저장합니다. 하지만 복잡한 외곽선은 메모리 사용량이 커질 수 있습니다. 따라서 CHAIN_APPROX_SIMPLE 방법을 사용하여 외곽선의 꼭짓점만 저장함으로써 메모리를 절약할 수 있습니다.
외곽선 검출의 주요 옵션 비교
- cv2.RETR_EXTERNAL은 가장 바깥쪽 외곽선만 검출하는데, 이는 물체 경계만 필요할 때 유용합니다.
- cv2.RETR_TREE는 계층적 구조로 모든 외곽선을 반환하여 객체 내부의 구조까지 파악하는 데 적합합니다.
예제 코드
import cv2
# Reading and binarizing images
image = cv2.imread('resources/contour.jpg')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
_, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
# Find contours
contours, hierarchy = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
# Draw contours
cv2.drawContours(image, contours, -1, (0, 255, 0), 2)
cv2.imshow('Contours', image)
cv2.waitKey(0)
cv2.destroyAllWindows()
3. 외곽선 단순화: cv2.approxPolyDP
cv2.approxPolyDP() 함수는 외곽선을 단순화하는 데 사용됩니다. 이는 객체의 외곽선을 보다 적은 점으로 근사하는 방법으로, 복잡한 외곽선을 간소화하는 데 유용합니다. 이 함수는 특히 형태 분석이나 객체의 다각형 근사를 통해 물체의 간략화된 모양을 파악할 때 유용합니다.
cv2.approxPolyDP의 함수 구조
approx = cv2.approxPolyDP(curve, epsilon, closed)
- curve: 근사할 외곽선(곡선). 주로 cv2.findContours()로 찾은 외곽선 리스트에서 특정 외곽선을 전달합니다.
- epsilon: 외곽선을 얼마나 근사할지 결정하는 매개변수로, 외곽선 길이에 대한 최대 오차를 설정합니다. 값이 작을수록 더 정확하게 근사하고, 값이 크면 단순화가 더 커집니다. 일반적으로 epsilon은 외곽선 길이의 비율로 설정합니다 (예: 0.02 * arcLength).
- closed: True일 경우 닫힌 외곽선, False일 경우 열린 외곽선으로 처리합니다.
근사 다각형 알고리즘 (Douglas-Peucker Algorithm)
cv2.approxPolyDP() 함수는 Douglas-Peucker Algorithm을 사용합니다. 이 알고리즘은 곡선을 적은 수의 점으로 근사하여 곡선이나 다각형을 단순화하는 방법입니다. 이 과정에서 원래 곡선과 근사된 다각형 사이의 거리가 epsilon을 넘지 않도록 설정합니다.
epsilon 값 선택의 중요성
epsilon 값은 다각형 근사의 정확도를 결정합니다. 값이 너무 작으면 원래 외곽선과 거의 차이가 없고, 너무 크면 외곽선이 지나치게 단순화되어 물체의 형상이 왜곡될 수 있습니다. 일반적으로 전체 외곽선 길이의 2% 정도를 epsilon 값으로 설정하는 것이 적절합니다.
적용 사례
- 모양 분석: 외곽선을 단순화하면 객체의 모양을 파악하기 더 쉬워집니다. 예를 들어, 정사각형 또는 삼각형처럼 다각형 근사를 통해 물체의 기본적인 형상을 파악할 수 있습니다.
- 물체 분류: 근사 다각형을 통해 물체의 모양을 분석하고, 규칙적인 형상인지 불규칙적인 형상인지 구별할 수 있습니다.
근사 다각형의 실제 응용
cv2.approxPolyDP()는 물체의 기하학적 특징을 분석하거나 모양 인식을 위한 사전 단계로 사용됩니다. 예를 들어, 사각형이나 삼각형과 같은 기하학적 모양을 감지할 때 이 함수가 유용합니다.
예제 코드
import cv2
# Reading and binarizing images
image = cv2.imread('resources/approxpolydp.jpg')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
_, binary = cv2.threshold(image, 127, 255, cv2.THRESH_BINARY)
# Find contours
contours, _ = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
# Select one of the outlines and apply an approximate polygon
for contour in contours:
epsilon = 0.01 * cv2.arcLength(contour, True)
approx = cv2.approxPolyDP(contour, epsilon, True)
# Draw approximate contours
cv2.drawContours(image, [approx], -1, (0, 0, 255), 2)
cv2.imshow('Approx PolyDP', image)
cv2.waitKey(0)
cv2.destroyAllWindows()
4. 볼록 껍질(Convex Hull) 검출: cv2.convexHull
볼록 껍질(Convex Hull)은 주어진 점 집합을 감싸는 가장 작은 볼록 다각형을 의미합니다. 이를 통해 객체의 외곽선을 분석할 때, 객체가 볼록한지 여부를 판단할 수 있습니다. OpenCV에서 cv2.convexHull() 함수를 사용하여 객체의 볼록 껍질을 검출할 수 있습니다.
cv2.convexHull의 함수 구조
hull = cv2.convexHull(points[, hull[, clockwise[, returnPoints]]])
- points: 외곽선을 이루는 점들의 집합, 주로 cv2.findContours()로 얻은 외곽선 데이터를 전달합니다.
- hull: 볼록 껍질을 저장할 변수입니다.
- clockwise: True일 경우 시계 방향으로 볼록 껍질을 생성합니다. 기본값은 False입니다.
- returnPoints: True일 경우 점 좌표를 반환하고, False일 경우 점의 인덱스를 반환합니다.
볼록 껍질과 외곽선 검출의 차이점
- 외곽선(Contour): 객체의 모든 경계를 따라 그려진 선입니다.
- 볼록 껍질(Convex Hull): 객체를 감싸는 가장 작은 볼록 다각형으로, 외곽선의 돌출된 부분만을 감쌉니다.
Convex Hull의 활용 사례
- 물체 형태 분석: 객체의 볼록성을 판단하여 물체가 얼마나 복잡한 형태를 가지는지 분석할 수 있습니다. 볼록 껍질을 활용해 물체의 기하학적 특성을 파악할 수 있습니다.
- 충돌 감지: 2D 물체 간의 충돌 감지나 경계를 구할 때 사용되며, 게임 개발이나 로봇 공학에서도 많이 활용됩니다.
Convexity Defects (볼록성 결함)
Convex Hull을 구한 후에 외곽선이 볼록하지 않은 부분, 즉 볼록 껍질에서 떨어진 부분을 Convexity Defects라 합니다. 이를 검출하면 물체의 울퉁불퉁한 부분을 분석할 수 있습니다.
예제 코드
import cv2
# Reading and binarizing images
image = cv2.imread('resources/approxpolydp.jpg')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
_, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
# Find contours
contours, _ = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
# Calculating and Drawing the Convex Hull
for contour in contours:
hull = cv2.convexHull(contour)
cv2.drawContours(image, [hull], -1, (0, 0, 255), 2)
cv2.imshow('Convex Hull', image)
cv2.waitKey(0)
cv2.destroyAllWindows()
5. 외곽선 중심 및 면적 계산: cv2.moments
cv2.moments() 함수는 외곽선의 모멘트를 계산하여, 객체의 중심, 면적, 기울기 등의 특성을 추출할 수 있습니다. 모멘트는 객체의 기하학적 특성을 나타내는 값들로, 이를 통해 객체의 중심점을 찾거나 회전 등을 분석할 수 있습니다.
모멘트란?
모멘트(moment)는 이미지의 특정 좌표에 대한 값들을 종합하여 객체의 위치, 크기, 모양을 수치로 표현한 것입니다. 모멘트는 크게 공간 모멘트(spatial moments), 중심 모멘트(central moments), 그리고 정규화된 중심 모멘트(normalized central moments)로 구분됩니다. 이러한 모멘트를 통해 객체의 면적, 무게 중심, 회전 정도를 계산할 수 있습니다.
cv2.moments 함수 구조
M = cv2.moments(contour)
- contour: cv2.findContours()로 얻은 외곽선입니다.
- 반환되는 M은 모멘트를 나타내는 딕셔너리로, 다양한 모멘트 값들을 포함합니다.
주요 모멘트 값
- M["m00"]: 면적을 나타내며, 외곽선 내부 픽셀들의 합을 나타냅니다. 2D 외곽선의 면적입니다.
- M["m10"] 및 M["m01"]: 외곽선의 무게 중심을 계산할 때 사용되는 값들입니다.
- M["mu20"], M["mu02"], M["mu11"]: 중심 모멘트로, 객체의 방향성을 계산할 때 사용됩니다.
무게 중심(Centroid) 계산
무게 중심은 외곽선의 중심 좌표를 나타내며, 모멘트를 사용해 다음과 같이 계산할 수 있습니다.
\[C_x = \frac{M[m10]}{M[m00]}, \quad C_y = \frac{M[m01]}{M[m00]}\]
여기서 \(C_x\) 와 \(C_y\) 는 각각 무게 중심의 x 좌표와 y 좌표를 나타냅니다.
객체의 면적과 무게 중심 분석
위 코드는 외곽선의 면적과 무게 중심을 계산하여 이미지에 표시하는 예제입니다. 모멘트를 사용하면 객체의 중심 좌표와 면적을 손쉽게 계산할 수 있습니다.
응용 사례
- 중심 좌표를 기반으로 물체 위치 추적: 객체가 이미지 내에서 움직이는 동안 중심 좌표를 추적하여 위치 변화를 감지할 수 있습니다.
- 물체의 회전 및 방향성 분석: 모멘트를 이용해 물체의 회전 정도와 방향성을 파악할 수 있습니다. 이를 통해 물체의 회전 방향을 추적하거나 정렬할 수 있습니다.
기울기 계산
모멘트를 통해 객체의 방향을 분석할 수 있는데, 중심 모멘트들을 활용해 객체의 방향성을 나타내는 기울기(angle of orientation)를 계산할 수 있습니다. 이는 객체가 회전된 각도를 나타냅니다.
\[\theta = \frac{1}{2} \arctan\left(\frac{2 \cdot M[mu11]}{M[mu20] - M[mu02]}\right)\]
이 계산을 통해 물체가 어느 방향으로 기울어져 있는지를 파악할 수 있습니다.
예제 코드: 면적 및 중심점 계산
import cv2
# Reading and binarizing images
image = cv2.imread('resources/approxpolydp.jpg')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
_, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
# Find contours
contours, _ = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
# Calculating moments for the first contour
for contour in contours:
M = cv2.moments(contour)
# Calculating area
area = M["m00"]
# Calculating center of gravity
if M["m00"] != 0:
cx = int(M["m10"] / M["m00"])
cy = int(M["m01"] / M["m00"])
else:
cx, cy = 0, 0
# Drawing contours and center of gravity
cv2.drawContours(image, [contour], -1, (0, 255, 0), 2)
cv2.circle(image, (cx, cy), 5, (0, 0, 255), -1)
cv2.imshow('Contours and Centroid', image)
cv2.waitKey(0)
cv2.destroyAllWindows()
내용이 너무 길어져서 다음 포스트로 넘기도록 하겠습니다.
해당 포스트가 유용하셨다면 하단의 좋아요와 구독하기 부탁드립니다. ^^
★ 모든 내용은 아래의 링크를 참조하였습니다. ★
'강의 (Lecture) > OpenCV 마스터 with Python (초급)' 카테고리의 다른 글
OpenCV + Python 외곽선 검출과 레이블링(labeling) - 2 (2) | 2024.10.30 |
---|---|
OpenCV + Python 모폴로지 (Morphology) (0) | 2024.10.25 |
OpenCV + Python 이진화 (임계처리) (5) | 2024.10.22 |
OpenCV + Python 엣지(Edge) 검출과 허프(Hough) 변환 (2) | 2024.10.21 |
OpenCV + Python 필터와 컨볼루션 (0) | 2024.10.14 |