实操教程|使用 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、腾讯、百度等名校名企视觉开发者互动交流~

