[OpenCV] 외곽선 검출 – findContours

이번 글에서는 객체의 외곽선(contours)을 검출하는 방법에 대해 알아보겠습니다.

외곽선 검출은 레이블링과 더불어 영상에서 객체의 정보를 검출하는 방법 중 하나입니다.

개념

영상에서 객체의 외곽을 검출하는 방법에 대해 알아보겠습니다.
외곽선 검출은 이진화된 영상에서 검출이 이루어지며 배경 영역과 닿아 있는 픽셀을 찾아 외곽선으로 인식합니다.

검은색은 배경, 흰색이 객체일 때 하늘색 픽셀은 외곽선으로 인식합니다. 그리고 이 외곽선들을 배열로 반환해 줍니다.

// ex)
contours[0] = ((2,1), (3,1), (4,1), (4,2), (4,3), (4,4), (3,4), (2,4), (1,4), (1,3), (2, 2))
contours[1] = ((7,2), (8,2))
contours[2] = ((2,7), (3,7), (4,7), (5,6), (6,6), (6,7), (6,8), (5,8), (4,8), (3,8), (2,8))

외곽선의 계층

외곽선은 객체 외부 뿐만 아니라 내부에도 생기는 경우가 있습니다. 객체 내부가 비어 있을 경우가 그 경우 입니다.

위와 같은 방식이 계속 될 수도 있습니다.

OpenCV에서는 이러한 외곽선 계층(hierarchy) 구조에 대한 정보를 받아오는 함수를 제공해줍니다.

함수

OpenCV에서는 영상 내부 객체들의 외곽선을 검출하는 findContours()함수를 제공합니다.

/**
@param image 입력 영상
@param contours 검출된 외곽선 정보 (e.g. std::vector<std::vector<cv::Point> >)
@param hierarchy 외곽선 계층 정보
@param mode 외곽선 검출 모드
@param method 외곽선 근사화 방법
@param offset 외곽선 점 좌표의 오프셋
 */
void findContours( InputArray image, OutputArrayOfArrays contours, OutputArray hierarchy, int mode, int method, Point offset = Point());

void findContours( InputArray image, OutputArrayOfArrays contours, int mode, int method, Point offset = Point());

외곽선 검출 모드(mode) 인자에는 아래 RetrievalModes 열거형 상수가 있습니다.

RetrievalModesexp
RETR_EXTERNAL객체 외부 외곽선만 검출
RETR_LIST객체 외부, 내부 외곽선 모두 검출
RETR_CCOMP모든 외곽선 검출, 2단계 계층 구조 구성
RETR_TREE모든 외곽선 검출, 전체 계층 구조 구성

외곽선 근사화 방법(method) 인자에는 검출된 외곽선 좌표를 근사화하는 방법을 ContourApproximationModes 열거형 상수로 지정합니다.

ContourApproximationModesexp
CHAIN_APPROX_NONE모든 외곽선 좌표를 저장
CHAIN_APPROX_SIMPLE외곽선 중에서 수평, 수직, 대각선 성분은 끝 점만 저장
CHAIN_APPROX_TC89_L1Teh & Chin L1 근사화 적용
CHAIN_APPROX_TC89_KCOSTeh & Chin k cos 근사화 적용

검출된 외곽선을 영상 위에 그리려면 drawContours() 함수를 사용하면 됩니다.

/** 
@param image 입력 영상
@param contours findContours에서 얻은 전체 외곽선 정보
@param contourIdx 외곽선 번호
@param color 외곽선 색상
@param thickness 외곽선 두께, -1일 경우 외곽선 내부를 채움
@param lineType 외곽선 타입
@param hierarchy 외곽선 계층 정보
@param maxLevel 그릴 외곽선 최대 레벨
@param offset 추가적으로 지정할 외곽선 좌표 오프셋
 */
void drawContours( InputOutputArray image, InputArrayOfArrays contours, int contourIdx, const Scalar& color, int thickness = 1, int lineType = LINE_8, InputArray hierarchy = noArray(), int maxLevel = INT_MAX, Point offset = Point() );

샘플 코드 1

첫번째 샘플 코드는 아래와 같습니다.

Mat src = imread("findcontours1.png", IMREAD_GRAYSCALE);
if (src.empty())
{
	cerr << "image open error" << endl;
	return -1;
}


vector<vector<Point>> contours;
findContours(src, contours, RETR_LIST, CHAIN_APPROX_NONE); // 외곽선 좌표 얻기

Mat dst;
cvtColor(src, dst, COLOR_GRAY2BGR);

for (int iIndex = 0 ; iIndex < contours.size() ; iIndex++)
{
	Scalar scalar(rand() % 256, rand() % 256, rand() % 256);
	drawContours(dst, contours, iIndex, scalar, 4, LINE_AA); // 외곽선 그리기

}

imshow("src", src);
imshow("dst", dst);

waitKey();
destroyAllWindows();

실행 결과는 아래와 같습니다.

샘플 코드 2

두번째 샘플 코드는 아래와 같습니다.

Mat src = imread("findcontours2.png", IMREAD_GRAYSCALE);
if (src.empty())
{
	cerr << "image open error" << endl;
	return -1;
}


vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
findContours(src, contours, hierarchy, RETR_CCOMP, CHAIN_APPROX_NONE);

Mat dst;
cvtColor(src, dst, COLOR_GRAY2BGR);

for (int iIndex = 0 ; 0 <= iIndex; iIndex = hierarchy[iIndex][0])
{
	Scalar scalar(rand() % 256, rand() % 256, rand() % 256);
	drawContours(dst, contours, iIndex, scalar, -1, LINE_8, hierarchy);
}

imshow("src", src);
imshow("dst", dst);

waitKey();
destroyAllWindows();

실행 결과는 아래와 같습니다.

이상으로 외곽선 검출에 대해 알아보았습니다.