介绍
MediaPipe 是一款由 Google Research 开发并开源的多媒体机器学习模型应用框架。 基于Mediapipe实现打哈欠检测,这里以静态图片为例,如需视频实时检测,只需通过opencv对每帧应用相同的思路即可。
实现原理
- 人脸检测:检测得到图像中人脸的位置
- 人脸关键点提取:人脸468/478关键点提取
- 通过特定关键点距离判断是否打哈欠
代码
基于Mediapipe进行人脸检测
# 参数定义
STATIC_IMAGE_MODE = False
MAX_NUM_FACES = 3
REFINE_LANDMARKS=True
MIN_DETECTION_CONFIDENCE=0.5
# 人脸关键点检测
mp_face_mesh = mp.solutions.face_mesh
mp_drawing = mp.solutions.drawing_utils
mp_drawing_styles = mp.solutions.drawing_styles
# 面网
def face_mesh_show(img_path):
print(f'正在处理图像: {os.path.split(img_path)[-1]}')
img = cv2.imread(img_path) # opencv加载的图片通道为BGR
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # 将通道转换为RGB
# 关键点检测器
face_mesh = mp_face_mesh.FaceMesh(
static_image_mode=STATIC_IMAGE_MODE, # 检测静态图片
max_num_faces=MAX_NUM_FACES,
refine_landmarks=REFINE_LANDMARKS,
min_detection_confidence=MIN_DETECTION_CONFIDENCE,
)
# 获取关键点
results = face_mesh.process(img_rgb)
all_landmarks = results.multi_face_landmarks # 所有人脸的关键点信息
# 处理检测到的每张人脸的关键点
if all_landmarks:
for i, face_landmarks in enumerate(all_landmarks):
print(f'detection: {i}, landmark num: {len(face_landmarks.landmark)}')
mp_drawing.draw_landmarks(
image=img,
landmark_list=face_landmarks,
connections=mp_face_mesh.FACEMESH_IRISES, # 虹膜
landmark_drawing_spec=mp_drawing.DrawingSpec(color=(0, 255, 255), thickness=1, circle_radius=1),
connection_drawing_spec=mp_drawing_styles.get_default_face_mesh_iris_connections_style(),
)
cv2.imshow('face mesh', img)
cv2.waitKey(0)
通过Mediapipe提供的人脸关键点检测器检测后,遍历图像中的每张人脸并进行面网绘制即可。
FaceMesh参数说明:
- static_image_mode=False:检测静态图片设置为False,检测视频设置为True,默认为False
- max_num_faces=1:能检测的最大人脸数,默认为1
- refine_landmarks=False:定位嘴唇、眼睛、瞳孔的关键点,设置为True,否则设置为False,共检测到478个关键点,当模型配置refine_landmarks=False时,得到468个关键点。
- min_detection_confidence=0.5:人脸检测的置信度
- min_tracking_confidence=0.5:人脸追踪的置信度(检测图像时可以忽略)
打哈欠检测
该图是整个人脸关键点分布图中的嘴唇部分,如图所示,通过61、81、311、291、178、402这6个关键点的位置,通过计算关键点81和178、311和402两者的距离并取平均数,然后再除以关键点61和291之间的距离,如果最后得到的结果大于0.5(阈值),则判断为打哈欠,这里判断的逻辑也比较简单,就是比较一下嘴巴张开的大小除以嘴唇的宽度。
multi_face_landmarks中以类似列表的结构(可迭代对象)存储了关键点,结构中每个“landmark”关键字段存储了关键点的坐标,包含x, y, z, x和y分别通过图像宽度和高度归一化为[0.0,1.0] z表示关键点深度,以头部中心的深度为原点,该值越小,关键点距离相机越近。
# 打哈欠检测
def yawn_detection(img_path, threshold=0.5):
img = cv2.imread(img_path)
img_height, img_width, _ = img.shape
face_mesh = mp_face_mesh.FaceMesh(
static_image_mode=STATIC_IMAGE_MODE,
max_num_faces=3,
refine_landmarks=True,
min_detection_confidence=0.5,
)
results = face_mesh.process(img)
all_landmarks = results.multi_face_landmarks
if all_landmarks:
for i, face_landmarks in enumerate(all_landmarks):
print(f'detection: {i}, landmark num: {len(face_landmarks.landmark)}')
# 得到61、81、311、291、402、178这6个关键点的位置
lm61 = face_landmarks.landmark[61]
lm81 = face_landmarks.landmark[81]
lm311 = face_landmarks.landmark[311]
lm291 = face_landmarks.landmark[291]
lm402 = face_landmarks.landmark[402]
lm178 = face_landmarks.landmark[178]
lm_dict = {'landmark-61': (int(img_width * lm61.x), int(img_height * lm61.y)),
'landmark-81': (int(img_width * lm81.x), int(img_height * lm81.y)),
'landmark-311': (int(img_width * lm311.x), int(img_height * lm311.y)),
'landmark-291': (int(img_width * lm291.x), int(img_height * lm291.y)),
'landmark-402': (int(img_width * lm402.x), int(img_height * lm402.y)),
'landmark-178': (int(img_width * lm178.x), int(img_height * lm178.y)),
}
# 利用opencv将这6个关键点绘制在图片中
for lm in lm_dict.values():
cv2.circle(img, lm, radius=1, color=(0, 255, 255), thickness=-1)
# 判断是否打哈欠
value = (get_distance(lm_dict['landmark-81'], lm_dict['landmark-178']) + get_distance(lm_dict['landmark-311'], lm_dict['landmark-402'])) / 2 / get_distance(lm_dict['landmark-61'], lm_dict['landmark-291'])
print(f'value: {value}')
cv2.putText(img, f'yawn: {value > threshold}', org=(20, img.shape[0] - 20), fontFace=cv2.FONT_HERSHEY_SIMPLEX,
fontScale=1, color=(0, 0, 255), thickness=2)
cv2.imshow('yawn_detection', img)
cv2.waitKey(0)
测试(测试图像来自Bing图片):
原始图像:
人脸检测和面网绘制:
打哈欠检测: