OpenCV 4.11.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);

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

结果

考虑使用以下两张图像进行人脸交换

第一张图像

第二张图像

交换后的结果