实战 | 用Python和MediaPipe搭建一个嗜睡检测系统 (详细步骤 + 源码)

文摘   科技   2024-10-24 20:26   重庆  

点击下方卡片,关注“ OpenCV与AI深度学习

视觉/图像重干货,第一时间送达!

导读
本文将使用Python和MediaPipe搭建一个嗜睡检测系统 (包含详细步骤 + 源码)(公众号:OpenCV与AI深度学习)

背景介绍

    疲劳驾驶的危害不堪设想,据了解,21%的交通事故都因此而生,尤其是高速路上,大多数车辆都是长途驾驶,加之速度快,危害更加严重。

    相关部门一般都会建议司机朋友及时休息调整后再驾驶,避免酿成惨剧。

    作为视觉开发人员,我们可否帮助驾驶人员设计一套智能检测嗜睡的系统,及时提醒驾驶员注意休息?如下图所示,本文将详细介绍如何使用Python和MediaPipe来实现一个嗜睡检测系统。

实现步骤

思路:疲劳驾驶的司机大部分都有打瞌睡的情形,所以我们根据驾驶员眼睛闭合的频率和时间来判断驾驶员是否疲劳驾驶(或嗜睡)。

详细实现步骤
【1】眼部关键点检测。
关于MediaPipe前面已经介绍过,具体可以查看下面链接的文章:
------MediaPipe介绍与手势识别------
我们使用Face Mesh来检测眼部关键点,Face Mesh返回了468个人脸关键点:

    由于我们专注于驾驶员睡意检测,在468个点中,我们只需要属于眼睛区域的标志点。眼睛区域有 32 个标志点(每个 16 个点)。为了计算 EAR,我们只需要 12 个点(每只眼睛 6 个点)。

    以上图为参考,选取的12个地标点如下:

  1. 对于左眼:  [362, 385, 387, 263, 373, 380]

  2. 对于右眼:[33, 160, 158, 133, 153, 144]

    选择的地标点按顺序排列:P 1、 P 2、 P 3、 P 4、 P 5、 P 6 

import cv2import numpy as npimport matplotlib.pyplot as pltimport mediapipe as mp
mp_facemesh = mp.solutions.face_meshmp_drawing = mp.solutions.drawing_utilsdenormalize_coordinates = mp_drawing._normalized_to_pixel_coordinates
%matplotlib inline

获取双眼的地标(索引)点。

# Landmark points corresponding to left eyeall_left_eye_idxs = list(mp_facemesh.FACEMESH_LEFT_EYE)# flatten and remove duplicatesall_left_eye_idxs = set(np.ravel(all_left_eye_idxs)) 
# Landmark points corresponding to right eyeall_right_eye_idxs = list(mp_facemesh.FACEMESH_RIGHT_EYE)all_right_eye_idxs = set(np.ravel(all_right_eye_idxs))
# Combined for plotting - Landmark points for both eyeall_idxs = all_left_eye_idxs.union(all_right_eye_idxs)
# The chosen 12 points: P1, P2, P3, P4, P5, P6chosen_left_eye_idxs = [362, 385, 387, 263, 373, 380]chosen_right_eye_idxs = [33, 160, 158, 133, 153, 144]all_chosen_idxs = chosen_left_eye_idxs + chosen_right_eye_idx

【2】检测眼睛是否闭合——计算眼睛纵横比(EAR)。
要检测眼睛是否闭合,我们可以使用眼睛纵横比(EAR) 公式:

EAR 公式返回反映睁眼程度的单个标量:

    1. 我们将使用 Mediapipe 的 Face Mesh 解决方案来检测和检索眼睛区域中的相关地标(下图中的点P 1 - P 6)。
    2. 检索相关点后,会在眼睛的高度和宽度之间计算眼睛纵横比 (EAR)。
    当眼睛睁开并接近零时,EAR 几乎是恒定的,而闭上眼睛是部分人,并且头部姿势不敏感。睁眼的纵横比在个体之间具有很小的差异。它对于图像的统一缩放和面部的平面内旋转是完全不变的。由于双眼同时眨眼,所以双眼的EAR是平均的。

上图:检测到地标P i的睁眼和闭眼。

底部:为视频序列的几帧绘制的眼睛纵横比 EAR。存在一个闪烁。

首先,我们必须计算每只眼睛的 Eye Aspect Ratio:

|| 表示L2范数,用于计算两个向量之间的距离。

为了计算最终的 EAR 值,作者建议取两个 EAR 值的平均值。

    一般来说,平均 EAR 值在 [0.0, 0.40] 范围内。在“闭眼”动作期间 EAR 值迅速下降。

    现在我们熟悉了 EAR 公式,让我们定义三个必需的函数:distance(…)、get_ear(…)和calculate_avg_ear(…)。

def distance(point_1, point_2):    """Calculate l2-norm between two points"""    dist = sum([(i - j) ** 2 for i, j in zip(point_1, point_2)]) ** 0.5    return dist

get_ear (…)函数将.landmark属性作为参数。在每个索引位置,我们都有一个NormalizedLandmark对象。该对象保存标准化的x、y和z坐标值。

def get_ear(landmarks, refer_idxs, frame_width, frame_height):    """    Calculate Eye Aspect Ratio for one eye.
Args: landmarks: (list) Detected landmarks list refer_idxs: (list) Index positions of the chosen landmarks in order P1, P2, P3, P4, P5, P6 frame_width: (int) Width of captured frame frame_height: (int) Height of captured frame
Returns: ear: (float) Eye aspect ratio """ try: # Compute the euclidean distance between the horizontal coords_points = [] for i in refer_idxs: lm = landmarks[i] coord = denormalize_coordinates(lm.x, lm.y, frame_width, frame_height) coords_points.append(coord)
# Eye landmark (x, y)-coordinates P2_P6 = distance(coords_points[1], coords_points[5]) P3_P5 = distance(coords_points[2], coords_points[4]) P1_P4 = distance(coords_points[0], coords_points[3])
# Compute the eye aspect ratio ear = (P2_P6 + P3_P5) / (2.0 * P1_P4)
except: ear = 0.0 coords_points = None
return ear, coords_points

最后定义了calculate_avg_ear(…)函数:

def calculate_avg_ear(landmarks, left_eye_idxs, right_eye_idxs, image_w, image_h):    """Calculate Eye aspect ratio"""
left_ear, left_lm_coordinates = get_ear( landmarks, left_eye_idxs, image_w, image_h ) right_ear, right_lm_coordinates = get_ear( landmarks, right_eye_idxs, image_w, image_h ) Avg_EAR = (left_ear + right_ear) / 2.0
return Avg_EAR, (left_lm_coordinates, right_lm_coordinates)

    让我们测试一下 EAR 公式。我们将计算先前使用的图像和另一张眼睛闭合的图像的平均 EAR 值。

image_eyes_open  = cv2.imread("test-open-eyes.jpg")[:, :, ::-1]image_eyes_close = cv2.imread("test-close-eyes.jpg")[:, :, ::-1]
for idx, image in enumerate([image_eyes_open, image_eyes_close]): image = np.ascontiguousarray(image) imgH, imgW, _ = image.shape
# Creating a copy of the original image for plotting the EAR value custom_chosen_lmk_image = image.copy()
# Running inference using static_image_mode with mp_facemesh.FaceMesh(refine_landmarks=True) as face_mesh: results = face_mesh.process(image).multi_face_landmarks
# If detections are available. if results: for face_id, face_landmarks in enumerate(results): landmarks = face_landmarks.landmark EAR, _ = calculate_avg_ear( landmarks, chosen_left_eye_idxs, chosen_right_eye_idxs, imgW, imgH )
# Print the EAR value on the custom_chosen_lmk_image. cv2.putText(custom_chosen_lmk_image, f"EAR: {round(EAR, 2)}", (1, 24), cv2.FONT_HERSHEY_COMPLEX, 0.9, (255, 255, 255), 2 ) plot(img_dt=image.copy(), img_eye_lmks_chosen=custom_chosen_lmk_image, face_landmarks=face_landmarks, ts_thickness=1, ts_circle_radius=3, lmk_circle_radius=3 )

结果:

如您所见,睁眼时的 EAR 值为0.28,闭眼时(接近于零)为 0.08

【3】设计一个实时检测系统。

  1. 首先,我们声明两个阈值和一个计数器。

    1. EAR_thresh: 用于检查当前EAR值是否在范围内的阈值。

    2. D_TIME:一个计数器变量,用于跟踪当前经过的时间量EAR < EAR_THRESH.

    3. WAIT_TIME:确定经过的时间量是否EAR < EAR_THRESH超过了允许的限制。 

  2. 当应用程序启动时,我们将当前时间(以秒为单位)记录在一个变量中t1并读取传入的帧。

  3. 接下来,我们预处理并frame通过Mediapipe 的 Face Mesh 解决方案管道。 

  4. 如果有任何地标检测可用,我们将检索相关的 ( Pi )眼睛地标。否则,在此处重置t1 和重置以使算法一致)。D_TIME (D_TIME

  5. 如果检测可用,则使用检索到的眼睛标志计算双眼的平均EAR值。

  6. 如果是当前时间,则加上当前时间和to之间的差。然后将下一帧重置为。EAR < EAR_THRESHt2t1D_TIMEt1 t2

  7. 如果D_TIME >= WAIT_TIME,我们会发出警报或继续下一帧。

参考链接:

https://learnopencv.com/driver-drowsiness-detection-using-mediapipe-in-python/

—THE END—

下载1:Pytorch常用函数手册

在「OpenCV与AI深度学习公众号后台回复:Pytorch函数手册即可下载学习全网第一份Pytorch函数常用手册,包括Tensors介绍、基础函数介绍、数据处理函数、优化函数、CUDA编程、多处理等十四章内容。

下载2:145个OpenCV实例应用代码
在「OpenCV与AI深度学习」公众号后台回复:OpenCV145即可下载学习145个OpenCV实例应用代码(Python和C++双语言实现)。


CV相关内容讨论交流欢迎加入微信交流群

觉得有用,记得点个赞和在看哟 

OpenCV与AI深度学习
专注计算机视觉、深度学习和人工智能领域干货、应用、行业资讯的分享交流!
 最新文章