이 글에서는 OpenCV를 활용해 눈을 추적하는 방법을 알아봅니다. Haar Cascade 분류기로 눈을 먼저 검출했다면, 그 이후의 추적 작업은 생각보다 훨씬 간단합니다. 이 예제에서는 검출된 눈을 원(circle)으로 감싸서 표시하며, 원의 중심 좌표를 추적하는 것이 곧 눈의 중심을 추적하는 것과 같습니다.
눈 추적의 핵심 원리
원의 중심을 추적하려면 두 개의 정수형 변수가 필요합니다. 아래 코드에서는 main() 함수의 첫 두 줄(9번째, 10번째 줄)에서 'x_axis'와 'y_axis'라는 이름의 변수를 선언하여 이를 준비합니다.
코드의 42번째와 43번째 줄에서는 원의 중심에 해당하는 수평(x)·수직(y) 좌표 값을 각각 'x_axis'와 'y_axis' 변수에 복사합니다. 이어서 44번째 줄에서 'cout' 문을 사용해 중심 좌표 값을 콘솔에 출력함으로써, 눈의 위치를 실시간으로 확인하고 추적할 수 있습니다.
C++로 구현한 전체 예제 코드
다음은 C++와 OpenCV를 사용해 카메라 영상 속 눈의 위치를 추적하는 전체 코드입니다.
#include<iostream>
#include<opencv2/highgui/highgui.hpp>
#include<opencv2/imgproc/imgproc.hpp>
#include<opencv2/objdetect/objdetect.hpp>
using namespace cv;
using namespace std;
int main() {
int x_axis;//좌표 값을 저장할 정수형 변수 선언//
int y_axis;//좌표 값을 저장할 정수형 변수 선언//
Mat frame;//비디오 프레임을 담을 행렬 선언//
namedWindow("Detect");//작업 결과를 보여줄 윈도우 생성//
VideoCapture image(0);//기본 카메라로부터 비디오 캡처//
if (!image.isOpened()){ //비디오 소스를 찾지 못한 경우 오류 메시지 출력//
cout << "Couldn't load video from the source.Make sure your camera is working properly." << endl;
system("pause");
return 0;
}
double height = image.set(CAP_PROP_FRAME_HEIGHT, 480);//각 프레임의 높이 설정//
double width = image.set(CAP_PROP_FRAME_WIDTH, 640);//각 프레임의 너비 설정//
CascadeClassifier face_cascade, eyes_cascade;//CascadeClassifier 객체 선언//
face_cascade.load("C:/opencv/sources/data/haarcascades/haarcascade_frontalface_alt.xml");//얼굴 검출용 캐스케이드 분류기 로드//
eyes_cascade.load("C:/opencv/sources/data/haarcascades/haarcascade_eye.xml");//눈 검출용 캐스케이드 분류기 로드//
while (true) {
bool temp = image.read(frame);//카메라에서 읽어온 프레임을 frame 행렬에 저장//
std::vector<Rect>faces;//faces라는 이름의 벡터 선언//
face_cascade.detectMultiScale(frame, faces, 1.1, 2, 0 | CASCADE_SCALE_IMAGE, Size(100, 100));//영상에서 얼굴 검출//
for (int i = 0; i < faces.size(); i++){ //검출된 얼굴 위치 확인//
Point center(faces[i].x + faces[i].width * 0.5, faces[i].y + faces[i].height * 0.5);//얼굴의 중심 좌표 계산//
ellipse(frame, center, Size(faces[i].width * 0.5, faces[i].height * 0.5), 0, 0, 360, Scalar(255, 0, 255), 4, 8, 0);//얼굴 위에 타원 그리기//
Mat faceROI = frame(faces[i]);//눈 검출을 위해 얼굴 영역을 ROI(관심 영역)로 지정//
std::vector<Rect>eyes;//eyes라는 이름의 벡터 선언//
eyes_cascade.detectMultiScale(faceROI, eyes, 1.1, 2, 0 | CASCADE_SCALE_IMAGE, Size(5, 5));//각 얼굴 영역에서 눈 검출//
for (size_t j = 0; j < eyes.size(); j++){ //검출된 눈 위치 확인//
Point center(faces[i].x + eyes[j].x + eyes[j].width * 0.5, faces[i].y + eyes[j].y + eyes[j].height * 0.5);//양쪽 눈의 중심 좌표 계산//
int radius = cvRound((eyes[j].width + eyes[j].height) * 0.25);//눈을 감싸는 원의 반지름 계산//
circle(frame, center, radius, Scalar(255, 0, 0), 4, 8, 0);//양쪽 눈 주위에 원 그리기//
x_axis = eyes[j].x;//눈의 x축 좌표를 x_axis에 저장//
y_axis = eyes[j].y;//눈의 y축 좌표를 y_axis에 저장//
cout << "Position of the eyes is:" << "(" << x_axis << "," << y_axis << ")" << endl;//눈의 좌표 값 출력//
}
}
imshow("Detect", frame);//'Detect' 윈도우에 결과 표시//
if (waitKey(30) == 27){ //프레임당 대기 시간은 30밀리초, ESC 입력 시 종료//
break;
}
}
return 0;
}
실행 결과
코드를 실행하면 카메라 영상에서 얼굴은 자홍색(magenta) 타원으로 표시되고, 검출된 각 눈은 파란색 원으로 둘러싸입니다. 동시에 콘솔 창에는 아래와 같은 형식으로 눈의 좌표가 실시간으로 출력됩니다.
Position of the eyes is:(x,y)


정리
이 예제는 haarcascade_frontalface_alt.xml로 얼굴을 먼저 찾은 뒤, 해당 얼굴 영역을 ROI(관심 영역)로 지정하고 그 안에서 haarcascade_eye.xml로 눈을 검출하는 2단계 구조입니다. 이런 방식을 사용하면 배경에 있는 유사한 패턴으로 인한 오탐(false positive)을 줄이고 검출 정확도를 높일 수 있습니다. 마지막으로 waitKey(30)은 프레임당 30밀리초의 대기 시간을 두어 영상이 자연스럽게 재생되도록 하며, ESC 키(아스키 코드 27)를 누르면 루프가 종료되고 프로그램이 끝납니다.