疲劳检测系统详解与实现(驾驶员/课堂监控)
摘要
疲劳检测是一类通过分析人体行为(如眼睛闭合、头部姿态、打哈欠等)来判断个体是否处于疲劳或注意力不集中的技术。它在驾驶员监控、驾驶安全、课堂学员状态检测、远程办公督导等场景中具有重要应用价值。本文以基于人脸关键点的眼睛纵横比(EAR, Eye Aspect Ratio)方法为基础,详细介绍原理、代码实现、参数调优、实际部署建议与改进方向,文末给出完整可运行的示例代码(含中文显示与眼睛凸包绘制)。
1. 背景与动机
疲劳导致的注意力下降会显著增加事故风险。传统基于车辆行驶特征(如车道偏离、方向盘行为)的疲劳检测受限于车辆传感器;基于生理信号(如EEG、心率)的方案则需要佩戴设备。视觉感知方案利用摄像头采集面部图像,通过计算机视觉方法无侵入地判断疲劳状态,部署成本较低、易扩展,是当前研究与工程应用的主流方向之一。
应用场景举例:
-
驾驶员监控(车载摄像头):及时提醒疲劳驾驶的驾驶员,或与车辆控制系统联动减速。
-
学员上课状态检测:判断学生是否走神、打瞌睡,辅助教师或远程学习平台统计学习投入度。
-
工业作业监控:对高危岗位作业人员进行实时监护。
2. 方法概述:基于EAR的闭眼检测
眼睛纵横比(EAR)由Soukupová 和 Čech 提出,基于眼睛的 6 个关键点位置计算。EAR 的优点在于:计算量小、对头部轻微转动具有鲁棒性、实时性好。其计算公式为:
EAR = \\frac{\|p_2 - p_6\| + \|p_3 - p_5\|}{2 \\cdot \|p_1 - p_4\|}
其中 p1~p6 为眼睛轮廓的 6 个关键点(见文中代码注释)。当眼睛闭合时,EAR 会显著下降;通过对连续帧的 EAR 设阈值与持续帧数,可以区分瞬时眨眼与真正的闭眼(疲劳/打瞌睡)。
优点与局限
-
优点:无需训练复杂模型,简单高效,适合资源受限场景。
-
局限:对遮挡(手、眼镜的强反光)、极端侧脸与光照变化敏感;单纯使用 EAR 无法区分打瞌睡与短暂低头等情况,通常需要结合头部姿态与 yaw/pitch/roll 或面部表情(如打哈欠)来提升准确率。
3. 代码实现说明(含关键步骤讲解)
下面的示例代码实现了基于 dlib 的人脸检测与 68 点关键点定位,计算左右眼 EAR,绘制眼睛凸包并在疲劳(连续闭眼超过阈值)时给出中文报警提示。代码包含:
-
eye_aspect_ratio(eye)
:计算单侧眼睛 EAR; -
cv2AddChineseText
:使用 PIL 在 OpenCV 图像上绘制中文(需要本地字体如 simsun.ttc); -
drawEye
:绘制眼眶凸包以便可视化; -
主循环:获取摄像头帧、检测人脸、计算 EAR、判断是否疲劳并报警。
import numpy as np
import dlib
import cv2
from sklearn.metrics.pairwise import euclidean_distances # 计算欧氏距离
from PIL import Image, ImageDraw, ImageFont # pip install pillowdef eye_aspect_ratio(eye):
'''-------------计算眼睛纵横比-------------
# 1 2
# 0 3 <----这是眼睛的6个关键点
# 5 4
'''
A = euclidean_distances(eye[1].reshape(1, 2), eye[5].reshape(1, 2))
B = euclidean_distances(eye[2].reshape(1, 2), eye[4].reshape(1, 2))
C = euclidean_distances(eye[0].reshape(1, 2), eye[3].reshape(1, 2))
ear = ((A + B) / 2.0) / C # 纵横比
return eardef cv2AddChineseText(img, text, position, textColor=(0, 255, 0), textSize=30):
"""向图片中添加中文"""
if (isinstance(img, np.ndarray)): # 判断是否OpenCV图片类型
img = Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB)) # 步骤array转image的转换
draw = ImageDraw.Draw(img) # 在图片上创建一个绘图的对象
# 字体的格式
fontStyle = ImageFont.truetype("simsun.ttc", textSize, encoding="utf-8")
draw.text(position, text, textColor, font=fontStyle) # 绘制文本
return cv2.cvtColor(np.asarray(img), cv2.COLOR_RGB2BGR) # 转换回OpenCV格式def drawEye(eye): # 绘制眼眶凸包
eyeHull = cv2.convexHull(eye)
cv2.drawContours(frame, [eyeHull], -1, (0, 255, 0), 1)COUNTER = 0 # 闭眼持续次数统计
detector = dlib.get_frontal_face_detector() # 构造脸部位置检测器
predictor = dlib.shape_predictor("shape_predictor_68_face_landmarks.dat") # 读取人脸关键点定位模型
cap = cv2.VideoCapture(0)while True:
ret, frame = cap.read()
faces = detector(frame, 0) # 获取人脸
for face in faces: # 循环遍历每一个人脸
shape = predictor(frame, face) # 获取关键点
# 将关键点转换为坐标(x,y)的形式
shape = np.array([[p.x, p.y] for p in shape.parts()])
rightEye = shape[36:42] # 右眼,关键点索引从36到41(不包含42)
leftEye = shape[42:48] # 左眼,关键点索引从42到47(不包含48)
rightEAR = eye_aspect_ratio(rightEye) # 计算右眼纵横比
leftEAR = eye_aspect_ratio(leftEye) # 计算左眼纵横比
ear = (leftEAR + rightEAR) / 2.0 # 均值处理if ear < 0.3: # 小于0.3认为闭眼,也可能是眨眼 COUNTER += 1 # 每检测到一次,将+1 if COUNTER >= 50: # 持续50帧都闭眼,则警报 frame = cv2AddChineseText(frame, "!!!!!危险!!!!! ", (250, 250)) # 宽高比>0.3,则计数器清零、解除疲劳标志 else: COUNTER = 0 # 闭眼次数清零 drawEye(leftEye) # 绘制左侧凸包 drawEye(rightEye) # 绘制右侧凸包 info = "EAR: {:.2f}".format(ear[0][0]) frame = cv2AddChineseText(frame, info, (0, 30)) # 显示眼睛闭合程度值 cv2.imshow("Frame", frame) # 捕获按键 key = cv2.waitKey(1) # 如果按下Ecs键,则退出(Esc的ASCII码为27) if key == 27: break
cv2.destroyAllWindows()
cap.release()
说明 :示例中使用 shape_predictor_68_face_landmarks.dat
(dlib 提供的 68 点模型),以及 simsun.ttc
字体用于中文显示。请确保这些文件路径在运行环境中可用。
4. 参数与阈值调整建议
关键超参数:
-
EAR 阈值(示例中为 0.3):该值与拍摄角度、相机分辨率、被检测者眼型有关。可通过离线标注数据集(含张眼、闭眼样本)计算 ROC 曲线以选取最优阈值。
-
连续帧数阈值(示例中为 50 帧):与摄像头帧率相关。若帧率为 30 FPS,50 帧约为 1.6 秒;对于疲劳检测通常选择 1--2 秒范围来区分眨眼(短于 0.4s)与长时间闭眼。
调优步骤建议:
-
记录视频/样本,包含正常眨眼、短时闭眼与疲劳闭眼的多样本;
-
在不同阈值下统计误报与漏报率,绘制 F1-Score 或 ROC,从而选择平衡点;
-
在线场景中可启用自适应阈值(例如基于初始化阶段记录的用户基线 EAR 的均值和方差进行动态归一化)。
5. 抗干扰与鲁棒性提升方法
为了在复杂环境(强光、侧脸、眼镜)下仍保持性能,可考虑:
-
使用更强健的人脸关键点检测器(例如基于深度学习的关键点检测网络)来替代 dlib 的 68 点预测器;
-
融合头部姿态估计(yaw/pitch/roll):若头部过度低头或侧转,暂时降低 EAR 的权重并结合面部朝向判断是否为真实疲劳;
-
使用红外或近红外摄像头:在夜间或强逆光环境有明显优势;
-
增加多模态输入:口部张开、打哈欠检测、面部肌肉活动等共同决策以降低单一指标误报率;
-
使用时间序列模型:例如 LSTM 或 1D 卷积对 EAR 序列建模,能够更好地区分短时眨眼与长期闭眼。
6. 实际工程中的部署考量
硬件选择
-
车载场景建议选择宽动态(WDR)摄像头或红外摄像头以适应光照变化;
-
校园/教室场景可以使用普通高清摄像头,注意视角覆盖与分辨率保证眼部关键点的可见性(建议人脸像素高度在 80px 以上)。
性能与延迟
-
EAR 方法极轻量,可在单核 CPU 或边缘设备(如 Raspberry Pi 4、NVIDIA Jetson Nano)上实现实时运行;
-
若采用深度学习关键点检测或多模态融合,建议使用带 GPU 的边缘设备或把计算放在本地服务器上。
隐私与合规
-
摄像头监控涉及个人隐私与法规合规(如 GDPR、各地隐私法),应在部署前明确通知被监控者、限定数据存储时间、并采取加密与权限控制措施;
-
对数据进行去标识化处理仅保存统计结果(如疲劳次数、注意力得分)可降低隐私风险。
7. 常见问题与排查
Q:为何 EAR 值计算异常(例如为 NaN 或极大值)?
- A:通常是因为关键点定位失败或某些点重合。建议在计算前检测关键点是否完整、并加入异常值过滤(例如当 C(水平距离)为 0 或过小,跳过该帧)。
Q:佩戴眼镜会影响检测?
- A:反光或深色镜片会降低关键点检测准确性。解决方式包括使用更多鲁棒的关键点检测器、使用 IR 摄像头或在检测失败时退回基于脸部姿态与其他特征的判断。
Q:如何降低误报(把头低看手机被误判为疲劳)?
- A:结合头部俯仰角(pitch)与面部朝向,当检测到明显低头且眼睛闭合时,先判为"低头行为",结合时间序列判断是否超过疲劳持续阈值后再报警。
8. 改进方向(研究与工程)
-
多模态融合:结合眼睛、嘴部(打哈欠)、头部姿态与驾驶行为数据(方向盘输入)实现更鲁棒的疲劳判定;
-
个体化模型:引入在线学习机制,根据用户基线 EAR 动态调整阈值;
-
轻量化深度模型:使用 MobileNet/ShuffleNet 系列训练专用关键点检测或疲劳分类器,便于在边缘设备上高效运行;
-
异常场景检测:对遮挡、多人场景、夜间等情形建立检测策略及回退机制。
9. 如何评估系统效果
评估指标包括准确率(Accuracy)、精确率(Precision)、召回率(Recall)、F1-Score;对于实时系统,还应统计平均响应延时与误报率(False Alarm Rate)。
建议构建包含多样性(不同年龄、性别、戴/不戴眼镜、不同光照与角度)的测试集,并使用交叉验证评价阈值与模型的泛化能力。
10. 总结与建议
基于 EAR 的疲劳检测是一条工程上可快速落地的路径,适合对实时性要求高、计算资源受限的场景。为了提高鲁棒性,建议将 EAR 与头部姿态、面部表情(如打哈欠)以及时间序列分析结合,采用多模态判定策略;在部署前则需考虑隐私合规与用户告知机制。
如果你希望,我可以:
-
将上述代码改写为模块化的可复用函数(支持多人检测、日志记录与事件回放);
-
帮你设计实验流程与标注工具来调参并评估模型;
-
给出基于深度关键点检测器或轻量化网络的替代实现示例(含性能对比)。
依赖与运行提示
-
Python 版本建议 3.7+;
-
依赖库:
dlib
,opencv-python
,numpy
,scikit-learn
(或直接使用numpy.linalg.norm
替代欧氏距离函数)、Pillow
; -
需要下载
shape_predictor_68_face_landmarks.dat
(dlib 官方提供)并确保simsun.ttc
或其他中文字体可用。
本文档包含完整代码与详细注释,方便复制运行与工程化改造。