행복한 하루

[도서 실습] Qt 5 and OpenCV 4 Computer Vision (얼굴 랜드마크 최종 결과물 – 안경, 콧수염, 쥐 코) 본문

Programming/Qt

[도서 실습] Qt 5 and OpenCV 4 Computer Vision (얼굴 랜드마크 최종 결과물 – 안경, 콧수염, 쥐 코)

변화의 물결 2022. 5. 20. 19:20

 

 

안녕하세요.

 

   이전 내용을 총정리하는 단계로 얼굴 특징점을 찾아 안경과 콧수염, 쥐 코를 선택하여 실시간 영상에 반영할 수 있도록 합니다. 선택하는 것은 체크박스를 생성하여 선택할 수 있도록 합니다.

 이전 파일에 오타 등이 있기 때문에 이번 첨부된 소스를 참고하시면 됩니다.


 1. 소스파일 수정

1) capture_thread.h

  - 어떤 특징점에 어떤 것을 표시할지 열거형의 타입을 생성합니다. 여기서 MASK_COUNT는 기능을 나타내는 것이 아니라 열거형의 개수를 확인하기 위한 마지막 카운트 값으로 사용

  - 체크상태를 업데이트할 수 있는 함수를 선언

  - 어떤 체크박스인지, 기능을 설정할지 여부를 인자로 가지는 함수를 선언

  - 체크박스의 각 상태를 비트 값으로 저장하는 형태로 하기 위해서 masks_flag를 선언하고 inline 함수로 현재 상태를 확인할 수 있는 함수를 구현 

public:
    enum MASK_TYPE{
        RECTANGLE = 0,
        LANDMARKS,
        GLASSES,
        MUSTACHE,
        MOUSE_NOSE,
        MASK_COUNT,
    };

	void updateMasksFlag(MASK_TYPE type, bool on_or_off);

private:
	uint8_t masks_flag;

private:
	bool isMaskOn(MASK_TYPE type) { return (masks_flag & (1<<type)) != 0; };

 

2) capture_thread.cpp

 - run() 함수에서 flag가 설정되었을 땐 얼굴인식 함수로 호출될 수 있도록 설정 

void CaptureThread::run() {
...
	//detectFaces(tmp_frame);
    if(masks_flag > 0) {
		detectFaces(tmp_frame);
	}

   - detectFaces 함수에서 어떤 기능을 선택한 것에 따라서 분류되도록 되어 있는데, 기존 소스 내용에서 추가하기가 조금 어려울 것 같아서 함수 내용을 전체를 가져왔습니다. 얼굴만 인식 필요한 경우(RECTANGLE)와 특징점 좌표가 필요한 경우로 나누어져 있습니다.     

void CaptureThread::detectFaces(cv::Mat &frame)
{
    vector<cv::Rect> faces;
    cv::Mat gray_frame;
    cv::cvtColor(frame, gray_frame, cv::COLOR_BGR2GRAY);
    classifier->detectMultiScale(gray_frame, faces, 1.3, 5);
    cv::Scalar color = cv::Scalar(0, 0, 255); // red

    // draw the circumscribe rectangles
    if (isMaskOn(RECTANGLE)) {
        for(size_t i = 0; i < faces.size(); i++) {
            cv::rectangle(frame, faces[i], color, 1);
        }
    }

    vector< vector<cv::Point2f> > shapes;
    if (mark_detector->fit(frame, faces, shapes)) {
        // draw facial land marks
        for (unsigned long i=0; i<faces.size(); i++) {
            if (isMaskOn(LANDMARKS)) {
                for(unsigned long k=0; k<shapes[i].size(); k++) {
                    //cv::circle(frame, shapes[i][k], 2, color, cv::FILLED);
                    QString index = QString("%1").arg(k);
                    cv::putText(frame, index.toStdString(), shapes[i][k], cv::FONT_HERSHEY_SIMPLEX, 0.4, color, 2);
                }
            }

            if (isMaskOn(GLASSES))
                drawGlasses(frame, shapes[i]);
            if (isMaskOn(MUSTACHE))
                drawMustache(frame, shapes[i]);
            if (isMaskOn(MOUSE_NOSE))
                drawMouseNose(frame, shapes[i]);
        }
    }
}

 

  - drawMustache 함수와 drawMouseNose 함수는 drawMustache 함수와 기능은 동일하나 좌표점과 계산이 조금 다른 것만 확인하면 됩니다.

  - updateMasksFlag함수에서는 선택한 기능을 스프트(shift)와 비트 연산을 통해 On/Off 상태를 저장합니다. 

void CaptureThread::drawMustache(cv::Mat &frame, vector<cv::Point2f> &marks)
{
    // resize
    cv::Mat ornament;
    double distance = cv::norm(marks[54] - marks[48]) * 1.5;
    cv::resize(mustache, ornament, cv::Size(0, 0), distance / mustache.cols, distance / mustache.cols, cv::INTER_NEAREST);

    // rotate
    double angle = -atan((marks[54].y - marks[48].y) / (marks[54].x - marks[48].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[33].x + marks[51].x) / 2, (marks[33].y + marks[51].y) / 2);
    cv::Rect rec(center.x - rotated.cols / 2, center.y - rotated.rows / 2, rotated.cols, rotated.rows);
    frame(rec) &= rotated;
}

void CaptureThread::drawMouseNose(cv::Mat &frame, vector<cv::Point2f> &marks)
{
    // resize
    cv::Mat ornament;
    double distance = cv::norm(marks[13] - marks[3]);
    cv::resize(mouse_nose, ornament, cv::Size(0, 0), distance / mouse_nose.cols, distance / mouse_nose.cols, cv::INTER_NEAREST);

    // rotate
    double angle = -atan((marks[16].y - marks[0].y) / (marks[16].x - marks[0].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 = marks[30];
    cv::Rect rec(center.x - rotated.cols / 2, center.y - rotated.rows / 2, rotated.cols, rotated.rows);
    frame(rec) &= rotated;
}

void CaptureThread::updateMasksFlag(MASK_TYPE type, bool on_or_off) {
    uint8_t bit = 1 << type;

    if(on_or_off) {
        masks_flag |= bit;
    } else {
        masks_flag &= ~bit;
    }
}

 

 

3) mainwindow.h

  - 체크 박스 생성을 위한 배열과 체크 동작에 대한 함수를 선언해줍니다. 

private slots:
         // ...
         void updateMasks(int status);
     // ...
     private:
         // ...
         QCheckBox *mask_checkboxes[CaptureThread::MASK_COUNT];

 

4)  mainwindow.cpp

  - 버튼 생성 밑 부분에 하나의 레이아웃을 생성하여 체크박스를 동적으로 생성하고 slot 함수와 연결해줍니다. 인덱스 번호로 어떤 체크박스가 눌렸는지 구분합니다.     

    shutterButton = new QPushButton(this);
    shutterButton->setText("Take a Photo");
    tools_layout->addWidget(shutterButton, 0, 0, Qt::AlignHCenter);
    //tools_layout->addWidget(new QLabel(this), 0, 2);

    // masks
    QGridLayout *masks_layout = new QGridLayout();
    main_layout->addLayout(masks_layout, 13, 0, 1, 1);
    masks_layout->addWidget(new QLabel("Select Masks:", this));

	for (int i = 0; i < CaptureThread::MASK_COUNT; i++){
        mask_checkboxes[i] = new QCheckBox(this);
        masks_layout->addWidget(mask_checkboxes[i], 0, i + 1);
        connect(mask_checkboxes[i], SIGNAL(stateChanged(int)), this, SLOT(updateMasks(int)));
    }
    mask_checkboxes[0]->setText("Rectangle");
    mask_checkboxes[1]->setText("Landmarks");
    mask_checkboxes[2]->setText("Glasses");
    mask_checkboxes[3]->setText("Mustache");
    mask_checkboxes[4]->setText("Mouse Nose");

  

  - 시그널 발생한 오브젝트(체크박스)를 찾아서 선택한 체크박스에 맞는 마스크 타입으로 변환하여, on/off 상태를 인자로 해서 updateMasksFlag 함수를 호출합니다. 

void MainWindow::updateMasks(int status)
{
	if(capturer == nullptr) {
		return;
	}

 	QCheckBox *box = qobject_cast<QCheckBox*>(sender());

	for (int i = 0; i < CaptureThread::MASK_COUNT; i++){
		if (mask_checkboxes[i] == box) {
			capturer->updateMasksFlag(static_cast<CaptureThread::MASK_TYPE>(i), status != 0);
		}
	}
}

 

  - openCamera()에서 체크상태를 초기화시켜 줍니다.     

     for (int i = 0; i < CaptureThread::MASK_COUNT; i++){
         mask_checkboxes[i]->setCheckState(Qt::Unchecked);
     }

 

3. 실행 결과

  - GLASSES, MUSTACHE, MOUSE NOSE를 선택한 후 웹캠에 출력한 얼굴 사진을 가까이 가져가면

합성된 영상으로 출력되는 것을 확인할 수 있습니다.

 4. 마무리

  - 위에 나온 사진은 가상 인물의 얼굴이며, 프린터 해서 웹캠에 비추어 보았습니다.

  - 위의 소스는 아직 좌표에 대한 오류도 있기 때문에 조금 더 다듬어서 사용하시는 것을 추천드립니다.

  - QtOpenCV를 가지고 Frame에 이렇게 하면 얼굴 포인트를 찾고 그 위에 무엇을 표시할 수 있구나 참고할 수 있는 내용이었습니다.

  - 인지 능력을 높이기 위해서는 OpenCV와 별도로 학습 데이터가 필요했습니다. 그래서 나중에 다른 학습된 데이터를 이용한다면 특정 물체를 인지하고 덧붙이는 영상처리도 가능할 것이라 보입니다.

 

 

감사합니다.

 

Facetious_day5.zip
0.14MB

Comments