实操教程|使用 Opencv 简化面部地标检测
极市导读
今天我们将使用 OpenCV 和 MediaPipe 来检测图像中的468个面部地标。>>加入极市CV技术交流群,走在计算机视觉的最前沿
OpenCV 是用于计算机视觉、机器学习和图像处理的跨平台开源库,我们可以使用它来开发实时计算机视觉应用程序。它主要用于图像或视频处理以及分析,包括对象检测、面部检测等。
面部地标用于定位和表示面部的重要区域,例如:
嘴巴 眼睛 眉毛 鼻子 下颌线等
应用
面部地标有许多应用,例如为人熟知的换脸、面部变形、头部姿势估计等。
换脸
如果我们在两张不同的脸上估计了面部地标特征点,我们可以将一张脸与另一张脸对齐,然后我们可以将一张脸克隆到另一张脸上。
面部变形
面部地标可用于通过对齐可变形的面部来生成中间图像。
头部姿势估计
一旦我们知道了一些面部地标点,那么我们也可以估计头部的姿势。
MediaPipe Face Mesh
即使在移动设备上,MediaPipe Face Mesh 也可以实时估计 468 个 3D 面部地标。通过应用机器学习 (ML) 来推断 3D 表面几何形状,它只需要单个相机输入,而无需专用的深度传感器。它提供了更好的实时性能。
面部地标模型
3D 面部地标模型使用迁移学习,并在具有不同目标的网络上进行训练:该网络预测合成渲染数据上的 3D 地标坐标。由此产生的网络在现实世界的数据上表现得相当好。
3D 地标网络将输入作为裁剪的视频帧,而无需额外的深度输入。该模型输出 3D 点的位置,在输入中合理对齐。
几何管线
几何管线是一个关键组件,它估计 3D Metric 空间内的几何对象。在每一帧上,分别执行以下步骤:
得到Metric 3D空间坐标,即将面部地标屏幕坐标转换为Metric 3D空间坐标。 面部姿态变换矩阵被估计为来自标准面部度量界标的刚性线性映射,然后将其发送到运行时面部度量界标中,以最小化两者之间的差异。 运行时面部度量地标创建一个面部网格。
让我们来实现它
首先,让我们检查我们的网络摄像头 ID 是否工作正常,并在输出屏幕上打印每秒帧数 (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\)
如果你有网络摄像头,它应该会打开一个窗口,否则你可以在VideoCapture
功能中指定视频路径而不是零。
在左上角,你可以看到 FPS(变化),如下所示。
现在让我们创建一个新的 python 文件并开始创建我们的面部地标检测模块。
安装所需的模块。
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\)
在上面的代码中,我们从网络摄像头获取输入,变量NUM\_FACE
表示有多少面部要从帧中检测和定位面部地标。
要绘制面部点,我们使用mpDraw
变量。我们将使用mp.solutions.face\_mesh
来创建面部网格。
为了控制连接线和点的粗细,我们将使用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 循环中读取帧并将帧转换为 RGB,将该图像传递给*faceMesh.process()
,* 然后在面部绘制检测到的地标。
为了看到468 个面部地标,取消对for loop 中的cv2.putText\(\)
函数的注释。语句 print \(id, x, y\)
将打印出 id 和坐标。然后输出如下。
现在为了创建一个模块,以便我们可以在不同的项目中使用它,首先我们需要创建一个包含函数的类。
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\(\)
结论
在上面的代码中,函数名称是*“findFaceLandmarks”,它检测面部地标并执行与上述相同的功能。类“FaceLandMarks()”* 取静态模式中,面部的最大数量和最小检测置信度和最小的跟踪置信度。然后创建 函数来运行代码。
完整代码: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
如果觉得有用,就请分享到朋友圈吧!
公众号后台回复“ICCV2021”获取最新论文合集~
# CV技术社群邀请函 #
备注:姓名-学校/公司-研究方向-城市(如:小极-北大-目标检测-深圳)
即可申请加入极市目标检测/图像分割/工业检测/人脸/医学影像/3D/SLAM/自动驾驶/超分辨率/姿态估计/ReID/GAN/图像增强/OCR/视频理解等技术交流群
每月大咖直播分享、真实项目需求对接、求职内推、算法竞赛、干货资讯汇总、与 10000+来自港科大、北大、清华、中科院、CMU、腾讯、百度等名校名企视觉开发者互动交流~