사람얼굴을 검출해보자 (OpenCV)

사람은 각종 배경 여러 물건이 혼합 된 이미지 속에서 사람 얼굴을 쉽게 찾아낸다. 

주변에는 인공지능 까지는 아니더라도 사용자를 알아보는 사례가 많다. 인터넷 쇼핑 사이트에서 자신이 검색한 물건

위주로 정렬해 다시 접속시 보여주는 기능도 한가지 예가 될것이다. 컴퓨터가 얼굴을 인식하도록 하는 방법은 없을까? 

다행이도 OpenCV 라이브러리에서 SDK형태로 제공해 쉽게 테스트 해볼수 있다.

다음은 OpenCV에서 제공하는 얼굴 인식 코드 이다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#include <cv.h>
#include <highgui.h>
#include <iostream>
 
using namespace std;
using namespace cv;
 
String face_cascade = "C:/opencv/sources/data/haarcascades/haarcascade_frontalface_alt.xml";
String eye_cascade = "C:/opencv/sources/data/haarcascades/haarcascade_eye.xml";
String img_name = "face.jpg"// 파일 명
 
CascadeClassifier face;
CascadeClassifier eye;
 
int main()
{
    Mat img = imread(img_name);
 
    if (img.data == NULL) {
        cout << img_name << " 이미지 열기 실패" << endl;
        return -1;
    }
 
    if (!face.load(face_cascade) || !eye.load(eye_cascade)) {
        cout << "Cascade 파일 열기 실패" << endl;
        return -1;
    }
    
    #pragma region 얼굴 검출
    Mat gray;
    cvtColor(img, gray, CV_RGB2GRAY);
 
    vector<Rect> face_pos; // 얼굴 위치 저장
    face.detectMultiScale(gray, face_pos, 1.13| CV_HAAR_SCALE_IMAGE, Size(1010)); // 얼굴 검출
 
    // 얼굴 영역 표시
    for (int i = 0; i < (int)face_pos.size(); i++)    {
        rectangle(img, face_pos[i], Scalar(02550), 2);
    }
    #pragma endregion
 
 
    #pragma region 눈 검출
    for (int i = 0; i < (int)face_pos.size(); i++) {
        vector<Rect> eye_pos; // 눈 위치 저장
        
        Mat roi = gray(face_pos[i]); // 관심영역 설정
        eye.detectMultiScale(roi, eye_pos, 1.13| CV_HAAR_SCALE_IMAGE, Size(1010)); // 눈 검출
 
        // 눈 영역 표시
        for (int j = 0; j < (int)eye_pos.size(); j++) {
            Point center(face_pos[i].x + eye_pos[j].x + (eye_pos[j].width / 2), 
                       face_pos[i].y + eye_pos[j].y + (eye_pos[j].height / 2));
            
            int radius = cvRound((eye_pos[j].width + eye_pos[j].height) * 0.2);
            circle(img, center, radius, Scalar(00255), 2); 
        }
    }
    #pragma endregion
 
    namedWindow("검출");
    imshow("검출", img);
        
    waitKey();
    return 0;
}
cs


                                              [그림 1] 얼굴 검출


       [그림 2] 얼굴 검출 & 눈 검출


결과를 보면 얼굴과 눈 검출이 매우 잘됨을 확인할 수 있었다. 

소스를 보면 매우 간단한 구조이다. 이미지와 cascade파일을 불러오고 검출 함수를 통해 출력하는 구조이다.

함수 하나만으로 저렇게 잘 동작한다니 과연 저 함수는 어떻게 동작하는 것일까?


함수는 어떤 파일을 불러와 동작하는것을 확인할 수 있다. 불러온 파일에 따라 얼굴이 검출되기도 하고,

눈이 검출되기도 했다. 불러온 파일을 보니 haarcascade_xxx 가 붙어 있다. 뒤에 오는것에 따라 

얼굴과 눈이 검출됬다. 과연 haarcascade가 무엇일까? 네이버 사전을 검색해보면 cascade는 

작은폭포? 풍성하게 늘어진것 같은 뜻을 지닌다. 그럼 Haar는 무엇일까?

2001년 두 학자(Viola, Jones)가  "Rapid Object Detection using a Boosted Cascade of Simple Features" 

논문에서 제안한 방법이다. 이들은 유사 하르 특징(Haar like feature)이라는 것에 기반한  알고리즘으로 구성

되어 있었다. 

Haar 특징에 대해 인터넷에 검색해보면 아래와 같은 사진을 볼 수 있다.


                                      [그림 3] Haar like feature


저 사각형은 무엇인가? 뭔지 몰라서 한참을 들여다 봤다. 알고보니 사람의 얼굴에는 뭔가 특별한 패턴이 있는것을

알수 있다. 두 눈은 명암이 어둡고 코는 명암이 밝다. 이런 명암을 이용해 패턴을 구하는 것이다. 

이것들을 Haar like feature라고 한다. 사람의 얼굴 위에 흑백의 사각형을 겹쳐 놓은 다음 밝은 영역에 속한 픽셀

값들의 평균에서 어두운 영역에 속한 픽셀값들의 평균의 차이를 구한다. 그 차이가 문턱값(threshold)를 넘으면

사람 얼굴에 대한 유사 하르 특징이 있는것이었다. 사람의 얼굴은 다양하지만 생김새의 패턴은 비슷하므로 

임의의 얼굴 위에서의 특정 위치, 특정 분포에 따른 명암의 차이는 거의 없을것이라고 판단한 것이다. 


              [그림 4] haarcascade_frontalface.xml 일부


위 그림은 haarcascade_frontalface.xml 파일의 일부이다. 태그 <size> 20 20은 얼굴의 크기이고 그 안의 영역에

[그림 3]의 사각형이 돌아다니면서 명암의 평균 차를 측정하는 것이었다. [그림 4]의 rect값인 3 7 14 4 -1은 순서

대로 x좌표, y좌표, 폭(width), 높이(height), 이득(gain)이다. 그리고 마지막 값은 부호를 나타낸다. -1이면 픽셀값의

평균을 빼라는 뜻이고, 2는 2를 곱한후 더하란 뜻이된다. <threshold>는 임계값(문턱치)가 될것이다.