千家信息网

C#中怎么利用OpenCV实现人脸替换功能

发表于:2024-11-22 作者:千家信息网编辑
千家信息网最后更新 2024年11月22日,C#中怎么利用OpenCV实现人脸替换功能,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。图像获取在C#中要解决这个问题,我们将使用 Ac
千家信息网最后更新 2024年11月22日C#中怎么利用OpenCV实现人脸替换功能

C#中怎么利用OpenCV实现人脸替换功能,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。

图像获取

在C#中要解决这个问题,我们将使用 Accord库OpenCvSharp3 以及 DLib 。 Accord库非常适合创建计算机视觉应用程序。 OpenCvSharp3是一个基于C#的OpenCV库,我们将使用这个库中的几个图像转换功能。 在计算机视觉世界中,DLib则是人脸检测的首选库。 虽然DLib完全用C ++编写,但是DlibDotNet,将所有程序封装到C#中。

我们首先需要获得一张布拉德利的原始自拍照和单人照:

原始自拍

单人照

说明:使用以下代码可以将单人照与自拍照中的任何人交换面孔,但是就以上两幅图而言选择替换布拉德利·库珀效果最好,因为两个人具有相同的视线方向且脸型相似度很高。

界标点检测

接下来我们将使用Dlib库,对人脸进行检测。Dlib面部检测器可以识别出覆盖面部、下巴、眉毛、鼻子、眼睛和嘴唇的68个界标点。这些标记点预先确定的,并有给予其特定的标号,如下图所示。

Dlib运行速度很快,计算所有这些点的计算开销仅为1ms!因此它也可以实时跟踪这些点。以下C#代码,用于检测图片中脸上的所有界标点:

/// /// Process the original selfie and produce the face-swapped image./// /// The original selfie image./// The new face to insert into the selfie./// A new image with faces swapped.private Bitmap ProcessImage(Bitmap image, Bitmap newImage){    // set up Dlib facedetectors and shapedetectors    using (var fd = FrontalFaceDetector.GetFrontalFaceDetector())    using (var sp = new ShapePredictor("shape_predictor_68_face_landmarks.dat"))    {        // convert image to dlib format        var img = image.ToArray2D();        // find bradley's faces in image        var faces = fd.Detect(img);        var bradley = faces[0];        // get bradley's landmark points        var bradleyShape = sp.Detect(img, bradley);        var bradleyPoints = (from i in Enumerable.Range(0, (int)bradleyShape.Parts)                             let p = bradleyShape.GetPart((uint)i)                             select new OpenCvSharp.Point(p.X, p.Y)).ToArray();         // remainder of code goes here...    }}

界标点检测结果

在这段代码中,我们首先实例化FrontalFaceDetectorShapePredictor。为此小伙伴们需要注意以下两个问题:

• 在Dlib中,检测面部和检测界标点(或者称为"检测形状")是两件不同的事情,它们的性能差异很大。人脸检测速度非常慢,而形状检测仅需约1毫秒,并且可以实时进行。

• ShapePredictor实际上是一个从完成训练的数据文件中加载出来的机器学习模型。我们也可以用自己喜欢的任何物体重新训练ShapePredictor,像人脸、猫狗脸、植物等。

接下来Dlib使用的图片格式与NET框架所使用的图片格式不同,因此我需要在运行上述代码之前先转换自拍的图片格式。其中ToArray2D<>方法即可将位图转换为阵列RgbPixel结构,这中结构正好可用于Dlib。

完成图像格式转换以后,我们使用Detect() 来检测图像中的所有面孔。我们选取布拉德利·库珀的面孔提供后续使用,在本次检测中刚好为faces(0)。并且我们还用一个矩形来标识布拉德利的脸在图片中的位置。

接下来,我们在ShapePredictor上调用Detect() 并提供自拍照和用于识别位置的脸部矩形。该函数的返回值是GetPart() 方法的类,我们可以使用GetPart()方法来检索所有界标点的坐标。

我们的后续人脸交换工作将在OpenCV上完成,而OpenCV拥有自己特定的指针结构,因此在代码的最后我们将Dlib点转换为OpenCV点。

凸包提取

接下来,我们需要计算界标点的凸包。一种简单的表达方式即,链接最外面的点形成围绕脸部的平滑边界。

OpenCV的内置功能可以帮助我们计算凸包:

// get convex hull of bradley's pointsvar hull = Cv2.ConvexHullIndices(bradleyPoints);var bradleyHull = from i in hull                  select bradleyPoints[i];                  // the remaining code goes here...

ConvesHullIndices() 方法可以计算所有凸包界标点的指数,因此我们需要做的就是运行一个LINQ查询,以获取布莱德利·库珀的这些界标点的枚举。

下图是布莱德利脸上的凸包外观。

完成上述内容后,我们需要对单人照中的脸重复这些步骤:

// find landmark points in face to swapvar imgMark = newImage.ToArray2D();var faces2 = fd.Detect(imgMark);var mark = faces2[0];var markShape = sp.Detect(imgMark, mark);var markPoints = (from i in Enumerable.Range(0, (int)markShape.Parts)                  let p = markShape.GetPart((uint)i)                  select new OpenCvSharp.Point(p.X, p.Y)).ToArray();// get convex hull of mark's pointsvar hull2 = Cv2.ConvexHullIndices(bradleyPoints);var markHull = from i in hull2                  select markPoints[i];// the remaining code goes here...

这里的代码完全相同,只是将newImage换成了image。下面是从单人照中检测到的凸包外观。

到目前为止,我们已经获得了两个凸包外观,第一个是布莱德利脸上的凸包外观,第二个是单人照上的外观。

Delaunay三角形变形

单人照与布拉德利的凸包点的坐标之间没有线性关系。如果我们尝试直接移动所有像素,则必须使用慢速非线性变换。但是,通过首先在Delaunay三角形中覆盖布莱德利的脸,然后分别对每个三角形进行变形,整个操作将变得线性(且速度很快!)。

因此我们将为两人的脸计算Delaunay三角形。获取单人照中的三角形以后,对它们进行一定的变形,使其与布莱德利的脸完全匹配。

Delaunay Triangulation是一个创建三角形网格的过程,该三角形网格完全覆盖了布莱德利的脸,每个三角形由凸包上的三个特定的界标点组成。结果如下,蓝线即组成了Delaunay三角形:

接下来,我们将对单人照中Delaunay三角形进行变形,使之与布莱德利脸上的每个三角形保持一直,使新的面孔更加适应这张自拍照。在这个过程的每个三角形扭曲都是线性变换,因此可以使用超快速线性矩阵运算来移动每个三角形内的像素。

在下图中,我们扭曲了单人照中由界标点3、14和24组成的Delaunay三角形,以使其正好适合布莱德利的脸,并且这三个点与布莱德利的3、14和24界标点精确匹配:

在C#中执行Delaunay三角剖分和变形的代码如下:

// calculate Delaunay trianglesvar triangles = Utility.GetDelaunayTriangles(bradleyHull);// get transformations to warp the new face onto Bradley's facevar warps = Utility.GetWarps(markHull, bradleyHull, triangles);// apply the warps to the new face to prep it for insertion into the main imagevar warpedImg = Utility.ApplyWarps(newImage, image.Width, image.Height, warps);// the remaining code goes here...

我们使用一个便捷类Utility,该类包含有GetDelaunayTriangles方法用于计算三角形,GetWarps方法用于计算每个三角形的翘曲,以及ApplyWarps方法使单人照脸部与布莱德利的脸部凸包相匹配。

现在,单人照中的脸已用warpedImg表示,并且以及充分变形匹配布莱德利:

颜色转换

单人照与布拉德利的凸包点

我们还有一件事需要处理,单人照中人物的肤色与布拉德利的肤色并不相同。因此,如果我只是在自拍照中将图像放在其顶部,我们将在图像边缘看到剧烈的颜色变化:

为了解决这一问题,我们将使用OpenCV中的一个函数SeamlessClone,该函数可以将一个图像无缝地融合到另一个图像中,并消除任何颜色差异。

这是在C#中进行无缝克隆的方法:

// prepare a mask for the warped imagevar mask = new Mat(image.Height, image.Width, MatType.CV_8UC3);mask.SetTo(0);Cv2.FillConvexPoly(mask, bradleyHull, new Scalar(255, 255, 255), LineTypes.Link8);// find the center of the warped facevar r = Cv2.BoundingRect(bradleyHull);var center = new OpenCvSharp.Point(r.Left + r.Width / 2, r.Top + r.Height / 2);// blend the warped face into the main imagevar selfie = BitmapConverter.ToMat(image);var blend = new Mat(selfie.Size(), selfie.Type());Cv2.SeamlessClone(warpedImg, selfie, mask, center, blend, SeamlessCloneMethods.NormalClone);// return the modified main imagereturn BitmapConverter.ToBitmap(blend);

使用SeamlessClone方法需要我们完成以下两件事:

• 首先需要一个mask来告诉它要混合哪些像素。我们在获取布拉德利面部凸包时使用FillConvexPoly方法即可计算所需的mask。

• 中心点处应该完全是单人照的肤色100%,距离中心点越远的像素将获得越接近的布拉德利肤色。我们通过调用BoundingRect获得布拉德利脸的边界框,然后取该框的中心来估计中心的位置。

然后,我调用SeamlessClone进行克隆并将结果存储在blend变量中,最终结果如下所示:

其他

看到这里小伙伴们可能在想为什么在这个过程中需要使用凸包,而不是直接不使用所有界标点来计算三角形?

原因实际上很简单,我们比较一下布拉德利的自拍与单人照。不难发现一个人在笑而另一个人没有?如果我们直接使用所有界标点,该程序将尝试把整个脸都进行变形,以便于和布拉德利的嘴唇,鼻子和眼睛完全匹配。这会使单人照中的人的嘴唇张开,以使单人照中的人物微笑并露出牙齿。

但结果似乎并不太好。

如果只使用凸包壳点,该程序可以使单人照中人物的下巴变形,以匹配布拉德利的下颌线。但是它无法处理该人物的眼睛,鼻子和嘴巴。这意味着表情等在新图像中保持不变,看起来也更加自然。

最后,我们将使用Instagram滤镜来进一步消除色差:

看完上述内容,你们掌握C#中怎么利用OpenCV实现人脸替换功能的方法了吗?如果还想学到更多技能或想了解更多相关内容,欢迎关注行业资讯频道,感谢各位的阅读!

0