重庆分公司,新征程启航
为企业提供网站建设、域名注册、服务器等服务
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... }}
界标点检测结果
在这段代码中,我们首先实例化FrontalFaceDetector和ShapePredictor。为此小伙伴们需要注意以下两个问题:
• 在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实现人脸替换功能的方法了吗?如果还想学到更多技能或想了解更多相关内容,欢迎关注创新互联行业资讯频道,感谢各位的阅读!