[OpenCV] 캐스케이드 분류기, 얼굴 검출

[OpenCV] 캐스케이드 분류기, 얼굴 검출

이번 글에서는 얼굴 검출에 대해 알아보겠습니다.

OpenCV에서는 얼굴 검출에 대한 몇가지 방법을 제공하며, 이번 글에서는 캐스케이드 분류기(cascade classifier)에 대해 알아보겠습니다.

우선, 캐스케이드 분류기에 대해 알아 보기전에 비올라-존스 알고리즘에 대해 알아보겠습니다.

비올라-존스 알고리즘

비올라-존스 알고리즘은 얼굴 검출에 사용되는 알고리즘입니다. 비올라-존스 알고리즘의 순서는 아래와 같습니다.

  1. 영상을 24×24 크기로 정규화를 합니다.
  2. 유사-하르 필터(Haar-like filter) 집합으로부터 특징 정보를 추출하여 얼굴 여부를 판단합니다.

유사-하르 필터는 흰색 사각형과 검은색 사각형이 붙어있는 형태로 구성된 필터입니다.

유사-하르 필터 예시

왜 이런 형태일까요? 유사-하르 필터에서 흰색 영역 픽셀값은 모두 더하고, 검은색 영역 픽셀값은 모두 빼서 하나의 특징 값을 구할 수 있습니다.

이러한 형태는 사람의 얼굴이 보통 밝은 영역과 어두운 영역이 정해져 있기 때문입니다.

이러한 방식으로 유사-하르 필터를 가지고 얼굴을 판별합니다. 하지만 24×24 크기에서는 유사-하르 필터를 약 18만개 만들 수 있고,  시간이 오래 걸리게 됩니다.

하지만 비올라와 존스는 이후에 에이다부스트(adaboost) 알고리즘과 적분 영상(integral image)을 이용하여 약 6천개의 유사-하르 필터를 선별하여 많은 개선을 했습니다… 만, 그래도 시간이 오래 걸리기는 마찬가지였습니다.

전체 영상에서 얼굴을 찾으려면 일부 영역을 잘라 24×24로 잘라야하고 연산을 해야하고, 얼굴 크기도 또한 다를 수 있으므로 크기도 다르게 잘라야하고…. 일이 너무 많아집니다.

캐스케이드(cascade)

그래서 캐스케이드 방식에서는 얼굴이 아닌 영역을 빠르게 걸러내는 방식을 사용합니다.

캐스케이드 구조 방식의 순서는 아래와 같습니다.

  1. 얼굴 검출에 가장 유용한 유사-하르 필터를 하나 사용하여 얼굴이 아닌지 판단합니다.
  2. 얼굴이 아니라고 판단하면 이후에는 검사하지 않습니다. 하지만 얼굴이라고 판단되면 다음 단계로 넘어갑니다.
  3. 얼굴이라고 판단된 영역에 다시 유사-하르 필터 다섯개를 사용하여 얼굴이 아닌지 판단합니다.
  4. 얼굴이 아니라고 판단하면 이후에는 검사하지 않습니다. 하지만 얼굴이라고 판단되면 다음 단계로 넘어갑니다.
  5. 반복

위 순서로 수행하면 얼굴이 아닌 영역을 빠르게 제거하게 됨으로써 얼굴인 영역만 남게됩니다.

XML 파일

코드를 만들어 보기전에 미리 훈련된 검출 분류기 XML 파일을 다운받아야합니다.

여기에서 2개 파일을 다운받아줍니다.(haarcascade_frontalface_default.xml, haarcascade_eye.xml)

코드

이제 코드로 가보겠습니다.

OpenCV에서는 검출 분류기를 이용하여 객체 검출이 가능한 CascadeClassifier 클래스를 제공합니다.
CascadeClassifier 클래스에서 XML파일을 불러오기 위해서 load 함수를 사용합니다.

/**
@param filename 파일 이름
*/
bool CascadeClassifier::load( const String& filename );

그리고 객체 검출하는 코드는 detectMultiScale 함수를 사용합니다.

/** 
@param image 입력 영상, CV_8U
@param objects 검출된 객체 사각형 좌표 정보
@param scaleFactor 검색 윈도우 확대 비율
@param minNeighbors 최소 검출 횟수
@param flags 사용 안함
@param minSize 검출할 객체 최소 크기
@param maxSize 검출할 객체 최대 크기
*/
void CascadeClassifier::detectMultiScale( InputArray image, CV_OUT std::vector<Rect>& objects, double scaleFactor = 1.1, int minNeighbors = 3, int flags = 0, Size minSize = Size(), Size maxSize = Size() );

전체 코드는 아래와 같습니다.

Mat src = imread("lenna.bmp");
if (src.empty())
{
	cerr << "image load fail" << endl;
	return -1;
}

// xml 로드
CascadeClassifier classifier_frontalface, classifier_eye;
classifier_frontalface.load("haarcascade_frontalface_default.xml"); // 얼굴
classifier_eye.load("haarcascade_eye.xml"); // 눈
if (classifier_frontalface.empty() || classifier_eye.empty())
{
	cerr << "xml load fail" << endl;
	return -1;
}

vector<Rect> faces;
classifier_frontalface.detectMultiScale(src, faces); // 얼굴 검출

for (Rect rectFace : faces)
{
	cout << "face:" << rectFace << endl;
	rectangle(src, rectFace, Scalar(0,0,255), 2);
	
	Mat faceROI = src(rectFace);
	vector<Rect> eyes;
	classifier_eye.detectMultiScale(faceROI, eyes); // 눈 검출

	for (Rect rectEye : eyes)
	{
		cout << "eye:" << rectEye << endl;
		rectangle(faceROI, rectEye, Scalar(0, 255, 0), 2);
	}
}

imshow("src", src);

waitKey();
destroyAllWindows();

출력 결과는 아래와 같습니다.

이상으로 캐스케이드 얼굴 검출에 대해 알아보았습니다.