该应用程序允许您将一张图像中的人脸与另一张图像中的人脸进行替换。该应用程序首先检测两张图像中的人脸并找到其特征点。然后,它将第一张图像中的人脸与另一张图像中的人脸进行替换。您只需要提供要交换的两张图像的路径即可运行应用程序。
// 运行样本所需的命令
./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);
即使在变形后,结果看起来仍然不自然。因此,为了改善结果,我们应用无缝克隆来获得所需的所需结果。
结果
考虑使用以下两张图像进行换脸
第一幅图像
第二幅图像
交换后的结果