LoginSignup
10
7

More than 5 years have passed since last update.

SNOWのような顔交換を実装してみる。

Posted at

概要

前回顔を検出してアフィン変換までしましたが、今回は変換後の座標に別の顔の座標にマッピングしてみます。
SNOWというアプリでも有名な顔の交換です。
題材としてブラピさんを使わせてもらいます。

結果

スクリーンショット 2017-01-18 22.38.01.png

ある程度角度による変化に対応出来ていますが、違和感は否めません。。

ソースコード


#include <dlib/opencv.h>
#include <opencv2/highgui/highgui.hpp>
#include <dlib/image_processing/frontal_face_detector.h>
#include <dlib/image_processing/render_face_detections.h>
#include <dlib/image_processing.h>
#include <dlib/gui_widgets.h>
#include <opencv2/imgproc.hpp>
#include <dlib/image_io.h>

using namespace dlib;
using namespace std;
const int WIDTH = 320;
const int HEIGHT = 240;

cv::Point2f get_dist_point(cv::Point2f srcPoint, std::map<int, cv::Point2f> srcMap, std::map<int, cv::Point2f> distMap) throw(int){
    std::map<int, cv::Point2f>::iterator it = srcMap.begin();
    while( it != srcMap.end() )
    {
        if((srcPoint.x == (*it).second.x) && (srcPoint.y == (*it).second.y)) {
            if(distMap.count((*it).first) > 0) {
                return distMap[(*it).first];
            }
        }
        ++it;
    }
    throw 0;
}

cv::Mat get_affined_image(std::vector<cv::Vec6f> triangles, cv::Rect rect, cv::Mat srcImg, std::map<int, cv::Point2f> srcMap, std::map<int, cv::Point2f> distMap, cv::Mat distMat)
{
    for(auto it = triangles.begin(); it != triangles.end(); it++)
    {
        cv::Vec6f &vec = *it;

        // 画面外の点は無視する(ドロネー図を描くと必ず画面を内包する三角形が一個だけ出来てしまう)
        dlib::vector<long, 2> p1((long)vec[0], (long)vec[1]);
        if(!rect.contains(cv::Point(p1.x(),p1.y()))) {
            continue;
        }
        dlib::vector<long, 2> p2((long)vec[2], (long)vec[3]);
        if(!rect.contains(cv::Point(p2.x(),p2.y()))) {
            continue;
        }
        dlib::vector<long, 2> p3((long)vec[4], (long)vec[5]);
        if(!rect.contains(cv::Point(p3.x(),p3.y()))) {
            continue;
        }

        // Input triangle
        cv::Point2f srcP1(vec[0],vec[1]);
        cv::Point2f srcP2(vec[2],vec[3]);
        cv::Point2f srcP3(vec[4],vec[5]);

        cv::vector <cv::Point2f> triIn;
        triIn.push_back(srcP1);
        triIn.push_back(srcP2);
        triIn.push_back(srcP3);

        // Output triangle
        cv::vector <cv::Point2f> triOut;
        try {
            triOut.push_back(get_dist_point(srcP1, srcMap, distMap));
            triOut.push_back(get_dist_point(srcP2, srcMap, distMap));
            triOut.push_back(get_dist_point(srcP3, srcMap, distMap));
        } catch(int n) {
            continue;
        }

        // 余分な部分の計算をしないようにするため三角形を内包する矩形を求めておく
        cv::Rect rectIn = boundingRect(triIn);
        cv::Rect rectOut = boundingRect(triOut);

        // 切り取られた三角形
        cv::vector<cv::Point2f> tri1Cropped, tri2Cropped;
        cv::vector<cv::Point> tri2CroppedInt;

        for(int i = 0; i < 3; i++)
        {
            tri1Cropped.push_back(cv::Point2f(triIn[i].x - rectIn.x, triIn[i].y -  rectIn.y));
            tri2Cropped.push_back(cv::Point2f(triOut[i].x - rectOut.x, triOut[i].y - rectOut.y));

            // fillConvexPoly needs a vector of Point and not Point2f
            tri2CroppedInt.push_back(cv::Point((int)(triOut[i].x - rectOut.x), (int)(triOut[i].y - rectOut.y)));
        }

        // 元画像から最小矩形を切り取った部分をパッチにコピーする
        cv::Mat img1Cropped;
        srcImg(rectIn).copyTo(img1Cropped);

        // 三角形のアフィン行列を求める
        cv::Mat warpMat = getAffineTransform(tri1Cropped, tri2Cropped);

        // 元画像からアフィン行列を実施する
        cv::Mat img2Cropped = cv::Mat::zeros(rectOut.height, rectOut.width, img1Cropped.type());
        warpAffine(img1Cropped, img2Cropped, warpMat, img2Cropped.size(), cv::INTER_LINEAR, cv::BORDER_REFLECT_101);

        // マスクを生成
        cv::Mat mask = cv::Mat::zeros(rectOut.height, rectOut.width, img1Cropped.type());
        fillConvexPoly(mask, tri2CroppedInt, cv::Scalar(1.0, 1.0, 1.0), 16, 0);

        // マスクをパッチに適用
        multiply(img2Cropped, mask, img2Cropped);

        // マスク部分ですでに値が入っている場合(境界線部分)は一旦0にする
        multiply(distMat(rectOut), cv::Scalar(1.0,1.0,1.0) - mask, distMat(rectOut));

        // 出力画像に追加する
        distMat(rectOut) = distMat(rectOut) + img2Cropped;
    }
    return distMat;
}

int main()
{
    try
    {
        array2d<bgr_pixel> bpImg;
        dlib::load_image(bpImg, "bp.png");
        cv::Mat bpMat = cv::Mat::zeros(HEIGHT, WIDTH, dlib::toMat(bpImg).type());
        bpMat = dlib::toMat(bpImg);

        cv::VideoCapture cap(0);
        cap.set(CV_CAP_PROP_FRAME_WIDTH , WIDTH);
        cap.set(CV_CAP_PROP_FRAME_HEIGHT , HEIGHT);
        if (!cap.isOpened())
        {
            cerr << "Unable to connect to camera" << endl;
            return 1;
        }

        image_window win1;
        win1.set_size(WIDTH, HEIGHT+CV_CAP_PROP_FRAME_HEIGHT);
        image_window win2;
        win2.set_size(WIDTH, HEIGHT+CV_CAP_PROP_FRAME_HEIGHT);
        image_window win3;

        win3.set_image(bpImg);

        // Load face detection and pose estimation models.
        frontal_face_detector detector = get_frontal_face_detector();
        shape_predictor pose_model;
        deserialize("shape_predictor_68_face_landmarks.dat") >> pose_model;
        while(!win1.is_closed())
        {
            cv::Mat capMat;
            cap >> capMat;
            cv_image<bgr_pixel> targetImg(capMat);

            // カメラ画像の表示
            win1.clear_overlay();
            win1.set_image(targetImg);

            // ###############
            // カメラ画像の処理
            // ###############

            // Detect faces
            std::vector<rectangle> targetFaces = detector(targetImg);

            // Find the pose of each face.
            std::vector<full_object_detection> targetShapes;
            for (int i = 0; i < targetFaces.size(); ++i) {
                targetShapes.push_back(pose_model(targetImg, targetFaces[i]));
            }

            // Subdiv2D初期化
            cv::Subdiv2D subdiv;
            cv::Rect rect(0, 0, capMat.cols, capMat.rows);
            subdiv.initDelaunay(rect);

            // 顔を検出していない場合は何もしない
            if(targetShapes.empty()) {
                continue;
            }

            // 最初に検出した顔の点を追加
            std::map<int, cv::Point2f> targetMap;
            full_object_detection& d1 = targetShapes[0];
            for (int i = 0; i < d1.num_parts(); ++i) {
                if(!rect.contains(cv::Point(d1.part(i).x(),d1.part(i).y()))) {
                    continue;
                }
                cv::Point2f point(d1.part(i).x(), d1.part(i).y());
                subdiv.insert(point);
                targetMap.insert(std::pair<int, cv::Point2f>(i, point));
            }

            // ###############
            // 重畳画像の処理
            // ###############

            // Detect faces
            std::vector<rectangle> bpFaces = detector(bpImg);

            // Find the pose of each face.
            std::vector<full_object_detection> bpShapes;
            for (int i = 0; i < bpFaces.size(); ++i) {
                bpShapes.push_back(pose_model(bpImg, bpFaces[i]));
            }

            // Subdiv2D初期化
            cv::Subdiv2D subdiv2;
            cv::Rect rect2(0, 0, bpMat.cols, bpMat.rows);
            subdiv2.initDelaunay(rect2);

            // 顔を検出していない場合は何もしない
            if(bpShapes.empty()) {
                continue;
            }

            // 最初に検出した顔の点を追加
            std::map<int, cv::Point2f> bpMap;
            full_object_detection& d2 = bpShapes[0];
            for (int i = 0; i < d2.num_parts(); ++i) {
                if(!rect.contains(cv::Point(d2.part(i).x(),d2.part(i).y()))) {
                    continue;
                }
                cv::Point2f point(d2.part(i).x(), d2.part(i).y());
                subdiv2.insert(point);
                bpMap.insert(std::pair<int, cv::Point2f>(i, point));
            }

            // ドロネー三角形のリストを取得
            std::vector<cv::Vec6f> triangles;
            subdiv2.getTriangleList(triangles);

            // ドロネー三角形をアフィン変換した画像を取得
            cv_image<bgr_pixel> affinedImg(get_affined_image(triangles, rect, bpMat, bpMap, targetMap, capMat));

            // 画像を表示
            win2.clear_overlay();
            win2.set_image(affinedImg);
        }
    }
    catch(serialization_error& e)
    {
        cout << "You need dlib's default face landmarking model file to run this example." << endl;
        cout << "You can get it from the following URL: " << endl;
        cout << "   http://dlib.net/files/shape_predictor_68_face_landmarks.dat.bz2" << endl;
        cout << endl << e.what() << endl;
    }
    catch(exception& e)
    {
        cout << e.what() << endl;
    }
}

10
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
7