尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

YOLOv5+DeepSORT实现驾驶员疲劳与分心行为实时检测

YOLOv5+DeepSORT实现驾驶员疲劳与分心行为实时检测 简介一份基于深度学习YOLOv5与DeepSort的驾驶员分心驾驶预警系统项目完整收录Python源码、模型权重、人脸关键点检测数据与配套说明文档适合计算机相关专业学生用于课程设计、期末大作业或深度学习实战练习。项目采用目标检测与多目标跟踪结合的技术路线覆盖疲劳状态识别与危险行为预警两类典型场景代码按检测、跟踪、界面等模块拆分配合演示视频与图文手册便于从原理到部署快速上手。资源共59个文件以py脚本、yaml配置文件、pyc编译文件为主同时提供pt权重、dat关键点模型、UI界面文件及mp4演示视频压缩包整体约118MB目录划分清晰拿到后即可对照文档运行调试。目前已有50人学习下载适合需要可运行项目参考、想快速完成课设或深入理解YOLOv5DeepSort应用流程的学习者。1. 疲劳驾驶检测为什么不能只看一帧YOLOv5Deepsort 的分层思路把基于深度学习、YOLOv5、Deepsort 的驾驶员分心驾驶预警系统从空目录推到能出误报报告我遇到最反直觉的一件事是单帧检测模型 F1 越高报警系统反而越没法用。YOLOv5 画出来的框再准它也只有“这一帧看到什么”的瞬时信息没有“这个人是刚才那个人”的身份概念。疲劳闭眼、打哈欠、低头看手机这一类行为本质上是跨帧累积的时序信号单帧置信度再高也推不出“已经持续了多久”而没有持续时长就谈不上预警。这套系统的工程实现是被需求倒逼出来的YOLOv5 处理单帧画面Deepsort 按外观和运动信息把检测框关联成带 ID 的轨迹最后接一个行为判定层用“连续 N 帧命中”代替单帧报警。它解决的是检测与告警之间的断层问题适合已经跑过目标检测、准备把模型接到真实预警任务上的工程师也适合做车载安全项目团队建立自己的数据闭环。2. 先搭检测骨架YOLOv5 训练自己的驾驶员数据集2.1 类别画像与行为定义驾驶员分心预警不是做一个“人”检测而是做一组细粒度行为类别。类别定义直接决定标注成本、数据采集难度和最终的漏报形态。我一般把舱内行为拆成疲劳和危险动作两大类疲劳类只保留闭眼和打哈欠危险动作保留使用手机、喝水、抽烟一共五个类别。类别数量超出这个范围比如把“双手离开方向盘”也加进去标注难度会指数上升因为方向盘和手的相互遮挡让边界框极不稳定。表格里的触发判定标准不是模型输出的类别概率而是后面时序判定层使用的先验规则。先把这个表定下来标注和训练才不会返工。class_id类别归属触发判定倾向0eye_close 闭眼疲劳需要长时间累积连续闭眼超过 1.5 秒才报警1yawn 打哈欠疲劳嘴巴张大动作持续且出现频率高2phone 使用手机危险动作手持手机朝向面部或低头注视3drink 喝水危险动作手持水杯靠近嘴部4smoke 抽烟危险动作手持烟支靠近嘴部或烟雾遮挡数据采集阶段至少要覆盖三种光照白天顺光、黄昏逆光、夜间仪表盘光源、三种人脸状态正常、戴眼镜、戴口罩并且包含不同体型驾驶员。只录一两段视频喂进去训练白天跑得顺黄昏就开始大量漏检这是训练的常见坑也是受外界参数影响最小的可用性差异。2.2 标注工具与数据集目录组织标注工具用 LabelImg 或 Label Studio 都行统一导出 YOLO 格式的 txt 文件每行是class x_center y_center width height坐标全部归一化到 0~1。驾驶舱近景有一个标注原则闭眼的框要从眉毛上沿框到下巴不要把额头切掉否则模型学到的特征混入了发型和眼镜框手机类要把手机和握持的手一起框进去只框手机屏会让小目标漏检率明显上升。数据集目录按 YOLOv5 约定组织训练集和验证集分开验证集从不同视频里抽帧而不是从同一段视频随机采样避免同一段画面既训练又验证造成虚高的 mAP。datasets/driver/ ├── images/ │ ├── train/ # 约 12000 张 │ └── val/ # 约 1500 张 ├── labels/ │ ├── train/ │ └── val/ └── driver.yamldriver.yaml是 YOLOv5 读取数据集的入口文件路径、类别数和类别名必须与实际目录一致类别名顺序和标注软件导出的一致。这里最容易出错的是 path 字段用了绝对路径换机器训练就要改一遍建议直接用相对路径并保证从 YOLOv5 根目录执行训练命令。# datasets/driver/driver.yaml path: ../datasets/driver train: images/train val: images/val nc: 5 names: 0: eye_close 1: yawn 2: phone 3: drink 4: smoke2.3 用 YOLOv5 训练自己的数据集的启动命令训练启动命令是这套系统里面最不值得反复研究的部分真正影响效果的参数就几个。下面的命令跑过一次之后基本可以固化成 shell 脚本后续只改数据路径。python train.py \ --data ../datasets/driver/driver.yaml \ --weights yolov5s.pt \ --epochs 120 \ --batch-size 16 \ --img 640 \ --patience 20 \ --name driver_v1--weights yolov5s.pt用的是 COCO 预训练权重YOLOv5s 在速度和精度之间比 m/l 版本更平衡驾驶舱场景单个目标占比大不需要更大的骨干网络--epochs 120对五分类数据集足够重点看 val 集 mAP 是否连续 20 个 epoch 不再上升这时--patience 20会自动截断训练--img 640是默认输入分辨率若夜间小目标漏检明显可以升到 768 或 960但推理帧率会下降车载场景要先想清楚算力预算再动这个参数。yolov5 超参数默认配置在data/hyps/hyp.scratch-low.yaml里mosaic、mixup 这些数据增强默认是开启的。针对疲劳检测我一般会关掉最后一个阶段的 mosaic因为 mosaic 会把四张图拼接在一起人脸经常被切在拼接缝上闭眼这种本来就小的语义特征被切掉半边之后模型学到的是半张脸而不是闭眼状态。修改方式是在训练命令里加参数比如--close-mosaic 10表示最后 10 个 epoch 关闭 mosaic。这个技巧是动手做驾驶预警项目时最容易忽略的调优手段比反复调学习率收益更直接。3. 让结果带时序Deepsort 如何把检测变成行为3.1 为什么纯 YOLOv5 不够纯 YOLOv5 输出的每个框都是一个独立事件没有轨迹概念。人的头部稍微转动画面里还是同一个驾驶员但模型只得到一个又一个瞬时框。报警场景里如果驾驶员只是打个哈欠后立刻坐直那不应该触发疲劳告警只有闭眼持续超过限定秒数才符合疲劳状态定义。而“持续超过限定秒数”必须知道同一个目标在前后帧里是不是同一个人这正是 Deepsort 的职责。3.2 Deepsort 的关联机制与两个距离Deepsort 是 Sort 的扩展核心区别在级联匹配阶段同时使用了运动距离和外观距离。运动距离是卡尔曼滤波预测的当前帧位置与实际检测框位置之间的马氏距离衡量的是“下一个位置是否合理”外观距离是轨迹里缓存的目标特征向量与当前检测框特征向量的最小余弦距离衡量的是“长得像不像”。级联匹配先处理最近更新过的轨迹再处理长期没更新的轨迹这个先后顺序避免了目标短暂丢失后 ID 乱跳。下面这个配置文件是驾驶员近景场景下比较稳妥的一组参数每项都值得按你的摄像头安装位置重新调。# configs/deepsort.yaml max_cosine_distance: 0.3 nn_budget: 100 max_age: 60 n_init: 3参数含义和调整方向如下表。特别说明max_cosine_distance它控制“外观像到什么程度算同一个目标”驾驶舱场景里驾驶员经常转头、抬手遮挡面部外观特征向量波动比行人跟踪大默认值 0.2 会造成频繁丢 ID放到 0.3 是起步值如果依然丢得厉害再往上加。参数默认值驾驶舱建议值调整理由max_cosine_distance0.20.3转头、抬臂遮挡导致外观特征抖动阈值过低容易频繁换 IDnn_budget100100缓存最近 100 条外观特征足够应对单人场景max_age3060方向盘遮挡造成瞬时断帧适当延长轨迹存活期n_init33连续 3 帧确认后才算有效轨迹抑制单帧误检3.3 驾驶舱场景的三个适配坑第一个坑是 Deepsort 把人脸跟丢之后重新分配新 ID导致行为判定状态机里的累积帧数被清零。即使max_age调到了 60遮阳板或方向盘长时间遮挡时 ID 切换依然不可避免。我的做法是检测到轨迹丢失并重新出现后做一个短时缓冲把丢失前最后 5 帧的类别状态与新轨迹的前 5 帧状态做合并避免刚闭眼 1 秒的疲劳累积因为 ID 重置而前功尽弃。第二个坑是卡尔曼滤波对驾驶员这种“非刚体、小位移”目标的预测并不理想。驾驶员在座位上小幅移动检测框中心抖动幅度大卡尔曼预测的方差被放大反而干扰级联匹配。我一般把检测框传进 Deepsort 之前先对 bbox 做一帧的时间平滑或者干脆把检测帧率降到 15 FPS 再喂给 Deepsort降低小幅抖动带来的噪声。第三个坑是类别信息没有参与 Deepsort 的匹配过程。Deepsort 只认识“这是一个人形目标”不区分这个人在闭眼还是在玩手机。如果你把五个类别都送去跟踪Deepsort 可能把同一个人的眼睛框和手机框当成两个目标。所以进入 Deepsort 之前必须先按类别分组每一类单独维护一个 tracker 实例或者只选择主目标类别比如 face参与跟踪其他类别作为副属性挂到主轨迹上。这个设计直接关系到行为判定能不能拿到稳定的 track_id。4. 训练、启动与参数调优环境配置与推理代码4.1 锁定 YOLOv5 运行环境而不是解决环境问题YOLOv5 的迭代速度很快不同 commit 之间的接口差异可能导致训练脚本跑一半报错。与其纠结最新版本不如按一个已知稳定的组合锁死环境这是交付源码和文档时最优先做的事。conda create -n driver python3.9 -y conda activate driver pip install torch1.13.0 torchvision0.14.0 --index-url https://download.pytorch.org/whl/cu118 git clone https://github.com/ultralytics/yolov5 cd yolov5 pip install -r requirements.txt pip install deep-sort-realtimedeep-sort-realtime是常用的 Deepsort 推理实现提供了DeepSort类输入检测框列表直接输出了带 track_id 的轨迹对象不用自己实现级联匹配。注意先装 torch 再安装 requirements.txt后者里的 numpy 版本不会因为重装依赖而跳变能减少不少隐性问题。4.2 推理阶段把 YOLOv5 和 Deepsort 接起来推理代码不复杂但需要同时关注检测置信度阈值、有效轨迹过滤和类别挂载三个地方。示例代码里已经标注了关键位置。import cv2 import torch from deep_sort_realtime.deepsort_tracker import DeepSort # 加载训练好的自定义权重 model torch.hub.load(ultralytics/yolov5, custom, pathruns/train/driver_v1/weights/best.pt) tracker DeepSort(max_age60, n_init3, max_cosine_distance0.3) # 只跟踪包含主目标的类别其余类别按 bbox 挂载到相近轨迹上 PRIMARY_CLASS 0 # face/head ALERT_CLASSES {1, 2, 3, 4} cap cv2.VideoCapture(demo.mp4) while cap.isOpened(): ret, frame cap.read() if not ret: break results model(frame) detections [] for *xyxy, conf, cls in results.xyxy[0].tolist(): if int(cls) PRIMARY_CLASS and conf 0.5: detections.append((xyxy, conf, int(cls))) tracks tracker.update_tracks(detections, frameframe) for track in tracks: if not track.is_confirmed() or track.time_since_update 1: continue track_id track.track_id bbox track.to_ltrb() # 将检测框对应的类别信息挂到轨迹上供行为判定层使用 alert_label classify_alert_bbox(results.xyxy[0], bbox) judge.push(track_id, alert_label, conf)逻辑说明detections 列表只持有主目标类别比如 faceDeepsort 只为驾驶员这个人维护身份每个跟踪到的轨迹再去关联原始检测结果中的危险行为类别把闭眼、手机这些属性挂到 track_id 下。过滤条件not track.is_confirmed()会跳过刚出现还没确认的轨迹time_since_update 1表示当前帧没有新的检测框、用的是卡尔曼预测位置预测位置的类别属性不可靠也应该跳过。4.3 行为判定状态机与触发参数行为判定层的核心是一个滑窗计数器。对于每个 track_id 维护一个长度固定的队列入队当前帧类别移除最早帧然后统计目标类别在窗口内的占比超过触发比例就报警。这个思路简单但参数设计决定系统是“乱报”还是“不报”。class FatigueJudge: def __init__(self, window45, trigger_ratio0.6): self.buffer {} self.window window self.trigger_ratio trigger_ratio def push(self, track_id, cls, conf): if track_id not in self.buffer: self.buffer[track_id] [] if conf 0.5: self.buffer[track_id].append(cls) if len(self.buffer[track_id]) self.window: self.buffer[track_id].pop(0) def should_alert(self, track_id, cls): seq self.buffer.get(track_id, []) if len(seq) self.window: return False hit sum(1 for c in seq if c cls) return hit / self.window self.trigger_ratio代码要点push 时只在置信度达标的情况下入队低于阈值的行为不参与累积防止低质量检测框污染滑窗should_alert 需要窗口填满才返回 True启动初期不报警。这个设计的代价是报警必然有延迟但换来了稳定性。窗口大小和触发比例按行为性质区分危险动作需要快速响应疲劳类需要长时间持续才确认参数预设如下表。行为类别判定窗口触发占比报警等级喝水、抽烟15 帧0.5普通使用手机30 帧0.6严重闭眼60 帧0.8严重打哈欠30 帧0.5普通窗口按 30 FPS 折算30 帧就是 1 秒60 帧是 2 秒。闭眼窗口设到 2 秒的用意是区分自然眨眼和疲劳闭眼自然眨眼通常在 300 毫秒以内不会连续命中 48 帧以上误报率能压下去一大截。5. 结果可信度检查离线视频回放与交付组织5.1 用离线回放脚本替代纯看 mAP跑完训练后 mAP 高不代表预警系统好用。模型只需在“闭眼”类别上识别得准而系统关心的是“提前多久、有没有误报、在哪个时段漏报”。我一般会用一段没参与训练的真实驾驶视频跑离线回放输出预测结果到 CSV再和人工标注比对。命令示例python detect_and_track.py \ --source test_clips/night_fatigue.mp4 \ --weights runs/train/driver_v1/weights/best.pt \ --deepsort-cfg configs/deepsort.yaml \ --csv-out result.csv输出的 CSV 包含帧号、track_id、类别、置信度和是否触发报警格式如下frame_id,track_id,class,conf,alert 135,3,phone,0.87, 136,3,phone,0.90, 137,3,phone,0.88,ALERT5.2 校准时盯住三个指标离线回放建议统计三个指标每个指标对应一个不同的坑指标计算方式报警系统里对应的坑召回率人工标注的危险片段中系统至少报警一次的比例漏报危险动作做了却没响误报率正常驾驶视频中触发报警的次数除以时长乱报正常眨眼、喝水也触发告警报警延迟报警帧号减去人工标注起始帧号太晚等低头玩手机抬头了才响误报率压到每 20 分钟一次以下系统才有上车价值。这里的调整入口不是重新训练而是先动行为判定层的窗口和触发占比误报多就拉大窗口或提高触发占比延迟大就缩短危险动作的判定窗口。动模型训练反而是最后的手段。5.3 源代码与文档的交付组织交付源码时要把训练、推理、配置、文档分层放好。README 里必须写清楚环境版本和装完环境后先跑哪个命令能出第一帧检测结果很多文档说明千篇一律地写功能唯独不写“第一步做什么”。driver_alert/ ├── README.md ├── requirements.txt ├── configs/deepsort.yaml ├── datasets/driver/ # 数据集的 reader 配置不含原始素材 ├── docs/ │ ├── 数据采集与标注说明.md │ └── 训练与调参流程.md ├── scripts/ │ ├── train.sh │ └── detect_and_track.py └── weights/ └── best.pt文档里额外写一段“已知限制”是提升可信度的关键包括墨镜遮挡对闭眼检测的影响程度、夜间红外摄像头下的表现、以及误报率是在什么测试集上统计出来的。校验片段独立留存不放进训练集后续每次调参都拿同一批片段跑回放对比 CSV 差异直到报警帧位置稳定收敛。本文还有配套的精品资源点击获取
返回列表