안녕하세요.
이전 내용을 총정리하는 단계로 얼굴 특징점을 찾아 안경과 콧수염, 쥐 코를 선택하여 실시간 영상에 반영할 수 있도록 합니다. 선택하는 것은 체크박스를 생성하여 선택할 수 있도록 합니다.
이전 파일에 오타 등이 있기 때문에 이번 첨부된 소스를 참고하시면 됩니다.
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. 마무리
- 위에 나온 사진은 가상 인물의 얼굴이며, 프린터 해서 웹캠에 비추어 보았습니다.
- 위의 소스는 아직 좌표에 대한 오류도 있기 때문에 조금 더 다듬어서 사용하시는 것을 추천드립니다.
- Qt와 OpenCV를 가지고 Frame에 이렇게 하면 얼굴 포인트를 찾고 그 위에 무엇을 표시할 수 있구나 참고할 수 있는 내용이었습니다.
- 인지 능력을 높이기 위해서는 OpenCV와 별도로 학습 데이터가 필요했습니다. 그래서 나중에 다른 학습된 데이터를 이용한다면 특정 물체를 인지하고 덧붙이는 영상처리도 가능할 것이라 보입니다.
감사합니다.