Programming/Qt

[도서 실습]얼굴 랜드마크(눈)에 안경 그리기)

변화의 물결1 2024. 5. 16. 00:03

 

 

 

안녕하세요.

 

  이전 내용에서 얼굴과 얼굴의 특징점(눈, 코, 입 등)을 비디오상에서 감지하는 코드를 작성하였습니다. 이 특징점에 장신구(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 (어파인 변환)

https://velog.io/@codren/%EC%98%81%EC%83%81-%ED%8C%8C%EC%9D%BC%EC%9D%98-%EA%B8%B0%ED%95%98%ED%95%99%EC%A0%81-%EB%B3%80%ED%99%98     

 

 

Facetious_day4.zip
0.14MB

반응형