您好,登錄后才能下訂單哦!
C#中怎么利用OpenCV實(shí)現(xiàn)人臉替換功能,相信很多沒(méi)有經(jīng)驗(yàn)的人對(duì)此束手無(wú)策,為此本文總結(jié)了問(wèn)題出現(xiàn)的原因和解決方法,通過(guò)這篇文章希望你能解決這個(gè)問(wèn)題。
圖像獲取
在C#中要解決這個(gè)問(wèn)題,我們將使用 Accord庫(kù) 、 OpenCvSharp3 以及 DLib 。 Accord庫(kù)非常適合創(chuàng)建計(jì)算機(jī)視覺(jué)應(yīng)用程序。 OpenCvSharp3是一個(gè)基于C#的OpenCV庫(kù),我們將使用這個(gè)庫(kù)中的幾個(gè)圖像轉(zhuǎn)換功能。 在計(jì)算機(jī)視覺(jué)世界中,DLib則是人臉檢測(cè)的首選庫(kù)。 雖然DLib完全用C ++編寫,但是DlibDotNet,將所有程序封裝到C#中。
我們首先需要獲得一張布拉德利的原始自拍照和單人照:
原始自拍
單人照
說(shuō)明:使用以下代碼可以將單人照與自拍照中的任何人交換面孔,但是就以上兩幅圖而言選擇替換布拉德利·庫(kù)珀效果最好,因?yàn)閮蓚€(gè)人具有相同的視線方向且臉型相似度很高。
界標(biāo)點(diǎn)檢測(cè)
接下來(lái)我們將使用Dlib庫(kù),對(duì)人臉進(jìn)行檢測(cè)。Dlib面部檢測(cè)器可以識(shí)別出覆蓋面部、下巴、眉毛、鼻子、眼睛和嘴唇的68個(gè)界標(biāo)點(diǎn)。這些標(biāo)記點(diǎn)預(yù)先確定的,并有給予其特定的標(biāo)號(hào),如下圖所示。
Dlib運(yùn)行速度很快,計(jì)算所有這些點(diǎn)的計(jì)算開(kāi)銷僅為1ms!因此它也可以實(shí)時(shí)跟蹤這些點(diǎn)。以下C#代碼,用于檢測(cè)圖片中臉上的所有界標(biāo)點(diǎn):
/// <summary>/// Process the original selfie and produce the face-swapped image./// </summary>/// <param name="image">The original selfie image.</param>/// <param name="newImage">The new face to insert into the selfie.</param>/// <returns>A new image with faces swapped.</returns>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<RgbPixel>(); // 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... }}
界標(biāo)點(diǎn)檢測(cè)結(jié)果
在這段代碼中,我們首先實(shí)例化FrontalFaceDetector和ShapePredictor。為此小伙伴們需要注意以下兩個(gè)問(wèn)題:
? 在Dlib中,檢測(cè)面部和檢測(cè)界標(biāo)點(diǎn)(或者稱為“檢測(cè)形狀”)是兩件不同的事情,它們的性能差異很大。人臉檢測(cè)速度非常慢,而形狀檢測(cè)僅需約1毫秒,并且可以實(shí)時(shí)進(jìn)行。
? ShapePredictor實(shí)際上是一個(gè)從完成訓(xùn)練的數(shù)據(jù)文件中加載出來(lái)的機(jī)器學(xué)習(xí)模型。我們也可以用自己喜歡的任何物體重新訓(xùn)練ShapePredictor,像人臉、貓狗臉、植物等。
接下來(lái)Dlib使用的圖片格式與NET框架所使用的圖片格式不同,因此我需要在運(yùn)行上述代碼之前先轉(zhuǎn)換自拍的圖片格式。其中ToArray2D<>方法即可將位圖轉(zhuǎn)換為陣列RgbPixel結(jié)構(gòu),這中結(jié)構(gòu)正好可用于Dlib。
完成圖像格式轉(zhuǎn)換以后,我們使用Detect() 來(lái)檢測(cè)圖像中的所有面孔。我們選取布拉德利·庫(kù)珀的面孔提供后續(xù)使用,在本次檢測(cè)中剛好為faces(0)。并且我們還用一個(gè)矩形來(lái)標(biāo)識(shí)布拉德利的臉在圖片中的位置。
接下來(lái),我們?cè)赟hapePredictor上調(diào)用Detect() 并提供自拍照和用于識(shí)別位置的臉部矩形。該函數(shù)的返回值是GetPart() 方法的類,我們可以使用GetPart()方法來(lái)檢索所有界標(biāo)點(diǎn)的坐標(biāo)。
我們的后續(xù)人臉交換工作將在OpenCV上完成,而OpenCV擁有自己特定的指針結(jié)構(gòu),因此在代碼的最后我們將Dlib點(diǎn)轉(zhuǎn)換為OpenCV點(diǎn)。
凸包提取
接下來(lái),我們需要計(jì)算界標(biāo)點(diǎn)的凸包。一種簡(jiǎn)單的表達(dá)方式即,鏈接最外面的點(diǎn)形成圍繞臉部的平滑邊界。
OpenCV的內(nèi)置功能可以幫助我們計(jì)算凸包:
// 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() 方法可以計(jì)算所有凸包界標(biāo)點(diǎn)的指數(shù),因此我們需要做的就是運(yùn)行一個(gè)LINQ查詢,以獲取布萊德利·庫(kù)珀的這些界標(biāo)點(diǎn)的枚舉。
下圖是布萊德利臉上的凸包外觀。
完成上述內(nèi)容后,我們需要對(duì)單人照中的臉重復(fù)這些步驟:
// find landmark points in face to swapvar imgMark = newImage.ToArray2D<RgbPixel>();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。下面是從單人照中檢測(cè)到的凸包外觀。
到目前為止,我們已經(jīng)獲得了兩個(gè)凸包外觀,第一個(gè)是布萊德利臉上的凸包外觀,第二個(gè)是單人照上的外觀。
Delaunay三角形變形
單人照與布拉德利的凸包點(diǎn)的坐標(biāo)之間沒(méi)有線性關(guān)系。如果我們嘗試直接移動(dòng)所有像素,則必須使用慢速非線性變換。但是,通過(guò)首先在Delaunay三角形中覆蓋布萊德利的臉,然后分別對(duì)每個(gè)三角形進(jìn)行變形,整個(gè)操作將變得線性(且速度很快?。?/p>
因此我們將為兩人的臉計(jì)算Delaunay三角形。獲取單人照中的三角形以后,對(duì)它們進(jìn)行一定的變形,使其與布萊德利的臉完全匹配。
Delaunay Triangulation是一個(gè)創(chuàng)建三角形網(wǎng)格的過(guò)程,該三角形網(wǎng)格完全覆蓋了布萊德利的臉,每個(gè)三角形由凸包上的三個(gè)特定的界標(biāo)點(diǎn)組成。結(jié)果如下,藍(lán)線即組成了Delaunay三角形:
接下來(lái),我們將對(duì)單人照中Delaunay三角形進(jìn)行變形,使之與布萊德利臉上的每個(gè)三角形保持一直,使新的面孔更加適應(yīng)這張自拍照。在這個(gè)過(guò)程的每個(gè)三角形扭曲都是線性變換,因此可以使用超快速線性矩陣運(yùn)算來(lái)移動(dòng)每個(gè)三角形內(nèi)的像素。
在下圖中,我們扭曲了單人照中由界標(biāo)點(diǎn)3、14和24組成的Delaunay三角形,以使其正好適合布萊德利的臉,并且這三個(gè)點(diǎn)與布萊德利的3、14和24界標(biāo)點(diǎn)精確匹配:
在C#中執(zhí)行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...
我們使用一個(gè)便捷類Utility,該類包含有GetDelaunayTriangles方法用于計(jì)算三角形,GetWarps方法用于計(jì)算每個(gè)三角形的翹曲,以及ApplyWarps方法使單人照臉部與布萊德利的臉部凸包相匹配。
現(xiàn)在,單人照中的臉已用warpedImg表示,并且以及充分變形匹配布萊德利:
顏色轉(zhuǎn)換
單人照與布拉德利的凸包點(diǎn)
我們還有一件事需要處理,單人照中人物的膚色與布拉德利的膚色并不相同。因此,如果我只是在自拍照中將圖像放在其頂部,我們將在圖像邊緣看到劇烈的顏色變化:
為了解決這一問(wèn)題,我們將使用OpenCV中的一個(gè)函數(shù)SeamlessClone,該函數(shù)可以將一個(gè)圖像無(wú)縫地融合到另一個(gè)圖像中,并消除任何顏色差異。
這是在C#中進(jìn)行無(wú)縫克隆的方法:
// 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方法需要我們完成以下兩件事:
? 首先需要一個(gè)mask來(lái)告訴它要混合哪些像素。我們?cè)讷@取布拉德利面部凸包時(shí)使用FillConvexPoly方法即可計(jì)算所需的mask。
? 中心點(diǎn)處應(yīng)該完全是單人照的膚色100%,距離中心點(diǎn)越遠(yuǎn)的像素將獲得越接近的布拉德利膚色。我們通過(guò)調(diào)用BoundingRect獲得布拉德利臉的邊界框,然后取該框的中心來(lái)估計(jì)中心的位置。
然后,我調(diào)用SeamlessClone進(jìn)行克隆并將結(jié)果存儲(chǔ)在blend變量中,最終結(jié)果如下所示:
其他
看到這里小伙伴們可能在想為什么在這個(gè)過(guò)程中需要使用凸包,而不是直接不使用所有界標(biāo)點(diǎn)來(lái)計(jì)算三角形?
原因?qū)嶋H上很簡(jiǎn)單,我們比較一下布拉德利的自拍與單人照。不難發(fā)現(xiàn)一個(gè)人在笑而另一個(gè)人沒(méi)有?如果我們直接使用所有界標(biāo)點(diǎn),該程序?qū)L試把整個(gè)臉都進(jìn)行變形,以便于和布拉德利的嘴唇,鼻子和眼睛完全匹配。這會(huì)使單人照中的人的嘴唇張開(kāi),以使單人照中的人物微笑并露出牙齒。
但結(jié)果似乎并不太好。
如果只使用凸包殼點(diǎn),該程序可以使單人照中人物的下巴變形,以匹配布拉德利的下頜線。但是它無(wú)法處理該人物的眼睛,鼻子和嘴巴。這意味著表情等在新圖像中保持不變,看起來(lái)也更加自然。
最后,我們將使用Instagram濾鏡來(lái)進(jìn)一步消除色差:
看完上述內(nèi)容,你們掌握C#中怎么利用OpenCV實(shí)現(xiàn)人臉替換功能的方法了嗎?如果還想學(xué)到更多技能或想了解更多相關(guān)內(nèi)容,歡迎關(guān)注億速云行業(yè)資訊頻道,感謝各位的閱讀!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。