안녕하세요.
이전 내용에서 얼굴과 얼굴의 특징점(눈, 코, 입 등)을 비디오상에서 감지하는 코드를 작성하였습니다. 이 특징점에 장신구(Ornaments)를 적용(그리는 것)시키는 것을 해보겠습니다. 이 책에서 제공해 주는 장신구는 안경과 콧수염, 쥐의 수염 이미지를 제공해주고 있습니다.
이 이미지를 그냥 불러서 사용해도 되겠지만, Qt에서 관리적인 차원에서 사용하려면 리소스 파일을 만들어서 사용할 수 있습니다. 그래서 코딩하기 전에 간단한 리소스 파일을 만들어야 합니다. 그리고 리소스 파일을 불러와서 특징점 좌표에 회전된 값을 구하여 그려주는 작업을 하면 됩니다.
1. 리소스 파일 만들기
1.1 image.qrc 파일 만들기
- 우선 FacetiousW 소스가 들어 있는 폴더에 Images 폴더를 만들고 장신구 이미지들을 복사합니다.
- Qt에서 리소스 파일을 만들려면 FacetiousW 프로젝트에서 오른쪽 버튼을 눌러 "Add New..." 선택합니다.
그리고 템플릿에서 "Qt -> Qt Resource File"을 선택합니다. 그리고 이름을 images라고 저장 경로를 지정해 줍니다. 마지막으로 포함될 프로젝트명을 FacetiousW 선택합니다.
- 프로젝트 창에 Resources 폴더와 images.qrc 파일이 만들어집니다. images.qrc에서 오른쪽 버튼을 눌러 "Add Existing Directory"를 선택합니다. 그리고 images를 선택합니다.
- images 밑에 첨부된 파일을 볼 수 있습니다. 그리고 실제적으로 qrc 파일에는 xml 형식으로 구성되어 있습니다.
- QtCreator를 통해 위와 같이 추가했다면 .pro 파일에 images.qrc가 추가되어 있습니다. 없다면 추가해야 합니다.
RESOURCES += \
images.qrc
1.2. capture_thread.h 수정
- 장신구(ornaments) 이미지를 메모리에 저장하는 함수와 Mat 타입의 변수를 선언해 줍니다.
// ...
private:
// ...
void loadOrnaments();
// ...
private:
// ...
// mask ornaments
cv::Mat glasses;
cv::Mat mustache;
cv::Mat mouse_nose;
// ...
1.3 capture_thread.cpp 수정
- QImage 클래스를 불러올 수 없다고 나오면 상단에 #include <QImage> 추가해 줍니다.
- 이미지 경로를 qrc 리소스 시스템에서 불러오는 경로로 지정합니다. qrc:///images/glasses.jpg 불러와도 된다고 합니다.
- 데이터 타입을 CV_8UC3을 사용하여 cv::Mat 인스턴스를 구성할 수 있도록 3-channel 및 8-depth 형식으로 변환합니다. 방금 생성한 Mat 인스턴스의 전체 복사본을 만들기 위해 clone 메서드를 호출합니다.
void CaptureThread::loadOrnaments()
{
QImage image;
image.load(":/images/glasses.jpg");
image = image.convertToFormat(QImage::Format_RGB888);
glasses = cv::Mat(
image.height(), image.width(), CV_8UC3,
image.bits(), image.bytesPerLine()).clone();
image.load(":/images/mustache.jpg");
image = image.convertToFormat(QImage::Format_RGB888);
mustache = cv::Mat(
image.height(), image.width(), CV_8UC3,
image.bits(), image.bytesPerLine()).clone();
image.load(":/images/mouse-nose.jpg");
image = image.convertToFormat(QImage::Format_RGB888);
mouse_nose = cv::Mat(
image.height(), image.width(), CV_8UC3,
image.bits(), image.bytesPerLine()).clone();
}
2. 안경 그리기
- 얼굴을 감지 후에 얼굴 사이즈에 맞게 안경의 사이즈를 조절하고 얼굴 기울기에 맞게 회전 후 눈 위치를 파악 후 그립니다.
2.1 capture_thread.h 수정
- 그리는 함수 선언을 해줍니다. 첫 번째 인자는 안경을 그릴 화면(frame)이고 두 번째 인자는 프레임에서 감지한 얼굴의 특징점 위치입니다.
private:
// ...
void drawGlasses(cv::Mat &frame, vector<cv::Point2f> &marks);
2.2 capture_thread.cpp 수정
- 좌표[45]가 왼쪽 눈의 가장 바깥쪽 지점이고 좌표[36]가 오른쪽 눈의 가장 바깥쪽 지점이라는 이전 내용에서 확인할 수 있었습니다. 보통 안경의 너비는 이 두 점 사이의 거리보다 약간 더 크므로 좌표 간 거리에 1.5를 안경의 적정 너비로 곱한 다음 안경 장식 이미지를 적절한 크기로 조정합니다. 이미지 크기를 조정할 때 너비와 높이가 동일한 비율로 조정됩니다.
- 삼각함수의 공식에서 처럼 얼굴의 기울어진 각도를 구하기 위해서 삼각함수 공식을 적용해서 sin값(수직), cos(수평) 값을 tan 함수에 전달해서 얼굴의 기울기를 구합니다. 그 결과 각도는 라디안으로 표시되므로 OpenCV를 사용하여 회전할 때 각도로 변환합니다. getRotationMatrix2D 함수를 통해서 중앙을 기준점으로 결괏값을 2x3 어파인 변환 행렬을 값을 받아서 받아옵니다. wrapAffine함수로 회전된 이미지를 이동시켜 줍니다.
- 안경을 그리기 위해서 양 끝 지점의 중심점의 좌표를 구합니다. 그리고 회전된 안경을 () 연산자와 &연산으로 원본 얼굴 영상에 그립니다.
void CaptureThread::drawGlasses(cv::Mat &frame, vector<cv::Point2f> &marks)
{
// resize
cv::Mat ornament;
double distance = cv::norm(marks[45] - marks[36]) * 1.5;
cv::resize(glasses, ornament, cv::Size(0, 0), distance / glasses.cols, distance / glasses.cols, cv::INTER_NEAREST);
// rotate
double angle = -atan((marks[45].y - marks[36].y) / (marks[45].x - marks[36].x));
cv::Point2f center = cv::Point(ornament.cols/2, ornament.rows/2);
cv::Mat rotateMatrix = cv::getRotationMatrix2D(center, angle * 180 / 3.14, 1.0);
cv::Mat rotated;
cv::warpAffine(
ornament, rotated, rotateMatrix, ornament.size(),
cv::INTER_LINEAR, cv::BORDER_CONSTANT, cv::Scalar(255, 255, 255));
// paint
center = cv::Point((marks[45].x + marks[36].x) / 2, (marks[45].y + marks[36].y) / 2);
cv::Rect rec(center.x - rotated.cols / 2, center.y - rotated.rows / 2, rotated.cols, rotated.rows);
frame(rec) &= rotated;
}
- 특징점을 알아낸 후 얼굴을 수만큼 루프를 도는 함수에 drawGlasses() 함수를 추가해 줍니다. 이전에 숫자 표시 혹은 점을 그리는 코드는 주석 처리합니다.
// ...
for (unsigned long i=0; i<faces.size(); i++) {
for(unsigned long k=0; k<shapes[i].size(); k++) {
// cv::circle(frame, shapes[i][k], 2, color, cv::FILLED);
}
drawGlasses(frame, shapes[i]);
}
// ...
3. 실행결과
- 사진이 약간 기울어져 있어도 기울어진 각도에 맞게 안경이 그려진 것을 확인할 수 있습니다.
감사합니다.
<참고 사이트>
1. 영상의 회전 - cv2.getRotationMatrix2D
https://deep-learning-study.tistory.com/199
2. 기하학적 변환 1 (어파인 변환)