面部标志检测:用OpenCV和树莓派实战68个关键点
面部标志检测,简单来说就是识别出脸上眉毛、眼睛、鼻子、嘴巴、下巴这些关键部位的位置。这项技术在很多场景中都有用武之地,比如人脸识别、表情分析、甚至美颜滤镜。今天咱们就用OpenCV和树莓派来实战一下,用dlib预训练的68个点面部标志检测器,配合Python OpenCV把检测结果可视化出来。
所需组件
先看看需要哪些家伙:
硬件:树莓派3和Pi摄像头模块。
软件:OpenCV、dlib、imutils、numpy,还有Python3。OpenCV负责图像处理,数字图像处理最常见的应用包括物体检测、人脸识别和人数统计;dlib则提供预训练的机器学习算法;imutils让OpenCV的一些操作更简便。

Pi摄像头与树莓派的接线细节,可以参考之前的教程(这里不再赘述)。
在树莓派中安装OpenCV
安装之前先更新系统:
sudo apt-get update
接着安装OpenCV所需的依赖:
sudo apt-get install libhdf5-dev -y
sudo apt-get install libhdf5-serial-dev -y
sudo apt-get install libatlas-base-dev -y
sudo apt-get install libjasper-dev -y
sudo apt-get install libqtgui4 -y
sudo apt-get install libqt4-test -y
然后安装OpenCV:
pip3 install opencv-contrib-python==4.1.0.25
安装imutils(用于平移、旋转、调整大小、骨架化等图像处理):
pip3 install imutils
安装dlib(机器学习工具包,包含预训练面部标志检测器):
pip3 install dlib
安装NumPy(科学计算核心库):
pip3 install numpy
如何使用dlib检测面部部位
dlib的预训练面部标志检测器可以输出映射到面部结构的68个(x, y)坐标。该预测器在iBUG 300-W数据集上训练,结果可靠。下面这张图标注了每个坐标对应的面部区域:


对Raspberry Pi进行面部地标检测编程
完整代码在文章末尾给出,这里先拆解关键部分。
首先,导入所需库:
from imutils import face_utils
import numpy as np
import argparse
import imutils
import dlib
import cv2
from picamera.array import PiRGBArray
from picamera import PiCamera
初始化摄像头对象,设置分辨率为640×480,帧率30fps:
camera = PiCamera()
camera.resolution = (640, 480)
camera.framerate = 30
rawCapture = PiRGBArray(camera, size=(640, 480))
使用参数解析器指定面部标志预测器的路径:
ap = argparse.ArgumentParser()
ap.add_argument("-p", "--shape-predictor", required=True,
help="面部地标预测路径")
args = vars(ap.parse_args())
初始化基于HOG的dlib面部检测器,并加载预训练的面部特征预测器:
detector = dlib.get_frontal_face_detector()
predictor = dlib.shape_predictor(args["shape_predictor"])
使用capture_continuous从摄像头持续捕获帧:
for frame in camera.capture_continuous(rawCapture, format="bgr", use_video_port=True):
image = frame.array
cv2.imshow("Frame", image)
key = cv2.waitKey(1) & 0xFF
rawCapture.truncate(0)
按下键盘“S”键捕获当前帧。然后调整图像尺寸并转为灰度:
if key == ord("s"):
image = imutils.resize(image, width=300)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
用dlib的检测器发现人脸:
rects = detector(gray, 1)
对每张检测到的人脸,用预测器计算68个面部标志,转换为NumPy数组:
for (i, rect) in enumerate(rects):
shape = predictor(gray, rect)
shape = face_utils.shape_to_np(shape)
遍历每个面部区域(如眉毛、眼睛、嘴巴等),在图像上标注名称,并在对应坐标画红点:
for (name, (i, j)) in face_utils.FACIAL_LANDMARKS_IDXS.items():
clone = image.copy()
cv2.putText(clone, name, (10, 30), cv2.FONT_HERSHEY_SIMPLEX,
0.7, (0, 0, 255), 2)
for (x, y) in shape[i:j]:
cv2.circle(clone, (x, y), 1, (0, 0, 255), -1)
还可以将每个面部部分提取为单独的图像,通过计算边界框并resize到250像素:
(x, y, w, h) = cv2.boundingRect(np.array([shape[i:j]]))
roi = image[y:y + h, x:x + w]
roi = imutils.resize(roi, width=250, inter=cv2.INTER_CUBIC)
最后,显示该部分的单独图像和带标注的全图,按ESC键切换面部区域:
cv2.imshow("ROI", roi)
cv2.imshow("Image", clone)
cv2.waitKey(0)
测试面部识别器
创建一个文件夹,进入目录:
mkdir face-part-detector
cd face-part-detector
从官方链接下载shape_predictor_68_face_landmarks.dat文件,放入该目录。新建detect.py,粘贴下面的完整代码。
运行命令:
python3 detect.py --shape-predictor shape_predictor_68_face_landmarks.dat

你会看到一个实时摄像窗口。按下“S”键捕获一帧,红点会标注在面部区域(默认先显示嘴巴)。用ESC键切换到其他部位,比如眉毛、眼睛、鼻子等。
完整代码
from imutils import face_utils
import numpy as np
import argparse
import imutils
import dlib
import cv2
from picamera.array import PiRGBArray
from picamera import PiCamera
from PIL import Image
camera = PiCamera()
camera.resolution = (640, 480)
camera.framerate = 30
rawCapture = PiRGBArray(camera, size=(640, 480))
# 构造参数解析器并解析参数
ap = argparse.ArgumentParser()
ap.add_argument("-p", "--shape-predictor", required=True,
help="面部地标预测路径")
args = vars(ap.parse_args())
detector = dlib.get_frontal_face_detector()
predictor = dlib.shape_predictor(args["shape_predictor"])
for frame in camera.capture_continuous(rawCapture, format="bgr", use_video_port=True):
image = frame.array
cv2.imshow("Frame", image)
key = cv2.waitKey(1) & 0xFF
rawCapture.truncate(0)
if key == ord("s"):
image = imutils.resize(image, width=300)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
rects = detector(gray, 1)
# 循环人脸检测
for (i, rect) in enumerate(rects):
# 确定面部区域的面部标志
shape = predictor(gray, rect)
shape = face_utils.shape_to_np(shape)
# 分别循环面部部分
for (name, (i, j)) in face_utils.FACIAL_LANDMARKS_IDXS.items():
# 在图像上显示人脸部分的名称
clone = image.copy()
cv2.putText(clone, name, (10, 30), cv2.FONT_HERSHEY_SIMPLEX,
0.7, (0, 0, 255), 2)
# 在特定的面部部分绘制圆圈
for (x, y) in shape[i:j]:
cv2.circle(clone, (x, y), 1, (0, 0, 255), -1)
# 提取人脸区域的ROI作为单独的图像
(x, y, w, h) = cv2.boundingRect(np.array([shape[i:j]]))
roi = image[y:y + h, x:x + w]
roi = imutils.resize(roi, width=250, inter=cv2.INTER_CUBIC)
# 显示特定的面部部分
cv2.imshow("ROI", roi)
cv2.imshow("Image", clone)
cv2.waitKey(0)
# 可视化所有面部地标
for (x, y) in shape:
cv2.circle(image, (x, y), 1, (0, 0, 255), -1)
cv2.imshow("Image", image)
cv2.waitKey(0)