基于opencv和Dlib的人脸交换(face swap)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/wangxing233/article/details/51771265

一 什么是人脸交换。

如下图所示,将右边汤唯的脸换成左边鹿晗的脸,就变成啦中间的照片。这就是人脸交换。这个效果通过PS也可以实现,不过这里是完全自动的实现,还是很赞的。这篇文章主要参考[1],作者给出在文章中给出啦代码,不过和上篇人脸合成的一样,给出的代码不是完整的。我这里给出完整的代码:https://github.com/iamwx/faceSwap

这里写图片描述这里写图片描述这里写图片描述
注:鹿晗和汤唯的图片来自网络

二 具体步骤

主要分为两个步骤:人脸对齐(face aligment)和无缝融合(Seamless Cloning,可能翻译的不准确)。其中人脸对齐又分为人脸关键点检测(face landmark detection),计算凸包(convex hull),Delaunay三角剖分(delaunay trangulation), 仿射变换(affine warp)。下面来一点点说。

1 人脸关键点检测

首先要检测出两张图片上的人脸关键点。直接使用dlib的检测函数,先检测人脸,再检测人脸关键点。

void faceLandmarkDetection(dlib::array2d<unsigned char>& img, shape_predictor sp, std::vector<Point2f>& landmark)
{
        dlib::frontal_face_detector detector = get_frontal_face_detector();
    //dlib::pyramid_up(img);

    std::vector<dlib::rectangle> dets = detector(img);
    //cout << "Number of faces detected: " << dets.size() << endl;


    full_object_detection shape = sp(img, dets[0]);
    //image_window win;
    //win.clear_overlay();
    //win.set_image(img);
    //win.add_overlay(render_face_detections(shape));
    for (int i = 0; i < shape.num_parts(); ++i)
    {
        float x=shape.part(i).x();
        float y=shape.part(i).y();  
        landmark.push_back(Point2f(x,y));       
    }


}

这里写图片描述
注:该图来自原作

2 . 计算凸包

如上图所示一共检测出68个关键点。然而我们只需要人脸边缘的哪些关键点。所以我们先计算这68个关键点的凸包。关于凸包的概念可以参考[2]。

    std::vector<Point2f> hull1;
    std::vector<Point2f> hull2;
    std::vector<int> hullIndex;//保存组成凸包的关键点的下标索引。

    cv::convexHull(points2, hullIndex, false, false);//计算凸包

    //保存组成凸包的关键点。
    for(int i = 0; i < hullIndex.size(); i++)
        {
        hull1.push_back(points1[hullIndex[i]]);
        hull2.push_back(points2[hullIndex[i]]);
        }

计算得到的凸包是有那些处在边缘的点组成的。如上图最左边显示。

3. Delaunay 三角剖份 和 仿射变换

有啦那些脸部边缘的关键点之后,我们对这些关键点进行三角剖份,如上图中间所示。具体的可以参考我的另外一篇博客基于opencv+Dlib的面部合成(Face Morph)
然后我们在将这左边脸上的这一个个小三角形通过仿射变换投影到右边的脸上,就得到啦右边那副图片。

for(size_t i=0;i<delaunayTri.size();++i)
    {
        std::vector<Point2f> t1,t2;//存放三角形的顶点
        correspondens corpd=delaunayTri[i];
        for(size_t j=0;j<3;++j)
        {
            t1.push_back(hull1[corpd.index[j]]);
            t2.push_back(hull2[corpd.index[j]]);
        }

        warpTriangle(imgCV1,imgCV1Warped,t1,t2); //进行仿射变换           
    }
void warpTriangle(Mat &img1, Mat &img2, std::vector<Point2f> &t1, std::vector<Point2f> &t2)
{

    Rect r1 = boundingRect(t1);
    Rect r2 = boundingRect(t2);

    // Offset points by left top corner of the respective rectangles
    std::vector<Point2f> t1Rect, t2Rect;
    std::vector<Point> t2RectInt;
    for(int i = 0; i < 3; i++)
    {

        t1Rect.push_back( Point2f( t1[i].x - r1.x, t1[i].y -  r1.y) );
        t2Rect.push_back( Point2f( t2[i].x - r2.x, t2[i].y - r2.y) );
        t2RectInt.push_back( Point(t2[i].x - r2.x, t2[i].y - r2.y) ); // for fillConvexPoly

    }

    // Get mask by filling triangle
    Mat mask = Mat::zeros(r2.height, r2.width, CV_32FC3);
    fillConvexPoly(mask, t2RectInt, Scalar(1.0, 1.0, 1.0), 16, 0);

    // Apply warpImage to small rectangular patches
    Mat img1Rect;
    img1(r1).copyTo(img1Rect);

    Mat img2Rect = Mat::zeros(r2.height, r2.width, img1Rect.type());

    applyAffineTransform(img2Rect, img1Rect, t1Rect, t2Rect);

    multiply(img2Rect,mask, img2Rect);
    multiply(img2(r2), Scalar(1.0,1.0,1.0) - mask, img2(r2));
    img2(r2) = img2(r2) + img2Rect;    

}

void applyAffineTransform(Mat &warpImage, Mat &src, std::vector<Point2f> &srcTri, std::vector<Point2f> &dstTri)
{
    // Given a pair of triangles, find the affine transform.
    Mat warpMat = getAffineTransform( srcTri, dstTri );

    // Apply the Affine Transform just found to the src image
    warpAffine( src, warpImage, warpMat, warpImage.size(), cv::INTER_LINEAR, BORDER_REFLECT_101);
}

这里写图片描述
注:该图来自原作

4. 无缝融合

可以从上图最左边看出来,这样直接投影过去的话非常生硬。所以还需要把投影过去的图片src和原来的图片dst进行融合。
这里需要先计算一个脸部的mask,如上图3所示。

    //calculate mask
    std::vector<Point> hull8U;

    for(int i=0; i< hull2.size();++i)
    {
        Point pt(hull2[i].x,hull2[i].y);
        hull8U.push_back(pt);
    }


    Mat mask = Mat::zeros(imgCV2.rows,imgCV2.cols,imgCV2.depth());
    fillConvexPoly(mask, &hull8U[0], hull8U.size(), Scalar(255,255,255));

之后再直接调用opencv的seamlessClone函数进行融合就可以啦。使用seamlessClone这个函数需要包含头文件

#include<opencv2/photo.hpp>

seamlessClone的具体定义可以参考opencv的官方文档[3]。在本文中的使用如下:

Rect r = boundingRect(hull2);
    Point center = (r.tl() +r.br()) / 2;

    Mat output;
    imgCV1Warped.convertTo(imgCV1Warped, CV_8UC3);
    seamlessClone(imgCV1Warped,imgCV2,mask,center,output,NORMAL_CLONE);

经过这些操作之后就得到了最终的图片。


参考资料:

[1] Face Swap using OpenCV ( C++ / Python )
[2] 凸包_百度百科
[3] seamlessClone opencv documents

猜你喜欢

转载自blog.csdn.net/wangxing233/article/details/51771265