拓冰建站拓冰建站
首页 / 资讯中心 / 正文

基于Python的人脸关键点驾驶员疲劳检测预警系统设计

简介这是一份面向计算机专业毕业设计的人脸识别驾驶员疲劳检测与预警系统项目基于Python与卷积神经网络实现适合正在完成课程设计、毕业设计或希望实战深度学习的开发者。资源内含完整源码、训练好的模型权重、图片测试样本与数据集压缩包涵盖数据增强、SSD网络构建、损失函数、模型训练与评估、摄像头及视频实时检测等关键模块可帮助快速搭建一套可运行的疲劳预警系统。压缩包共37个文件包含16个Python源码文件、3个模型权重文件、5张测试图片及数据处理脚本等整体约500MB文件分类清晰便于按功能查阅。已有54人学习下载项目经导师指导并调试通过具备较高的参考价值和可复用性适合需要完整项目思路与工程实现参考的读者。1. 基于Python的人脸识别驾驶员疲劳检测与预警系统到底要解决什么这个标题的组合看起来复杂实际要做的只有三件事从摄像头或视频流里定位脸用人眼动作判断驾驶员是否疲劳一旦疲劳立即给出提示。很多人一上来就去找人脸识别代码然后在YouTube或GitHub上复制一段OpenCV截图最后发现眼神飘忽、光线一暗就报警乱响。原因往往是只做了人脸检测没有把“眼睛闭合、打哈欠、头部俯仰”这几个疲劳指标量化出来。这套系统在毕设和工程原型里都常见区别只在于完成度。对课程设计和本科毕设来说源码、数据和模型三样东西分别对应检测模块、回测样本和特征提取器对想往嵌入式DMS方向走的人Python版本就是算法原型后续再迁移到C或NPU逻辑完全一致。我会按“指标定义→模型与数据→实时工程→调优打包”的顺序把一次完整的设计过程讲清楚。中间会用dlib、OpenCV和Keras写可运行代码这些代码量不大但每一行都能在答辩时对应到一个实际参数。2. 人脸关键点检测与疲劳指标的计算原理我先说结论疲劳检测的核心不是人脸识别而是人脸关键点。人脸识别回答“这个人是谁”疲劳检测回答“眼睛、嘴巴和头部的几何状态是怎样的”。把关键点之间的几何关系量化成指标才能对连续视频帧做比较和判定。相比直接扔一张图给CNN做二分类这套方法更可控也更容易解释。2.1 关键点模型68点还是468点常见做法是用dlib的68个关键点预测器或者MediaPipe FaceMesh的468点网格。68点的优势是索引稳定EAR、MAR、头部姿态的公开实现多出问题容易搜到答案468点侧脸鲁棒性更高点更密但在额头、耳朵附近的坐标语义和dlib不完全一致要重新映射指标。我一般建议第一版用dlib因为查资料成本低如果要做真实的驾驶员状态检测再用MediaPipe替换。下面的表格是我在两个项目里用到的选型对比也适合作为答辩时的设计依据。模块代表工具关键点数量CPU帧率参考选型理由人脸检测dlib HOG Linear SVM人脸框30FPS以上无需GPU离线可用人脸关键点dlib shape_predictor_686820FPS以上索引文档丰富容易集成人脸网格MediaPipe FaceMesh46825FPS以上支持3D坐标适合头部姿态轻量分类MobileNetV2二分类取决输入尺寸压缩模型后可在边缘设备运行2.2 眼睛纵横比EAR的定义与Python实现眼睛闭合程度最常用的指标是EAREye Aspect Ratio。它取每只眼睛6个关键点计算上下眼睑的欧氏距离与左右眼角的距离之比。人眼正常睁开时EAR在0.25到0.35之间完全闭合时通常低于0.1。dlib的68点模型里左眼索引是36到41右眼是42到47。import cv2 import dlib from scipy.spatial import distance def eye_aspect_ratio(eye): # eye 是6个关键点坐标顺序为外眼角、上眼皮、内眼角、下眼皮 vertical_1 distance.euclidean(eye[1], eye[5]) vertical_2 distance.euclidean(eye[2], eye[4]) horizontal distance.euclidean(eye[0], eye[3]) return (vertical_1 vertical_2) / (2.0 * horizontal) left_eye_idx [36, 37, 38, 39, 40, 41] right_eye_idx [42, 43, 44, 45, 46, 47]eye_aspect_ratio入参是6个(x, y)坐标列表返回的就是EAR。分子取两组竖直距离的平均分母用外眼角到内眼角的水平距离做归一化。水平距离对眼皮肌肉变化不敏感所以即使人脸在画面中稍远或稍近EAR仍能稳定反映开合程度。在实时检测中一般取左右眼中较小的EAR值因为侧脸时一侧眼睛被遮挡会使平均值虚高。2.3 嘴部纵横比MAR和头部俯仰角打哈欠是疲劳的另一个强特征。嘴部MAR与EAR结构相同但使用嘴部外轮廓点。当MAR连续多帧大于0.8就计数一次哈欠。头部姿态则用cv2.solvePnP把二维关键点与三维人脸模型点做透视求解得到俯仰角。俯仰角持续低于-15度可以当作驾驶员低头或瞌睡的先兆但抬头障碍物等场景要额外排除否则容易误报。2.4 PERCLOS从单帧判断到时间段判断单纯看一帧EAR非常不稳定眨眼、光线闪动都会造成瞬时突变所以工程上一定要算PERCLOS即单位时间内眼睛闭合所占比例。经典定义是P80指眼睑遮盖瞳孔超过80%的时间比例工程实现时一般用“EAR低于阈值”来近似。下面的代码用一个60帧滑动窗口保存闭眼状态窗口满后重置最早那一帧。EAR_THRESH 0.25 WINDOW_SIZE 60 eye_closed_history [] def calculate_perclos(current_ear): closed 1 if current_ear EAR_THRESH else 0 eye_closed_history.append(closed) if len(eye_closed_history) WINDOW_SIZE: eye_closed_history.pop(0) return sum(eye_closed_history) / len(eye_closed_history)WINDOW_SIZE60是针对约30FPS摄像头设计的代表2秒时间窗。PERCLOS超过0.4再进入预警状态能过滤掉单次眨眼和短暂低头。这个0.4不是拍脑袋后面回测时会把不同阈值下的准确率拉出来比较。3. 疲劳检测模型选型、公开数据与轻量分类器训练有了EAR和PERCLOS还缺一个关键环节怎么稳定地拿到眼睛、嘴巴的关键点。这里有现成模型也有需要自己训练的小模型。很多人误以为疲劳检测必须训练一个大网络实际恰恰相反绝大部分计算开销都在人脸检测和关键点上最后的疲劳判定只是几十行阈值逻辑。3.1 现成模型怎么选商用许可怎么判断dlib的shape_predictor_68_face_landmarks.dat权重文件是公开可下载的离线模型不依赖云服务运行时不传任何人脸图像到外部网络带宽有限的车载场景能用。MediaPipe FaceMesh可以在CPU上跑且自带3D坐标适合头部姿态估计。如果是做人脸检测而非识别还可以替换为人脸检测的开源模型比如OpenCV 4.x内置的YuNet人脸检测器模型文件只有几百KB适合配置较低的机器。选择时重点看三点是否可离线、是否是免费商用、许可证是否允许修改。答辩时被问“为什么不用云API”就回答“驾驶场景断网偏多本地方案时延更稳定”。3.2 公开数据组织YawDD、CEW和自制视频疲劳检测通常不直接训练“疲劳还是清醒”的整图分类器而是先训练“眼睛闭合还是睁开”的小分类器。公开数据集里YawDD是驾驶员打哈欠视频CEW包含闭眼和睁眼图像NTHU-DDD包含多种疲劳行为。用这些数据训练眼睛分类器时建议把每只眼睛通过人脸关键点裁剪成固定尺寸图像统一存成两个文件夹open、closed。目录组织方式越简单后面做数据增强和模型读取越省事。我见过不少项目把原始视频直接扔给模型训练这种做法样本量需求很大容易过拟合。正确的做法是先用关键点把眼睛区域扣出来再用分类器判断开闭。OpenCV本身不能区分左右眼但dlib的关键点索引可以把每帧眼睛区域保存到对应标签目录就行。3.3 用Keras微调一个眼睛状态分类器如果决定自己训练最稳的方案是迁移学习而不是从零搭CNN。用MobileNetV2作为骨干输入尺寸设为64x64只训练最后几层。import tensorflow as tf from tensorflow.keras.preprocessing.image import ImageDataGenerator train_datagen ImageDataGenerator( rescale1.0 / 255, rotation_range5, width_shift_range0.1, height_shift_range0.1, horizontal_flipFalse ) train_generator train_datagen.flow_from_directory( data/eye/train, target_size(64, 64), batch_size32, class_modebinary ) base_model tf.keras.applications.MobileNetV2( input_shape(64, 64, 3), include_topFalse, weightsimagenet ) base_model.trainable False model tf.keras.Sequential([ base_model, tf.keras.layers.GlobalAveragePooling2D(), tf.keras.layers.Dropout(0.2), tf.keras.layers.Dense(1, activationsigmoid) ]) model.compile( optimizertf.keras.optimizers.Adam(learning_rate1e-3), lossbinary_crossentropy, metrics[accuracy] )这段代码只说两件事。第一flow_from_directory直接按文件夹名称生成标签不需要手写CSV适合原型验证。第二base_model.trainableFalse冻结预训练权重避免在小数据集上把Imagenet特征破坏掉。学习率设1e-3batch_size设32这两项在分类项目里是稳定起点。训练完成后用model.save(eye_classifier.h5)保存之后在检测主循环里做二分类对关键点跟踪冻结或人脸快速移出画面的帧尤其有效。3.4 关键点模型和分类器如何协同关键点模型负责回归位置眼睛分类器负责判断状态。EAR特征适合做连续帧的数值监控分类器适合在EAR处于0.15到0.25之间的模糊地带做二次确认。两者叠加后疲劳判定的准确率会显著高于只用一个指标。推理时只对较模糊的帧调用分类器能控制CPU占用。4. 实时疲劳预警系统的工程实现前面的指标和模型都是静态验证真正让系统跑起来的是实时主循环。主循环看起来简单读取摄像帧、做人脸检测、提取眼关键点、算EAR、更新PERCLOS、触发预警。但直接写出来的代码大概率会出现误报和报警刷屏因此还需要帧率控制、状态机防抖和报警冷却。4.1 视频输入与帧率控制使用cv2.VideoCapture可以同时支持摄像头和已录制的视频文件。测试时建议加一个--source参数输入本地视频路径这样就能反复验证同一场景下的参数。摄像头默认30FPS但关键点检测和EAR计算会吃掉一部分时间实际处理帧率可能只有15FPS所以不要把时间窗口写成固定帧数最好用真实时间戳。cap cv2.VideoCapture(0) # 0表示默认摄像头 fps cap.get(cv2.CAP_PROP_FPS) if fps 0: fps 30.0CAP_PROP_FPS读取不到时返回0此时要手动设定位30.0。车载环境下分辨率和曝光不同读取不到是常态。把FPS作为参数传入PERCLOS窗口计算可以让窗口长度跟随真实帧率变化。4.2 疲劳状态机为什么不能只看当前帧如果每帧EAR一低于阈值就提醒整个系统会像闹钟一样响个不停。眨眼本身就会让EAR短暂低于0.25必须要求连续N帧闭眼才判断为一次闭眼事件。与此同时疲劳预警需要另一个独立的计数在一分钟或两分钟内出现多次闭眼事件或者单次闭眼事件持续超过2秒。我习惯用两个计数器组成状态机一个负责“当前是否闭眼”一个负责“是否已经疲劳”。考虑连续闭眼超过40帧才进入预警状态等EAR恢复正常并持续30帧后再退出预警。这个逻辑在工程上叫滞回比较可以避免临界值附近的抖动。直接写成if ear thresh的代码会忽略这种抖动这是很多“高分毕设”源码里隐藏的扣分点。下面是一个简化版状态机实现FRAME_CONSECUTIVE 40 CLOSED_QUIET_FRAMES 30 EAR_THRESH 0.25 closed_count 0 quiet_count 0 alert_active False while True: ear compute_ear(landmarks) if ear EAR_THRESH: closed_count 1 else: closed_count 0 if closed_count FRAME_CONSECUTIVE: alert_active True if alert_active: if ear EAR_THRESH: quiet_count 1 else: quiet_count 0 if quiet_count CLOSED_QUIET_FRAMES: alert_active False quiet_count 0FRAME_CONSECUTIVE设为40以25FPS计算大约代表1.6秒持续闭眼这个值对应“睡着”而不是“眨眼”。alert_active一旦置位即使中间有几帧眼睛睁开也不会立刻取消必须恢复睁眼状态并持续30帧才解除。这种状态机把疲劳与正常眨眼彻底分开同时保留了主动恢复能力。4.3 声音预警与可视化面板预警不能只靠屏幕变红驾驶场景下声音提示更重要。Windows环境可以直接用winsound.Beep跨平台建议用playsound播放本地音频文件。声音触发时要注意冷却时间同一轮疲劳周期内不要反复播放否则听觉疲劳后反而忽略告警。import winsound last_alert_time 0 ALERT_INTERVAL 5 # 秒 if alert_active and (time.time() - last_alert_time ALERT_INTERVAL): winsound.Beep(1000, 800) last_alert_time time.time()1000, 800分别表示频率和时长1000Hz持续800ms是比较醒目的提示。ALERT_INTERVAL5做报警节流避免一帧级误触导致连续响笛。可视化面板上我一般画三样东西人脸框、左右眼EAR数值、疲劳状态文本和剩余冷却时间。4.4 把完整流程串成一个类不要把全部代码堆在while循环里。把身份识别、关键点检测、状态机分别封装成类主循环只负责单帧推理。这样做的好处是回测时可以直接把摄像头对象替换成视频文件代码不用改结构。类之间的依赖关系是单向的主控类调检测器检测器输出关键点疲劳类消费关键点。想换检测模型只要保证输出的关键点索引一致。5. 阈值回测、参数调优与演示打包技巧很多项目跑通就结束参数全凭初始猜测。真正能拿高分的地方在于用视频回测数据证明你的阈值有效并把演示环境做成开箱即用。5.1 回测不依赖实时视频评估阈值先准备一段包含正常驾驶、眨眼、打哈欠和闭眼疲劳的短视频逐帧标注0或1表示疲劳。回测脚本将这段视频逐帧送入系统记录每帧的EAR、PERCLOS和最终报警状态与人工标注对比计算出准确率、召回率和漏检率。python evaluate.py --source test_video.mp4 --label test_label.csv \ --ear-thresh 0.25 --perclos-thresh 0.4 --window-size 60evaluate.py输出一个CSV包含帧号、EAR、预测标签、真实标签。用这些数据画出误报曲线后再回到实时程序里调整阈值。这一步的价值明显优于现场改参数因为同一段视频可以反复比较不同阈值。5.2 三个必调参数的经验值参数作用常见区间调整方向EAR_THRESH区分睁眼和闭眼0.20-0.30戴眼镜或小眼时调低PERCLOS_THRESH判定疲劳的时间占比0.30-0.50误报多调高漏检多调低FRAME_CONSECUTIVE连续闭眼帧数下限30-50帧低头看手机可能触发的场景调高戴眼镜场景下反光会让上下眼皮距离被拉大EAR数值整体偏高此时把EAR_THRESH从0.25降到0.20更合理。反过来晚上开车光线很差关键点抖动厉害FRAME_CONSECUTIVE要适当提高避免把暗光下的噪声当成闭眼。5.3 环境配置与二进制打包最终交付时需要让老师能在自己电脑上直接运行而不是现场配置TensorFlow。先写一个requirements.txt固定关键版本然后使用虚拟环境安装。常见坑在于dlib的安装需要CMake和Visual C Build Tools在Windows上这一步失败率最高我的建议是直接安装预编译的dlib-bin省去编译时间。python -m venv venv source venv/bin/activate # Windows 下是 venv\Scripts\activate pip install -r requirements.txt pyinstaller --onefile --windowed --add-data shape_predictor_68_face_landmarks.dat;. \ --add-data eye_classifier.h5;. main.pyPyInstaller打包时--add-data会把模型文件放进资源目录。但需要注意运行时用相对路径读取模型会失效代码里要用sys._MEIPASS重新拼接模型路径。最后演示时准备一段预录制视频并指定--source video.mp4比现场找摄像头更稳因为环境光线和摄像头驱动都不是你能掌控的。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门