이번 글에서는 얼굴 검출에 대해 알아보겠습니다.
OpenCV에서는 얼굴 검출에 대한 몇가지 방법을 제공하며, 이번 글에서는 캐스케이드 분류기(cascade classifier)에 대해 알아보겠습니다.
우선, 캐스케이드 분류기에 대해 알아 보기전에 비올라-존스 알고리즘에 대해 알아보겠습니다.
비올라-존스 알고리즘
비올라-존스 알고리즘은 얼굴 검출에 사용되는 알고리즘입니다. 비올라-존스 알고리즘의 순서는 아래와 같습니다.
- 영상을 24×24 크기로 정규화를 합니다.
- 유사-하르 필터(Haar-like filter) 집합으로부터 특징 정보를 추출하여 얼굴 여부를 판단합니다.
유사-하르 필터는 흰색 사각형과 검은색 사각형이 붙어있는 형태로 구성된 필터입니다.
왜 이런 형태일까요? 유사-하르 필터에서 흰색 영역 픽셀값은 모두 더하고, 검은색 영역 픽셀값은 모두 빼서 하나의 특징 값을 구할 수 있습니다.
이러한 형태는 사람의 얼굴이 보통 밝은 영역과 어두운 영역이 정해져 있기 때문입니다.
이러한 방식으로 유사-하르 필터를 가지고 얼굴을 판별합니다. 하지만 24×24 크기에서는 유사-하르 필터를 약 18만개 만들 수 있고, 시간이 오래 걸리게 됩니다.
하지만 비올라와 존스는 이후에 에이다부스트(adaboost) 알고리즘과 적분 영상(integral image)을 이용하여 약 6천개의 유사-하르 필터를 선별하여 많은 개선을 했습니다… 만, 그래도 시간이 오래 걸리기는 마찬가지였습니다.
전체 영상에서 얼굴을 찾으려면 일부 영역을 잘라 24×24로 잘라야하고 연산을 해야하고, 얼굴 크기도 또한 다를 수 있으므로 크기도 다르게 잘라야하고…. 일이 너무 많아집니다.
캐스케이드(cascade)
그래서 캐스케이드 방식에서는 얼굴이 아닌 영역을 빠르게 걸러내는 방식을 사용합니다.
캐스케이드 구조 방식의 순서는 아래와 같습니다.
- 얼굴 검출에 가장 유용한 유사-하르 필터를 하나 사용하여 얼굴이 아닌지 판단합니다.
- 얼굴이 아니라고 판단하면 이후에는 검사하지 않습니다. 하지만 얼굴이라고 판단되면 다음 단계로 넘어갑니다.
- 얼굴이라고 판단된 영역에 다시 유사-하르 필터 다섯개를 사용하여 얼굴이 아닌지 판단합니다.
- 얼굴이 아니라고 판단하면 이후에는 검사하지 않습니다. 하지만 얼굴이라고 판단되면 다음 단계로 넘어갑니다.
- 반복
위 순서로 수행하면 얼굴이 아닌 영역을 빠르게 제거하게 됨으로써 얼굴인 영역만 남게됩니다.
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();
출력 결과는 아래와 같습니다.
이상으로 캐스케이드 얼굴 검출에 대해 알아보았습니다.