您好,登錄后才能下訂單哦!
C#中怎么利用OpenCV實現人臉替換功能,相信很多沒有經驗的人對此束手無策,為此本文總結了問題出現的原因和解決方法,通過這篇文章希望你能解決這個問題。
圖像獲取
在C#中要解決這個問題,我們將使用 Accord庫 、 OpenCvSharp3 以及 DLib 。 Accord庫非常適合創建計算機視覺應用程序。 OpenCvSharp3是一個基于C#的OpenCV庫,我們將使用這個庫中的幾個圖像轉換功能。 在計算機視覺世界中,DLib則是人臉檢測的首選庫。 雖然DLib完全用C ++編寫,但是DlibDotNet,將所有程序封裝到C#中。
我們首先需要獲得一張布拉德利的原始自拍照和單人照:
原始自拍
單人照
說明:使用以下代碼可以將單人照與自拍照中的任何人交換面孔,但是就以上兩幅圖而言選擇替換布拉德利·庫珀效果最好,因為兩個人具有相同的視線方向且臉型相似度很高。
界標點檢測
接下來我們將使用Dlib庫,對人臉進行檢測。Dlib面部檢測器可以識別出覆蓋面部、下巴、眉毛、鼻子、眼睛和嘴唇的68個界標點。這些標記點預先確定的,并有給予其特定的標號,如下圖所示。
Dlib運行速度很快,計算所有這些點的計算開銷僅為1ms!因此它也可以實時跟蹤這些點。以下C#代碼,用于檢測圖片中臉上的所有界標點:
/// <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... }}
界標點檢測結果
在這段代碼中,我們首先實例化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<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。下面是從單人照中檢測到的凸包外觀。
到目前為止,我們已經獲得了兩個凸包外觀,第一個是布萊德利臉上的凸包外觀,第二個是單人照上的外觀。
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實現人臉替換功能的方法了嗎?如果還想學到更多技能或想了解更多相關內容,歡迎關注億速云行業資訊頻道,感謝各位的閱讀!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。