【Python】基于Mediapipe的打哈欠检测

1,728 阅读3分钟

介绍

MediaPipe 是一款由 Google Research 开发并开源的多媒体机器学习模型应用框架。 基于Mediapipe实现打哈欠检测,这里以静态图片为例,如需视频实时检测,只需通过opencv对每帧应用相同的思路即可。

实现原理

  1. 人脸检测:检测得到图像中人脸的位置
  2. 人脸关键点提取:人脸468/478关键点提取
  3. 通过特定关键点距离判断是否打哈欠

代码

基于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:人脸追踪的置信度(检测图像时可以忽略)

打哈欠检测

image.png 该图是整个人脸关键点分布图中的嘴唇部分,如图所示,通过61、81、311、291、178、402这6个关键点的位置,通过计算关键点81和178、311和402两者的距离并取平均数,然后再除以关键点61和291之间的距离,如果最后得到的结果大于0.5(阈值),则判断为打哈欠,这里判断的逻辑也比较简单,就是比较一下嘴巴张开的大小除以嘴唇的宽度。

multi_face_landmarks中以类似列表的结构(可迭代对象)存储了关键点,结构中每个“landmark”关键字段存储了关键点的坐标,包含x, y, z, xy分别通过图像宽度和高度归一化为[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图片):

原始图像:

image.png image.png

人脸检测和面网绘制:

image.png

打哈欠检测:

image.png