實操教程|使用 Opencv 簡化面部地標(biāo)檢測

極市導(dǎo)讀
今天我們將使用 OpenCV 和 MediaPipe 來檢測圖像中的468個面部地標(biāo)。>>加入極市CV技術(shù)交流群,走在計算機(jī)視覺的最前沿
OpenCV 是用于計算機(jī)視覺、機(jī)器學(xué)習(xí)和圖像處理的跨平臺開源庫,我們可以使用它來開發(fā)實時計算機(jī)視覺應(yīng)用程序。它主要用于圖像或視頻處理以及分析,包括對象檢測、面部檢測等。
面部地標(biāo)用于定位和表示面部的重要區(qū)域,例如:
嘴巴 眼睛 眉毛 鼻子 下頜線等
應(yīng)用
面部地標(biāo)有許多應(yīng)用,例如為人熟知的換臉、面部變形、頭部姿勢估計等。
換臉
如果我們在兩張不同的臉上估計了面部地標(biāo)特征點,我們可以將一張臉與另一張臉對齊,然后我們可以將一張臉克隆到另一張臉上。

面部變形
面部地標(biāo)可用于通過對齊可變形的面部來生成中間圖像。

頭部姿勢估計
一旦我們知道了一些面部地標(biāo)點,那么我們也可以估計頭部的姿勢。

MediaPipe Face Mesh
即使在移動設(shè)備上,MediaPipe Face Mesh 也可以實時估計 468 個 3D 面部地標(biāo)。通過應(yīng)用機(jī)器學(xué)習(xí) (ML) 來推斷 3D 表面幾何形狀,它只需要單個相機(jī)輸入,而無需專用的深度傳感器。它提供了更好的實時性能。
面部地標(biāo)模型
3D 面部地標(biāo)模型使用遷移學(xué)習(xí),并在具有不同目標(biāo)的網(wǎng)絡(luò)上進(jìn)行訓(xùn)練:該網(wǎng)絡(luò)預(yù)測合成渲染數(shù)據(jù)上的 3D 地標(biāo)坐標(biāo)。由此產(chǎn)生的網(wǎng)絡(luò)在現(xiàn)實世界的數(shù)據(jù)上表現(xiàn)得相當(dāng)好。
3D 地標(biāo)網(wǎng)絡(luò)將輸入作為裁剪的視頻幀,而無需額外的深度輸入。該模型輸出 3D 點的位置,在輸入中合理對齊。
幾何管線
幾何管線是一個關(guān)鍵組件,它估計 3D Metric 空間內(nèi)的幾何對象。在每一幀上,分別執(zhí)行以下步驟:
得到Metric 3D空間坐標(biāo),即將面部地標(biāo)屏幕坐標(biāo)轉(zhuǎn)換為Metric 3D空間坐標(biāo)。 面部姿態(tài)變換矩陣被估計為來自標(biāo)準(zhǔn)面部度量界標(biāo)的剛性線性映射,然后將其發(fā)送到運行時面部度量界標(biāo)中,以最小化兩者之間的差異。 運行時面部度量地標(biāo)創(chuàng)建一個面部網(wǎng)格。
讓我們來實現(xiàn)它
首先,讓我們檢查我們的網(wǎng)絡(luò)攝像頭 ID 是否工作正常,并在輸出屏幕上打印每秒幀數(shù) (fps)。
import cv2
import time
cap = cv2.VideoCapture\(0\)
pTime = 0
while True:
success, img = cap.read\(\)
imgRGB = cv2.cvtColor\(img, cv2.COLOR\_BGR2RGB\)
cTime = time.time\(\)
fps = 1/\(cTime-pTime\)
pTime = cTime
cv2.putText\(img, f'FPS:\{int\(fps\)\}', \(20, 70\), cv2.FONT\_HERSHEY\_SIMPLEX, 1, \(0, 255, 0\), 2\)
cv2.imshow\("Test", img\)
cv2.waitKey\(1\)
如果你有網(wǎng)絡(luò)攝像頭,它應(yīng)該會打開一個窗口,否則你可以在VideoCapture功能中指定視頻路徑而不是零。
在左上角,你可以看到 FPS(變化),如下所示。

現(xiàn)在讓我們創(chuàng)建一個新的 python 文件并開始創(chuàng)建我們的面部地標(biāo)檢測模塊。
安裝所需的模塊。
pip install opencv-python
pip install mediapipe
import cv2
import mediapipe as mp
import time
cap = cv2.VideoCapture\(0\)
pTime = 0
NUM\_FACE = 2
mpDraw = mp.solutions.drawing\_utils
mpFaceMesh = mp.solutions.face\_mesh
faceMesh = mpFaceMesh.FaceMesh\(max\_num\_faces=NUM\_FACE\)
drawSpec = mpDraw.DrawingSpec\(thickness=1, circle\_radius=1\)
在上面的代碼中,我們從網(wǎng)絡(luò)攝像頭獲取輸入,變量NUM\_FACE表示有多少面部要從幀中檢測和定位面部地標(biāo)。
要繪制面部點,我們使用mpDraw變量。我們將使用mp.solutions.face\_mesh來創(chuàng)建面部網(wǎng)格。
為了控制連接線和點的粗細(xì),我們將使用drawSpec。
while True:
success, img = cap.read\(\)
imgRGB = cv2.cvtColor\(img, cv2.COLOR\_BGR2RGB\)
results = faceMesh.process\(imgRGB\)
if results.multi\_face\_landmarks:
for faceLms in results.multi\_face\_landmarks:
mpDraw.draw\_landmarks\(img, faceLms,mpFaceMesh.FACE\_CONNECTIONS, drawSpec, drawSpec\)
for id,lm in enumerate\(faceLms.landmark\):
print\(lm\)
ih, iw, ic = img.shape
x,y = int\(lm.x\*iw\), int\(lm.y\*ih\)
# uncomment the below line to see the 468 facial landmark
# cv2.putText\(img, str\(id\), \(x, y\), cv2.FONT\_HERSHEY\_SIMPLEX, 0.3, \(0, 255, 0\), 1\)
print\(id, x,y\)
cTime = time.time\(\)
fps = 1/\(cTime-pTime\)
pTime = cTime
cv2.putText\(img, f'FPS:\{int\(fps\)\}', \(20,70\), cv2.FONT\_HERSHEY\_SIMPLEX, 1, \(0,255,0\), 2\)
cv2.imshow\("Test", img\)
cv2.waitKey\(1\)
然后在 while 循環(huán)中讀取幀并將幀轉(zhuǎn)換為 RGB,將該圖像傳遞給*faceMesh.process(),* 然后在面部繪制檢測到的地標(biāo)。
為了看到468 個面部地標(biāo),取消對for loop 中的cv2.putText\(\)函數(shù)的注釋。語句 print \(id, x, y\)將打印出 id 和坐標(biāo)。然后輸出如下。

現(xiàn)在為了創(chuàng)建一個模塊,以便我們可以在不同的項目中使用它,首先我們需要創(chuàng)建一個包含函數(shù)的類。
import cv2
import mediapipe as mp
import time
NUM\_FACE = 2
class FaceLandMarks\(\):
def \_\_init\_\_\(self, staticMode=False,maxFace=NUM\_FACE, minDetectionCon=0.5, minTrackCon=0.5\):
self.staticMode = staticMode
self.maxFace = maxFace
self.minDetectionCon = minDetectionCon
self.minTrackCon = minTrackCon
self.mpDraw = mp.solutions.drawing\_utils
self.mpFaceMesh = mp.solutions.face\_mesh
self.faceMesh = self.mpFaceMesh.FaceMesh\(self.staticMode, self.maxFace, self.minDetectionCon, self.minTrackCon\)
self.drawSpec = self.mpDraw.DrawingSpec\(thickness=1, circle\_radius=1\)
def findFaceLandmark\(self, img, draw=True\):
self.imgRGB = cv2.cvtColor\(img, cv2.COLOR\_BGR2RGB\)
self.results = self.faceMesh.process\(self.imgRGB\)
faces = \[\]
if self.results.multi\_face\_landmarks:
for faceLms in self.results.multi\_face\_landmarks:
if draw:
self.mpDraw.draw\_landmarks\(img, faceLms, self.mpFaceMesh.FACE\_CONNECTIONS, self.drawSpec, self.drawSpec\)
face = \[\]
for id, lm in enumerate\(faceLms.landmark\):
# print\(lm\)
ih, iw, ic = img.shape
x, y = int\(lm.x \* iw\), int\(lm.y \* ih\)
#cv2.putText\(img, str\(id\), \(x,y\), cv2.FONT\_HERSHEY\_SIMPLEX, 0.3, \(0,255,0\), 1\)
#print\(id, x, y\)
face.append\(\[x,y\]\)
faces.append\(face\)
return img, faces
def main\(\):
cap = cv2.VideoCapture\(0\)
pTime = 0
detector = FaceLandMarks\(\)
while True:
success, img = cap.read\(\)
img, faces = detector.findFaceLandmark\(img\)
if len\(faces\)\!=0:
print\(len\(faces\)\)
cTime = time.time\(\)
fps = 1 / \(cTime - pTime\)
pTime = cTime
cv2.putText\(img, f'FPS:\{int\(fps\)\}', \(20, 70\), cv2.FONT\_HERSHEY\_SIMPLEX, 1, \(0, 255, 0\), 2\)
cv2.imshow\("Test", img\)
cv2.waitKey\(1\)
if \_\_name\_\_ == "\_\_main\_\_":
main\(\)
結(jié)論
在上面的代碼中,函數(shù)名稱是*“findFaceLandmarks”,它檢測面部地標(biāo)并執(zhí)行與上述相同的功能。類“FaceLandMarks()”* 取靜態(tài)模式中,面部的最大數(shù)量和最小檢測置信度和最小的跟蹤置信度。然后創(chuàng)建 函數(shù)來運行代碼。
完整代碼:https://github.com/BakingBrains/Face_LandMark_Detection
參考
https://www.youtube.com/watch?v=V9bzew8A1tc&t=2125s
https://learnopencv.com/facial-landmark-detection/
https://google.github.io/mediapipe/solutions/face_mesh.html
如果覺得有用,就請分享到朋友圈吧!
公眾號后臺回復(fù)“ICCV2021”獲取最新論文合集~

# CV技術(shù)社群邀請函 #

備注:姓名-學(xué)校/公司-研究方向-城市(如:小極-北大-目標(biāo)檢測-深圳)
即可申請加入極市目標(biāo)檢測/圖像分割/工業(yè)檢測/人臉/醫(yī)學(xué)影像/3D/SLAM/自動駕駛/超分辨率/姿態(tài)估計/ReID/GAN/圖像增強(qiáng)/OCR/視頻理解等技術(shù)交流群
每月大咖直播分享、真實項目需求對接、求職內(nèi)推、算法競賽、干貨資訊匯總、與 10000+來自港科大、北大、清華、中科院、CMU、騰訊、百度等名校名企視覺開發(fā)者互動交流~

