ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于YOLOv5与Dlib的疲劳驾驶检测系统:从目标框选到PERCLOS判定

基于YOLOv5与Dlib的疲劳驾驶检测系统:从目标框选到PERCLOS判定 简介这份基于YOLOv5、dlib与OpenCV的疲劳驾驶检测完整项目面向正在准备毕业设计或课程设计的计算机专业学生也适合需要实战练习的开发者。整套方案包含算法源代码、预训练权重文件与详细文档从人脸关键点定位、眼部纵横比计算到驾驶状态判定均有完整实现可复现高分毕设的完整流程。压缩包共93个文件涵盖Python脚本.py、模型配置.yaml、权重文件.pt、人脸关键点模型.dat及说明文档等另有演示视频与测试图片便于快速验证效果整体大小约136.47MB。目前已有124人学习下载代码结构清晰、注释到位并附有运行说明小白也能根据文档独立完成环境配置与调试。这套项目不仅可直接作为毕业设计素材还包含多个功能模块和实用工具便于在此基础上进行二次开发与算法优化是计算机视觉方向不可多得的实战参考资料。1. 疲劳驾驶检测为什么先选 yolov5 选框再用 dlib 取点只用一个算法做疲劳驾驶检测的毕设很多但绝大多数在真实视频上跑不过三分钟。纯肤色分割的手指检测驾驶员一戴墨镜就失效只看方向盘转角和时间戳弯道变道全是误报。真正能拿高分且能复现的思路是把目标检测和关键点检测串成一条流水线yolov5 负责在每一帧图像里框出眼睛和嘴巴区域dlib 的 68 点模型在已经把目标裁切好的局部区域里做关键点标定最后用 EAR 和 POM 两个几何特征计算眼睛闭合程度和嘴部开合程度再按 PERCLOS 准则判断疲劳状态。这套方案对光照和遮挡的容忍度比传统 opencv 图像处理方法高一个量级代码量却比端到端的复杂神经网络小很多适合作为毕业设计的主干逻辑也适合作为课程设计、期末大作业的完整项目来复现。2. 检测链路拆解目标框选、关键点标定与 PERCLOS 判定2.1 为什么要两段式检测而不是单一模型一步到位先想清楚一个问题疲劳驾驶检测的本质是连续多帧判断“眼睛是否闭合、嘴部是否张开”这既需要知道眼睛在哪里又需要知道眼睑轮廓的精确位置。单独用 dlib 在全图中检测人脸并标定 68 点遇到侧脸、低头、遮挡时检测框会偏移或者丢失单独用 yolov5 检测眼睛和嘴巴虽然能返回矩形框但矩形框无法直接计算眼睑的开合距离因为框本身不提供内部轮廓信息。两段式设计的价值在于分工yolov5 先把大目标锁定把画面中的人脸、眼睛、嘴巴区域框出来并裁切缩小搜索范围然后 dlib 在已经定位好的裁切区域里执行 68 点标定提取眼睛上、下眼睑的 6 个关键点和嘴部外轮廓的 12 个关键点。这样既避免了整图关键点检索的无效计算又为后续特征计算提供了亚像素级别的坐标数据。在 NVIDIA GPU 上整个流程单帧耗时大约在 30 到 80 毫秒完全满足驾驶场景下 20 帧以上的实时处理需求。2.2 EAR 和 POM从坐标到几何特征的换算眼睛的闭合程度用 Eye Aspect Ratio 衡量。设左右眼的 6 个关键点分别用索引表示纵向两点间的欧氏距离作为分子横向两点间的欧氏距离作为分母比值在睁眼时约为 0.25 到 0.35闭眼时趋近于 0.1 或更低。这个比值对头部姿态有一定程度的自适应因为它是一个相对比例而非绝对像素值。def eye_aspect_ratio(eye_points): # eye_points 是 dlib 返回的 6 个关键点坐标 # 计算纵向两对点的欧氏距离 vertical_1 ((eye_points[1][0] - eye_points[5][0]) ** 2 (eye_points[1][1] - eye_points[5][1]) ** 2) ** 0.5 vertical_2 ((eye_points[2][0] - eye_points[4][0]) ** 2 (eye_points[2][1] - eye_points[4][1]) ** 2) ** 0.5 # 计算横向一对点的欧氏距离 horizontal ((eye_points[0][0] - eye_points[3][0]) ** 2 (eye_points[0][1] - eye_points[3][1]) ** 2) ** 0.5 # 加一个极小值防止除零 ear (vertical_1 vertical_2) / (2.0 * horizontal 1e-6) return ear这里的索引对应 dlib 68 点模型中的眼睛区域左眼为第 36 到 41 个点右眼为第 42 到 47 个点。代码中 vertical_1 和 vertical_2 分别对应上眼睑与下眼睑两侧的距离horizontal 对应外眼角到内眼角的距离EAR 综合这三段距离后可以消除部分个体眼型差异。嘴部开合度 POM 使用类似思路取嘴唇外轮廓上下点的距离与左右嘴角距离的比值张嘴打哈欠时该值明显上升与 EAR 共同作为疲劳状态的第二重判据。2.3 PERCLOS把单帧特征映射成疲劳状态单帧的 EAR 数值抖动很大眨眼瞬间也会短暂低于阈值但如果直接判定为疲劳就会产生大量误报。工程上通常采用 PERCLOS 标准统计一段时间窗口内眼睛闭合帧数占总帧数的比例当这一比例超过某一设定值时才判定为疲劳状态。PERCLOS 有三种测量标准 P70、P80 和 EM其中 EM 标准眼睑闭合超过瞳孔面积 70% 以上即认为闭眼与驾驶疲劳的相关性最高也是本项目采用的默认逻辑。判定指标计算公式典型阈值适用场景EAR眼睑距离比低于 0.2 记为闭眼单帧眼睛状态判断POM嘴部开合比高于 0.5 记为打哈欠辅助判据防漏检PERCLOS-EM闭眼帧数 / 窗口总帧数超过 0.4 判定疲劳连续驾驶监测窗口长度通常取 30 到 60 帧。设置 60 帧窗口意味着 3 秒内闭眼时间累计超过 1.2 秒就触发报警这个参数需要根据视频帧率和实际路测效果反复调整。如果只检测眼睛窗口可以缩短到 30 帧以提升响应速度但误报率会同步上升把 POM 指标加入后打哈欠和闭眼同时出现会显著提高疲劳判定的置信度。3. 环境与权重落地dlib 编译、yolov5 权重与文件清单说明3.1 版本组合与依赖安装这个项目对版本敏感的地方是 dlib 和 torch。dlib 在 Windows 上直接用 pip 安装经常失败原因是它需要 C 编译环境torch 的版本又直接决定 yolov5 能否正常导出权重。推荐的组合是 Python 3.8、torch 1.10.0 加 CUDA 11.3、opencv-python 4.5.5、dlib 19.22.0这套组合在 x86_64 Windows 和 Ubuntu 20.04 上都验证过能保证项目自带的 best.pt 权重正常加载。pip install opencv-python4.5.5.64 pip install torch1.10.0cu113 torchvision0.11.0cu113 -f https://download.pytorch.org/whl/torch_stable.html pip install dlib19.22.0如果 dlib 安装报错需要先安装 CMake 和 Visual Studio 2019 的 C 生成工具然后重新编译。Windows 用户也可以下载预编译的 dlib wheel 文件但版本必须与 Python 位数一致。安装完成后在 Python 里分别执行 import cv2、import torch、import dlib三个库都能正常导入再继续下一步。新装的机器上 import cv2 报错时多半是 opencv-python 的依赖 numpy 版本冲突直接升级 numpy 到 1.21 以上即可解决。3.2 权重文件与辅助模型的功能定位解压项目压缩包后需要认清每个文件的角色。best.pt 是 yolov5 训练好的权重负责检测闭眼、睁眼、打哈欠、吸烟等目标类别shape_predictor_68_face_landmarks.dat 是 dlib 的 68 点人脸关键点检测模型负责输出眼睑和嘴部轮廓点的坐标detector.svm 是一个独立的吸烟检测分类器用来识别驾驶员手持香烟的画面与疲劳检测主体逻辑互补。文件名称类型核心作用best.ptyolov5 权重检测眼睛、嘴巴、手机、香烟等目标shape_predictor_68_face_landmarks.datdlib 模型定位 68 个面部关键点detector.svmdlib 分类器识别吸烟行为main.py主程序视频推理与疲劳判定入口train.py训练脚本在自建数据集上重新训练input.mp4 / output.mp4测试与输出处理前后的视频文件detector.svm 是 dlib 的 HOG 加线性分类器产物输入的是固定尺寸的图像块输出为是否包含香烟的概率。main.py 的逻辑是先跑 yolov5 检测到人脸再把人脸区域送入 shape_predictor_68_face_landmarks.dat 提取关键点关键点坐标返回后计算 EAR 特征同时把眼睛和嘴巴的裁切图像送入 detector.svm 做吸烟判断。这样一个主循环里融合了两套模型体系也正是这个毕业设计能拿高分的原因之一。4. main.py 推理主流程从视频帧到疲劳判定的代码走读4.1 模型初始化和参数配置打开 main.py最前面的部分加载三种模型资源。yolov5 部分使用 torch.hub 从本地项目路径加载模型而不是从网络下载避免因网络问题导致无法运行。dlib 的人脸检测器使用 get_frontal_face_detector这个检测器速度快但召回率有限配合 yolov5 的人脸检测结果使用效果更好。import cv2 import dlib import torch from models.experimental import attempt_load # 加载 yolov5 模型使用本地权重 model attempt_load(best.pt, map_locationcpu) model.eval() # 加载 dlib 人脸检测器和 68 点关键点模型 face_detector dlib.get_frontal_face_detector() landmark_predictor dlib.shape_predictor(shape_predictor_68_face_landmarks.dat) # 加载吸烟检测分类器 smoke_detector dlib.simple_object_detector(detector.svm)这三个模型的加载顺序不能乱尤其是 detector.svm一旦 dlib 版本不匹配加载时不会立刻报错但运行到检测环节会出现断言失败。attempt_load 是 yolov5 项目 models.experimental 模块里封装的加载函数它能处理不同版本的权重文件结构比直接用 torch.load 更健壮。map_location 参数设置为 cpu 可以避免 GPU 显存不足导致的启动崩溃部署后再按需切换到 cuda。4.2 单帧处理EAR 计算、吸烟检测与状态累积主循环使用 cv2.VideoCapture 逐帧读取视频每一帧先做一次尺寸缩放保持长边不超过 640 像素这一步能显著降低推理耗时。缩放后的帧送入 yolov5 推理得到人脸和眼睛的候选框然后遍历候选框对每个框做 padding 后调用 landmark_predictor 提取 68 个关键点再根据索引计算左右眼的 EAR 值。cap cv2.VideoCapture(input.mp4) fps cap.get(cv2.CAP_PROP_FPS) frame_count 0 ear_buffer [] # 保存最近 60 帧的 EAR 平均值 closed_frames 0 while True: ret, frame cap.read() if not ret: break frame_count 1 rgb_frame cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) # yolov5 推理 results model(rgb_frame, size640) for *box, conf, cls in results.xyxy[0]: # 提取眼睛区域坐标 x1, y1, x2, y2 [int(v) for v in box] eye_roi rgb_frame[y1:y2, x1:x2] # 在局部区域执行 dlib 关键点检测 rect dlib.rectangle(x1, y1, x2, y2) landmarks landmark_predictor(rgb_frame, rect) left_eye_ear eye_aspect_ratio(landmarks, range(36, 42)) right_eye_ear eye_aspect_ratio(landmarks, range(42, 48)) ear (left_eye_ear right_eye_ear) / 2.0 ear_buffer.append(ear) # 维护滑动窗口 if len(ear_buffer) int(fps * 3): ear_buffer.pop(0) avg_ear sum(ear_buffer) / len(ear_buffer) if avg_ear 0.2: closed_frames 1 else: closed_frames 0 if closed_frames fps * 1.5: cv2.putText(frame, FATIGUE!, (50, 50), cv2.FONT_HERSHEY_SIMPLEX, 1.0, (0, 0, 255), 2) cv2.imshow(Fatigue Detection, frame) if cv2.waitKey(1) 0xFF ord(q): break这段代码的核心在滑动窗口处理。ear_buffer 只保留最近 3 秒的 EAR 数值闭眼帧计数 closed_frames 的作用是把单帧误检过滤掉只有连续闭眼超过 1.5 秒才报警。注意 EAR 的判定阈值 0.2 不是固定的摄像头安装位置、驾驶员眼部特征、是否佩戴眼镜都会影响该值实际项目里需要采集 3 到 5 分钟的驾驶数据统计正常状态下的 EAR 分布再以均值减两倍标准差作为动态阈值。4.3 视频输出与关键参数说明输入视频可以是摄像头实时流也可以是本地视频文件。使用 cv2.VideoCapture(input.mp4) 时opencv 会根据文件扩展名自动选择解码器使用摄像头时把路径改为设备索引号比如 0 代表第一个 USB 摄像头。设置输出视频时需要先获取原始视频的帧率和分辨率然后创建 VideoWriter 对象编码器通常选 mp4v 或 XVID。参数名位置推荐值作用说明sizemodel(rgb_frame, size640)640yolov5 输入尺寸越大精度越高但更慢ear阈值if avg_ear 0.20.2低于该值判定闭眼需按人调整滑动窗口int(fps * 3)3 秒窗口越长误报越少但延迟增大连续闭眼时间fps * 1.51.5 秒触犯疲劳报警的最短闭眼时长如果发现检测不到人脸优先调整的是人脸框的 padding 值也就是在 dlib 矩形框四周多扩出 10 到 20 个像素再送入关键点检测。因为原始检测框可能刚好处在轮廓边缘截断后关键点坐标会被强行压缩到图像边界内导致 EAR 计算异常。项目自带 output.mp4 是完整的处理结果可以用它对照检查自己的代码是否与原版逻辑一致。5. 更换自己的数据集yolov5 训练配方、标签格式与超参数调整5.1 数据集组织方式与标注工具项目自带的 best.pt 虽然能直接使用但如果想在更复杂的驾驶场景下提升检测精度就需要用自己的数据集重新训练。yolov5 要求数据集按 images 和 labels 两个目录组织标签文件与图片文件同名格式为 txt每行内容为 class_id、归一化后的中心点 x、中心点 y、宽度 w、高度 h。标注工具推荐 labelImg 或 labelme导出格式选择 YOLO 格式。# img_001.txt 示例 0 0.4567 0.3456 0.1234 0.0987 1 0.5123 0.6789 0.2345 0.1876其中 class_id 对应自己定义的类别顺序0 可以是睁眼1 可以是闭眼2 可以是哈欠张嘴。标注闭眼时要注意闭合程度低于 50% 就应标记为闭眼否则训练出的模型会对半闭眼状态非常迟钝。每一类的图片数量尽量不低于 2000 张并且要包含白天、夜晚、墨镜、眼镜、不同肤色、不同拍摄角度的样本这样模型才有泛化能力。5.2 训练参数与超参数配置训练之前先修改 data.yaml指定训练集和验证集路径以及类别名称。训练命令的常用参数epochs 设为 100 到 150batch-size 根据显存大小调整imgsz 设为 640。如果使用项目自带的 train.py直接继承原有的 YOLOv5s 网络结构yolov5s.yaml 是最合适的起点因为在保持较高精度的同时模型体积小后期部署到车载平台比较容易。python train.py --data data.yaml --cfg models/yolov5s.yaml --weights --epochs 120 --batch-size 16 --imgsz 640 --device 0weights 参数传空字符串表示从头训练而不是加载官方预训练权重。如果数据集较小建议把冻结层参数 freeze 设为 10让模型先训练头部几层再逐层解冻这样可以防止过拟合。yolov5 超参数文件 hyp.scratch.yaml 里的 lr0 初始学习率默认是 0.01在自定义数据集上通常调低到 0.005避免 loss 在训练初期就发散。训练过程中重点关注验证集上的 mAP0.5 指标这个值达到 0.85 以上模型才算基本可用。5.3 权重转换与替代 best.pt训练完成后runs/train/exp 目录下会生成 best.pt 和 last.pt把 best.pt 复制到项目根目录并更名为 best-custom.pt再修改 main.py 中的权重路径即可完成替换。这里有个细节替换权重后类别数量或顺序可能与原权重不同main.py 里解析 results.xyxy[0] 时conf 和 cls 的索引需要对应新的类别定义。训练参数含义推荐范围epochs训练轮次100 到 150batch-size每批次图片数显存 8G 以下用 816G 用 16 以上imgsz输入分辨率640 或 416越小训练越快freeze冻结浅层数10 适合小数据集lr0初始学习率0.005 到 0.01自建数据集的一个常见误区是只标注眼睛不标注嘴部导致 POM 特征无法计算。疲劳驾驶场景下长时间闭眼与频繁打哈欠存在高度相关性嘴部标注的缺失会让整个判据链少了一条腿。标注时把嘴巴张开超过 30% 标记为张嘴再配合 EAR 的闭眼判据模型的召回率能提升 10 个百分点以上。6. 落地时最容易翻车的四个坑与对应处理第一dlib 的 shape_predictor 在图像边界附近检测时会返回负坐标EAR 计算因此出现极大值或负数。解决办法是在调用 landmark_predictor 之前对检测矩形框做边界裁剪确保坐标为非负且不超过图像尺寸同时把 padding 后的矩形框再与图像区域求交集。第二cv2.VideoCapture 读取本地视频正常但读取摄像头流时画面卡顿原因是回调线程与主推理循环共用同一个摄像头句柄。常见做法是单独开一个子线程持续 read 并存入队列主线程从队列取帧这样可以保证推理帧率不受摄像头的读取速度限制。opencv 的 waitKey(1) 在部分系统上会阻塞主循环改用 cv2.waitKey(1) 且传入非零值时效果更稳定。第三detector.svm 加载后运行时报错提示 object_detector 类型不匹配。这通常是 dlib 主版本不一致导致的19.22 训练的 svm 文件在 19.24 上会出现序列化兼容问题。解决方案是锁死 dlib 版本或者在代码里捕获异常并输出模型文件路径便于排查。第四yolov5 输出结果使用 results.xyxy[0] 解析时框的坐标是基于缩放后的图像坐标系而 dlib 的 rectangle 需要原始图像坐标。如果不把坐标还原关键点检测会有大约 30 到 60 像素的偏移。调试时可以在原始帧上画框对比快速定位坐标系不一致的问题。针对第二个坑一段可靠的摄像头读取封装思路是使用队列加锁操作队列最大长度设为 2读线程持续向队列放入帧推理线程每次从队列中取出最新的一帧丢弃旧帧。这样即便推理速度低于读取速度内存占用也保持恒定不会因为帧堆积导致延迟越来越大。使用 Python 的 queue.Queue 即可实现不需要引入额外依赖。如果项目中需要把检测结果接入到其他系统比如车载终端或者监控平台可以在每一帧输出判定结果的同时把对应的帧号、EAR 值和判定结果写入结构化日志便于事后回放分析误报原因。本文还有配套的精品资源点击获取
返回列表