OpenCV 4.12.0
开源计算机视觉
加载中...
搜索中...
无匹配项
使用人脸特征点检测进行换脸

此应用程序允许您将一张图像中的面部与另一张图像中的面部进行交换。该应用程序首先检测两张图像中的人脸并找到其面部标志。然后,它将第一张图像中的面部与另一张图像中的面部进行交换。您只需提供图像的路径并运行应用程序即可交换两张人脸。

// 运行示例需要键入的命令
./sample_face_swapping -file=trained_model.dat -face_cascade=lbpcascadefrontalface.xml -image1=/path_to_image/image1.jpg -image2=/path_to_image/image2.jpg

命令参数说明

‍* image1 i1 (必需) 要应用换脸的第一个图像文件的路径。

  • image2 i2 (必需) 要应用换脸的第二个图像文件的路径。
  • model m (必需) 包含要加载的面部标志检测模型的文件的路径。
  • face_cascade f (必需) 您要用作人脸检测器的面部级联 xml 文件的路径。

代码理解

本教程将解释使用 OpenCV 进行人脸交换的示例代码。直接跳转到代码

c++
CascadeClassifier face_cascade;
bool myDetector( InputArray image, OutputArray ROIs );
bool myDetector( InputArray image, OutputArray ROIs ){
Mat gray;
std::vector<Rect> faces;
if(image.channels()>1){
cvtColor(image.getMat(),gray,COLOR_BGR2GRAY);
}
else{
gray = image.getMat().clone();
}
equalizeHist( gray, gray );
face_cascade.detectMultiScale( gray, faces, 1.1, 3,0, Size(30, 30) );
Mat(faces).copyTo(ROIs);
return true;
}

facemark API 为用户提供了使用自己的面部检测器进行面部标志检测的功能。上面的代码创建了一个示例人脸检测器。上面的函数将被传递给 facemark API 中的函数指针。

c++
Mat img = imread(image);
face_cascade.load(cascade_name);
FacemarkKazemi::Params params;
params.configfile = configfile_name;
Ptr<Facemark> facemark = FacemarkKazemi::create(params);
facemark->setFaceDetector(myDetector);

上面的代码创建了面部标志检测类的指针。上面创建的人脸检测器必须作为函数指针传递给为训练模型而创建的 facemark 指针,以检测人脸。

c++
vector<Rect> faces1,faces2;
vector< vector<Point2f> > shape1,shape2;
float ratio1 = (float)img1.cols/(float)img1.rows;
float ratio2 = (float)img2.cols/(float)img2.rows;
resize(img1,img1,Size(640*ratio1,640*ratio1),0,0,INTER_LINEAR_EXACT);
resize(img2,img2,Size(640*ratio2,640*ratio2),0,0,INTER_LINEAR_EXACT);
Mat img1Warped = img2.clone();
facemark->getFaces(img1,faces1);
facemark->getFaces(img2,faces2);
facemark->fit(img1,faces1,shape1);
facemark->fit(img2,faces2,shape2);

上面的代码创建了向量来存储检测到的人脸,以及一个向量的向量来存储在两张图像中检测到的每个人脸的形状。然后,它检测在两张图像中检测到的每个人脸的面部标志。图像会被调整大小,因为处理小图像更容易。图像会根据它们的实际比例调整大小。

c++
vector<Point2f> boundary_image1;
vector<Point2f> boundary_image2;
vector<int> index;
convexHull(Mat(points2),index, false, false);
for(size_t i = 0; i < index.size(); i++)
{
boundary_image1.push_back(points1[index[i]]);
boundary_image2.push_back(points2[index[i]]);
}

上面的代码然后找到凸包,以找到必须交换的图像中面部的边界点。

c++
vector< vector<int> > triangles;
Rect rect(0, 0, img1Warped.cols, img1Warped.rows);
divideIntoTriangles(rect, boundary_image2, triangles);
for(size_t i = 0; i < triangles.size(); i++)
{
vector<Point2f> triangle1, triangle2;
for(int j = 0; j < 3; j++)
{
triangle1.push_back(boundary_image1[triangles[i][j]]);
triangle2.push_back(boundary_image2[triangles[i][j]]);
}
warpTriangle(img1, img1Warped, triangle1, triangle2);
}

现在,由于我们需要将一张脸扭曲到另一张脸上,因此我们需要找到仿射变换。现在,由于 OpenCV 中找到仿射变换的函数需要三组点来计算仿射矩阵。此外,我们只需要扭曲面部而不是周围区域。因此,我们将面部分成三角形,以便每个三角形都可以轻松地扭曲到另一张图像上。

函数 divideIntoTriangles 将检测到的人脸分成三角形。然后,函数 warpTriangle 将一张图像的每个三角形扭曲到另一张图像,以交换人脸。

c++
vector<Point> hull;
for(size_t i = 0; i < boundary_image2.size(); i++)
{
Point pt((int)boundary_image2[i].x,(int)boundary_image2[i].y);
hull.push_back(pt);
}
Mat mask = Mat::zeros(img2.rows, img2.cols, img2.depth());
fillConvexPoly(mask,&hull[0],(int)hull.size(), Scalar(255,255,255));
Rect r = boundingRect(boundary_image2);
Point center = (r.tl() + r.br()) / 2;
Mat output;
img1Warped.convertTo(img1Warped, CV_8UC3);
seamlessClone(img1Warped,img2, mask, center, output, NORMAL_CLONE);
imshow("Face_Swapped", output);

即使在扭曲之后,结果看起来仍然有些不自然。因此,为了改善结果,我们应用无缝克隆以获得所需的结果。

结果

考虑以下两张用于换脸的图像

第一张图像

第二张图像

换脸后的结果