尧图精选

Python驾驶员疲劳检测系统源码解析与工程调参实战

🕒 发布时间:2026/10/1 23:31:35 📁 来源:尧图网络
简介一套基于Python与OpenCV/Dlib的驾驶员疲劳检测系统完整源码主要面向计算机视觉方向毕业设计、课程项目以及希望了解软硬结合开发的初学者。系统通过摄像头实时捕捉驾驶员面部画面利用Dlib定位眼睛、鼻子、嘴巴等关键点分析眨眼频率、眼睛闭合时长及头部姿态判断疲劳状态并通过单片机串行通信控制报警装置覆盖从图像采集、特征提取、状态判定到预警触发的完整链路。压缩包共45个文件其中10个py为系统主程序和功能模块20个xml用于界面布局或项目配置另有dat模型、mp3提示音和说明文档整体大小为135.75MB目录结构清晰便于按模块阅读和直接调试。已有61人学习下载。资源内包含完整工程文件、人脸关键点模型和疲劳判定逻辑可帮助读者快速搭建检测Demo理解HOG特征检测、OpenCV图像预处理、串口通信等知识点也为毕业设计中的需求分析、系统集成和答辩演示提供了可运行的参考样例。1. 这个“基于Python的驾驶员面部特征的疲劳检测系统源码”到底值不值得你花一个下午跑通不少来找这套源码的人第一诉求是毕业设计第二诉求是车载场景里“能不能真的预警”。这套方案的核心并不玄学用摄像头拍驾驶员的脸定位眼睛、嘴巴、下巴的几十个关键点再把这些点的几何关系换算成眼睛开合度、嘴巴张合度、头部俯仰角度最后用统计规则判定疲劳状态。它不需要GPU一台普通笔记本在CPU上就能跑出实时帧率适合课题验证、小型监护设备评估也适合想快速落地一个原型验证算法效果的技术人。这类Python人脸疲劳检测方案最大的价值是“可解释”每一个判定结果都能追到具体数值和阈值而不是一个黑匣子神经网络给你一个困意概率。你可以在源码里直接改参数、看实时特征曲线必要的时候还能换成自己的摄像头和视频样本。缺点同样明显——它对光照、角度、遮挡敏感直接拿到车上用还需要人在环调试。下面从源码包结构开始一层层拆到可以自己动手改参数为止。2. 拆开“驾驶员疲劳检测系统源码.zip”四个模块与它们各自的分工拿到这类源码包不要急着跑。先看目录结构多数读者会先打开主程序结果缺模型文件直接报错那体验真的劝退。一份能跑起来的基于Python的驾驶员面部特征的疲劳检测系统源码通常会把工作拆成四个部分视频输入与预处理、人脸检测与关键点定位、疲劳特征计算、疲劳判定与告警。我们一个个看。2.1 包里的代码在做三件事检测、判定、告警我见过很多初学者把注意力全放在“人脸检测”上实际上这个项目的重头戏在“特征计算”和“判定策略”。人脸检测只是原料判定才是产品。常规的包会包含以下几类文件作用各不相同。文件角色常见文件名负责内容人脸检测器detector.py / face_detect.py定位画面中的人脸框dlib HOG或OpenCV Haar关键点定位landmarks.py / shape_utils.py输出人脸的68个关键点坐标特征计算fatigue_features.py / metrics.py计算眼睛纵横比EAR、嘴巴纵横比MAR、头部姿态判定与告警fatigue_detection.py / main.py基于PERCLOS规则判定疲劳等级触发声音提示工具与配置config.py / utils.py统一存放阈值、摄像头编号、模型路径这里的“检测”不是指训练模型而是调用现成的预训练模型。疲劳检测项目几乎不会从零训练人脸关键点模型常见做法是直接使用dlib的68点shape_predictor或者OpenCV的LBF模型。源码包一般只打包代码因为预训练模型文件往往有近百兆很多作者会选择让你额外下载。2.2 眼睛、嘴巴、头部的几何特征是这整套系统能用起来的底层原因先说眼睛。人眼在68个关键点里对应左右眼各6个点左眼索引36到41右眼索引42到47。EAREye Aspect Ratio眼睛纵横比的公式是EAR (||p2 - p6|| ||p3 - p5||) / (2 * ||p1 - p4||)p1到p6按顺序取眼睛的6个点其中p1是内眼角p4是外眼角p2和p3是上眼皮点p5和p6是下眼皮点。标准EAR数值区间约在0.2到0.35之间人眨眼时上下眼皮距离迅速缩小EAR会掉到0.15以下。因为做过除法归一化它在一定程度上减弱了人脸尺寸和摄像头距离的影响这正是它能做疲劳判定的底气。嘴巴的特征同理用MARMouth Aspect Ratio计算嘴的张合程度。打架哈欠时嘴巴张开幅度大且持续时间长MAR会明显高于正常说话时的短促波动。头部姿态则稍微复杂一些常用做法是从68点中取下巴轮廓点用几个关键点的位置关系估算低头角度或用solvePnP求三维姿态角。多数源码包不会把后一种做得很重因为求解需要相机内参容易引入额外的标定麻烦。2.3 判定引擎是规则机不是神经网络这是它最大的优点也是边界这套系统的疲劳判定普遍采用PERCLOSPercentage of Eyelid Closure思路即在一段统计时间内眼睛闭合时间所占的比例。行业里常用P70和P80两种口径P80指眼睑闭合超过80%算作一次闭眼统计比例超过某个阈值就判定疲劳。这个口径是有理论支撑的它不是简单数眨眼次数而能反映持续困倦状态下眼睛闭合时间变长的趋势。源码里的判定逻辑通常是这样的每帧计算EAR低于阈值就标记为闭眼用一个时间窗口统计闭眼帧占比占比超过设定值或连续闭眼超过一定时长则输出疲劳告警。这种规则机的最大优点是可解释、可调参——闭眼阈值0.22意味着什么、连续闭眼5帧意味着什么都是明确的。它的缺点也由此而来如果只盯眼睛墨镜和侧脸就直接让系统失效所以正经一点的版本都会把打哈欠、点头频率作为辅助信号一起输入判定。2.4 主循环里的两个工程点降采样和异步视频处理不能每帧都跑全流程这是新手最容易忽略的。假如摄像头输出1080pdlib的人脸检测每帧可能花80到150毫秒算下来帧率只有10fps左右判定没有实时性。源码里通常会做两件事第一把输入帧缩小到宽度480或640大幅减少检测耗时第二只隔几帧做一次完整检测比如每3帧检测一次人脸中间帧沿用上一帧的人脸框。还有一类优化是开双线程。一个线程负责摄像头读帧和预处理另一个线程负责检测与判定中间用队列传递数据。这样即使某几帧检测卡顿画面采集也不会被拖住。后面自己动手优化时这两个点是优先级最高的比换模型、调阈值更见效。3. 在本地跑通这套源码环境、模型文件、调试脚本三步走拿到别人写的“源码.zip”最怕的就是缺依赖、缺模型、跑不起来。执行下面的步骤之前先去官网装好Python建议直接用3.8或3.9版本。新版本Python不是不行但不少dlib预编译包对3.10以上的支持有延迟容易让新手误以为源码有问题。3.1 Python环境里最需要避开的一个坑dlib的安装疲劳检测项目通常依赖opencv-python、dlib、numpy、imutils这几个核心库。opencv和numpy都很好装dlib是真正的分水岭。新一点的dlib版本在Windows上有预编译wheel可以一条pip命令装完但如果你拿到的是老版本依赖就需要本机有CMake和Visual Studio Build Tools很多人卡在这一步。# 创建独立虚拟环境避免污染基础Python环境 python -m venv fatigue_env # Windows 激活 fatigue_env\Scripts\activate # Linux/macOS 激活 source fatigue_env/bin/activate # 直接安装核心依赖dlib指定版本避免编译失败 pip install opencv-python numpy imutils dlib19.24.2这段命令里venv是Python自带的虚拟环境工具能让这个项目的依赖与全局环境隔离。Windows下脚本在Scripts目录Linux/macOS在bin目录路径不同容易踩坑。dlib固定到19.24.2是稳妥做法因为该版本对Python 3.8/3.9的预编译包支持最完整遇到“Could not find CMake”或“Failed building wheel for dlib”的概率会低很多。如果你安装dlib时还是触发编译先检查Python版本再检查是否有Visual Studio 2019以上版本的C桌面开发组件。这个依赖装好后后面基本就顺了。3.2 模型文件缺失是“启动即报错”的头号原因源码包里几乎不可能包含shape_predictor_68_face_landmarks.dat这个模型文件它有90多MB多数作者不会把它压进zip。启动时如果报“Unable to open shape_predictor_68_face_landmarks.dat”基本都是这个文件缺失。你需要把模型文件放到固定目录然后在启动参数里指定路径。# 目录结构约定models/ 放模型文件不放在源码根目录 # 这样换模型时不用改动代码只换文件 python fatigue_detection.py \ --shape-predictor models/shape_predictor_68_face_landmarks.dat \ --camera 0 \ --ear-thresh 0.25 \ --perclos 0.4 \ --window 150这个命令包含几个关键参数--shape-predictor指向模型文件--camera 0表示使用笔记本内置摄像头外接USB摄像头通常是1--ear-thresh是眼睛闭合阈值--perclos是闭眼比例判定阈值--window是统计窗口长度。需要特别注意的是不同源码包的参数名可能不同有的是--eyes有的是--threshold启动前先看一眼主程序里的argparse定义别硬套。3.3 先别急着触发告警用一个调试脚本看实时特征值主程序的告警是给最终用户看的开发调试时更需要的是“当前EAR是多少、MAR是多少”这类原始数据。我一般会先写一个极简调试脚本不接告警逻辑只看摄像头实时特征值确认整条链路是通的。# debug_ear.py —— 只打印特征值不触发告警 import cv2 import dlib import numpy as np detector dlib.get_frontal_face_detector() predictor dlib.shape_predictor(models/shape_predictor_68_face_landmarks.dat) cap cv2.VideoCapture(0) def dis(p1, p2): return np.linalg.norm(np.array([p1.x, p1.y]) - np.array([p2.x, p2.y])) def calc_ear(shape): left_ear (dis(shape.part(37), shape.part(41)) dis(shape.part(38), shape.part(40))) / ( 2.0 * dis(shape.part(36), shape.part(39))) right_ear (dis(shape.part(43), shape.part(47)) dis(shape.part(44), shape.part(46))) / ( 2.0 * dis(shape.part(42), shape.part(45))) return (left_ear right_ear) / 2.0 while True: ok, frame cap.read() if not ok: break frame cv2.resize(frame, (480, 360)) # 压低分辨率保证实时 gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) faces detector(gray, 0) for face in faces: shape predictor(gray, face) ear calc_ear(shape) print(fEAR{ear:.3f}) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()这个脚本的用处是验证三个东西模型路径是否正确、摄像头是否能被OpenCV打开、关键点索引是否和模型输出对齐。cal_ear函数里37、41、38、40是左眼的上下眼皮点36、39是内眦和外眦如果索引写错一个EAR曲线会变得毫无规律。正常睁眼时EAR应该在0.2到0.35之间眨眼时会瞬间降到0.15附近再反弹。如果你的数值一直稳定在0.1以下可能是索引搞错了如果完全没有波动可能是人脸框定位不稳定或者光线问题。frame缩放到480宽度是为了让dlib的检测速度保持在25fps以上。灰度转换更关键dlib的检测器和关键点预测器接受的都是单通道图像直接把彩图传进去会报错。跑通这个脚本后再回到主程序里调阈值手感会完全不同。4. 改参数之前先搞懂EAR、PERCLOS和时间窗口的关系很多人拿到源码第一件事就是把EAR阈值从0.25改到0.2理由仅仅是“这样就不误报了”。这其实是对判定逻辑理解不够。阈值改低确实能减少瞬时误报但也会让真正的疲劳闭眼不被识别问题从“频繁响”变成“不响应”更危险。4.1 EAR不是死值它跟人脸距离、摄像头角度都有关系EAR虽然做了归一化但它并不能完全消除个体差异。戴眼镜的人、脸比较大的、摄像头装得高一点低一点的正常睁眼时的EAR基准值都可能不同。我看到过同一台设备上不同驾驶员的EAR基线最大能差0.08左右。所以优秀的源码包会让你在启动阶段做校准先采集3秒睁眼状态的EAR平均值再以这个值为基准动态设定闭眼阈值。一个比较稳的做法是睁眼基准值乘以0.7作为闭眼阈值。举例来说如果你的正常EAR稳定在0.30阈值设为0.21如果另一个人正常值只有0.26阈值就降到0.18。这个逻辑在主程序里通常体现为一个可选的校准函数如果源码里没有可以考虑自己加10行代码的事效果却比固定阈值稳得多。4.2 PERCLOS统计的是占比不是次数时间窗口长度要按帧率算PERCLOS的统计口径很关键一般有两种实现固定时间窗统计和滑动时间窗统计。固定窗口写法是每150帧统计一次闭眼比例窗口到点就清零重来。滑动窗口则维护一个长度固定的布尔列表每次新状态进来最旧的状态被挤出占比实时计算。后者响应更平滑不会出现窗口边界处的跳变。# 滑动窗口版 PERCLOS 统计窗口长度按“秒 * fps”计算 WINDOW_SIZE 150 # 5秒 * 30fps sleep_ratio_buf [] # 每帧闭眼状态1表示闭眼0表示睁眼 while True: # current_state 来自上一帧的 EAR 与闭眼阈值比较 if ear ear_thresh: current_state 1 else: current_state 0 sleep_ratio_buf.append(current_state) if len(sleep_ratio_buf) WINDOW_SIZE: sleep_ratio_buf.pop(0) ratio sum(sleep_ratio_buf) / len(sleep_ratio_buf) if ratio 0.4: alert(疲劳状态请休息)这段代码里WINDOW_SIZE不能拍脑袋写。如果你的摄像头实际帧率只有15fps150帧代表10秒如果帧率是30fps则是5秒。把窗口长度和帧率挂钩是源码调参里最常见的需求。比如60fps的摄像头要用5秒窗口那窗口长度应该是300。为了应对帧率波动更稳的写法是用时间戳记录每个状态帧的时间按时间窗口清理旧数据而不是按帧数清理。源码里若没有替换成时间戳版本也很容易。4.3 眼部以外还需要两个“投票者”哈欠与点头只看眼的方案在真实的驾驶环境里不够用因为它对个别场景太敏感。比如驾驶员揉眼睛EAR会迅速下降可能会误判为闭眼。所以完整的判定引擎通常会让三个特征一起“投票”眼睛连续闭合时间、嘴巴张合频率、点头频率。特征常用判定事件推荐阈值建议时间窗口眼部EAR低于0.22连续闭眼超过50帧1.5秒以上嘴部MAR高于0.5单次张合超过1秒2秒以上头部俯仰角变化超过25度1分钟内点头超过8次1分钟统计这三个特征单独看都可能产生误报但组合起来就靠谱得多。许多源码会设计一个简单的评分规则比如眼部疲劳判一次加2分哈欠加1分点头加1分3秒内累计超过4分才触发告警。这样设计的好处是降低单特征偶然波动造成的干扰代价是需要额外调参。调参时建议录一段自己开车时的视频回放逐帧对照评分输出而不是对着摄像头现场等结果。5. 避坑光照、口罩、侧脸和那些让检测系统“翻车”的细节跑了几天这套源码后你大概率会发现演示视频里一切都好一装到真实环境里就各种翻车。下面这些坑基本是按出现频率排的每条都值得记进自己的排查清单。5.1 侧脸不到30度就检测不到人脸现象驾驶员转头看后视镜或右侧窗外画面里还能看到半张脸但程序已经不输出人脸框了后续所有特征计算全部暂停。原因dlib的HOG人脸检测器对正面和小角度偏转表现好但对大角度侧脸没有专门训练。这不是代码bug是模型本身的局限。解决第一调整摄像头安装位置尽量正对驾驶员面部第二在判定逻辑里加入“检测不到人脸”的处理不能直接当无事发生超过5秒检测不到人脸应该提示“请正视前方”而不是沉默。第三如果你对侧脸有硬性要求考虑换成OpenCV的YuNet或MediaPipe人脸检测器它们对侧脸的容忍度更高但源码改造量会大一点。5.2 口罩让系统一直认为你在打哈欠现象戴上口罩后MAR一直偏高系统频繁触发“疲劳打哈欠”告警。原因口罩遮住嘴部区域dlib关键点仍然会输出但上下嘴唇关键点会落在口罩表面导致嘴部纵横比类似张开状态。解决最直接的办法是关闭嘴部特征分支只用眼部和头部特征。如果必须保留哈欠检测可以改用嘴部轮廓的内外两圈关键点的相对位移来判断口罩会让外圈变化而内圈被遮住位移关系会明显异常。在实际部署里口罩场景非常普遍我通常建议把MAR的权重降低只在没有口罩识别的注释开启。5.3 傍晚和夜间EAR整体数值偏低现象白天眼睛睁得很大EAR稳定在0.30到了晚上光线暗同样睁眼EAR只有0.22系统开始频繁误报疲劳。原因光照降低后灰度图对比度变差关键点定位精度下降上下眼皮关键点会往中间收拢导致EAR系统性偏低。这不是疲劳是特征提取的噪声。解决第一优先提高输入图像质量加一个简单的自适应直方图均衡化能明显改善低照度下的定位稳定性。第二夜间场景要单独设置阈值比如日间闭眼阈值0.22夜间改成0.18。第三如果摄像头支持红外补光优先用红外因为红外可以在全黑环境下保持稳定的面部轮廓。5.4 关键点抖动让EAR数值不停乱跳现象人脸保持不动屏幕上打印的EAR却在0.20到0.32之间来回跳眨眼检测里混杂了大量假闭眼。原因关键点预测器每一帧独立工作帧与帧之间本来就有随机抖动加上摄像头的自动曝光和轻微运动模糊抖动会被放大。解决对EAR做指数平滑滤波这是性价比最高的做法。代码里加一个很短的滤波器alpha取0.3左右平滑后曲线会稳定很多同时不会明显延迟眨眼事件的响应。alpha 0.3 smoothed_ear 0.0 for raw_ear in ear_stream: if smoothed_ear 0.0: smoothed_ear raw_ear else: smoothed_ear alpha * raw_ear (1 - alpha) * smoothed_ear这段滤波代码的核心就是“新值占三成旧值占七成”让突变被压住。alpha调大则响应快但滤波弱调小则曲线平滑但迟滞增大。实战里0.2到0.4之间比较合适具体看你的摄像头帧率。5.5 回放录像测得好好的现场用却疯狂误报现象用录制的视频测试各项指标都正常换成摄像头实时采集误报率立刻升高。原因很多源码的测试视频是固定曝光参数录的而摄像头默认开启自动曝光光线变化时每帧亮度都在变关键点位置也跟着飘。另外视频回放是“即时生效”实时采集则有摄像头驱动缓冲两者帧率节奏不同。解决把摄像头自动曝光关掉固定曝光值和白平衡让每一帧的光照条件尽量一致。OpenCV里可以尝试设置CAP_PROP_AUTO_EXPOSURE为0并使用固定暴露值但不是所有摄像头驱动都支持不支持时可以用代码锁定并保证使用环境光线相对稳定。这也是为什么有些项目宁可预先录好视频做测试也不拿实时流做数据对比。6. 让系统真正敢上车自测协议、分级告警与数据脱敏跑通源码只是起点真正有信心得有数据支撑。我自己的习惯是把“疲劳检测系统”当成一个要验收的模块每次改完参数都跑一遍固定测试流程。6.1 用一份固定的“模拟驾驶测试录像”替代现场模糊测试在测试阶段不要每次都对着摄像头做随机动作。录一段结构化的测试视频内容依次是正常驾驶2分钟、频繁眨眼30秒、打哈欠2次、点头10次、低头看手机1分钟、遮挡左眼/右眼各30秒、夜间模拟环境1分钟。然后对每段视频记录程序输出按下面的表格统计。测试片段期望结果验收标准正常驾驶不触发或最多1次误报误报率为0频繁眨眼持续闭眼超过阈值时触发触发延迟在3秒内打哈欠触发哈欠提醒不计为重度疲劳事件识别成功低头看手机触发头部姿态提醒事件识别成功遮挡单眼不触发疲劳告警抗干扰通过这套录像回放测试比现场测试效率高很多因为每次改阈值都可以用相同输入对比结果不存在“这次没眨眼所以没触发”的偶然性。有条件的话把录像按时间戳对齐程序输出日志哪里误报、哪里漏报一眼就能定位。6.2 把告警做成三级提示而不是单一刺耳蜂鸣源码包最常见的告警方式就是电脑蜂鸣或播放一个wav文件效果很粗糙。真实场景里驾驶室本来就有噪音过于刺耳的提示反而会干扰驾驶。我一般会改成三级告警轻疲劳只显示提示图标中度疲劳用柔和提示音加界面闪烁重度疲劳才用高频间歇提示音并连续播报。用numpy合成提示音很简单不需要额外音频文件import numpy as np import sounddevice as sd fs 44100 t np.linspace(0, 1, int(fs)) tone 0.5 * np.sin(2 * np.pi * 880 * t) * np.exp(-2 * t) sd.play(tone, fs)这里880Hz是一个偏柔和的提醒频率指数衰减项让声音不刺耳。把这段封装成trigger_alert(level)函数分别用不同频率和重复次数表示不同等级。注意sd.play是异步播放不会阻塞主检测循环。6.3 最后一步保存记录时做数据脱敏只留特征不留原始人脸真正要把这套系统投入使用时隐私问题躲不掉。摄像头画面属于敏感个人信息疲劳检测只需要特征数据不需要完整人脸画面。建议在保存记录时只保存关键点坐标和EAR/MAR序列以及告警类型和时间戳如果必须保存画面则对非面部区域做模糊处理或只保存面部区域且加上水印标识。这是从“实验室能用”到“实际敢用”必须跨过的一道坎。我自己每改一个阈值都必须把这些测试片段完整回放一遍确认误报和漏报没有漂移才会考虑把它接到驾驶模拟器上做下一步验证。整个过程里最大的教训就是不要信感觉要信回放数据。希望帮到你。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联 返回资讯列表 →