ARTICLE DETAIL

资讯详情

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

基于YOLOv5与DeepSORT的分心驾驶预警系统构建

基于YOLOv5与DeepSORT的分心驾驶预警系统构建 简介这是一份基于YOLOv5与Deepsort的驾驶员分心驾驶行为预警系统完整项目资源。系统面向深度学习与计算机视觉学习者用于解决驾驶过程中疲劳状态闭眼、打哈欠与危险行为玩手机、抽烟、喝水的实时检测与预警。资源共58个文件涵盖Python源码、YAML模型配置、训练权重、UI界面、Dlib关键点模型等压缩包约89MB。代码包含基于Dlib人脸关键点计算的Perclos疲劳程度评估以及YOLOv5对三种分心行为的识别同时提供已训练的best.pt权重、68点面部关键点模型、可直接运行的main.py与可视化主界面便于快速复现与二次开发。V1.0版本在原作者基础上重训练了YOLOv5权重精简了疲劳检测模块并优化了前端UI使整体结构更聚焦、运行更轻量。目前已有729人学习下载适合作为课程设计、毕业设计或工程实践参考。1. 驾驶员分心驾驶行为预警系统YOLOv5管看DeepSORT记得住一套基于深度学习的驾驶员分心驾驶行为预警系统核心要回答两个问题司机当前有没有在看路、手有没有离开方向盘以及这种异常状态持续了多久。把YOLOv5单独搬出来它能逐帧框出手机、烟、闭眼、打哈欠这些目标但单帧结果撑不起“预警”二字——只有把同一目标的轨迹串起来才能区分“低头看了一眼导航”和“连续闭眼两秒”。DeepSORT在系统里承担的就是这个任务给每个目标分配稳定ID、跨帧续上轨迹让判定逻辑拿到连续证据。下文会从数据集标注讲到训练命令、从DeepSORT参数调到预警阈值设置最后收敛到排查经验和边缘设备优化目标是让你在自有数据上复现出一套能实时跑通的分心驾驶预警流程。适合正在做毕设、车载安全项目或想落地目标跟踪方案的开发者。2. 检测加跟踪的选型逻辑为什么单帧检测扛不住驾驶场景2.1 单帧检测的三个局限漏检、误检、没有“持续状态”很多第一次做驾驶员监控的人拿到YOLOv5就跑把检测框画出来就觉得完事了。实际一测就发现驾驶舱里单帧检测的可靠性远低于公开榜单上的mAP数字。第一是漏检。手拿手机的动作可能只持续十几帧其中手机还被方向盘或安全带挡住了一部分。YOLO在某一帧漏了下一帧又检测到了中间这段空白如果不靠跟踪去补系统就会把一次完整的“玩手机”行为切成好几段计数完全乱掉。第二是误检。驾驶舱背景复杂安全带的斜条纹、方向盘上的按键、甚至阳光透过玻璃形成的亮斑都可能被框成目标。单帧检测只能按置信度过滤但置信度高不代表时序上合理——一个背景亮斑不可能连续出现在完全不同的位置。第三也是最关键的问题状态没有持续性。单帧检测只能回答“这一帧里有没有手机”回答不了“手机出现在视野里已经多久了”。而分心驾驶预警必须基于持续时间比如闭眼超过0.8秒或者手持电话超过2秒才触发警告这只能靠跨帧累积来判定。所以这套系统的正确拆法是YOLOv5负责“感知”DeepSORT负责“记忆”。感知把每一帧的目标变成带坐标的检测框记忆把同一目标的检测框连成轨迹后面再接一段简单的时序逻辑按轨迹持续时间判断是否需要预警。2.2 YOLOv5检测结果怎么变成DeepSORT的输入轨迹YOLOv5的网络结构图网上到处都是但落地时真正要理解的是它输出之后的那几步——NMS去重、置信度过滤、坐标格式转换。YOLOv5推理结果是一个xyxy, conf, cls结构的张量xyxy是目标框的左上角和右下角坐标。DeepSORT需要的输入格式不是xyxy而是xywh也就是目标的中心点坐标和宽高。这一步不做直接喂数据会让追踪器把坐标理解错。检测和追踪的接口处还有两个常见动作。一是按类别过滤只有我们关心的类别才送进DeepSORT比如cellphone、closed_eye、yawn、smoke其他背景目标直接丢弃省掉大量无效计算。二是按置信度过滤通常设0.3到0.5之间太低会把大量误检框送进追踪器导致轨迹混乱太高又会丢掉被遮挡的目标。DeepSORT拿到检测框后会做三件事用卡尔曼滤波器预测每个已有轨迹在下一帧的位置把预测位置和当前检测框做匹配匹配成功就用检测框更新轨迹状态。如果同一个目标连续多帧匹配成功轨迹就会从tentative变成confirmed系统开始给它累积行为状态。这就是“记忆”的机制轨迹是跨帧存在的检测只是轨迹的一个个输入片段。2.3 行为判定规则从坐标变化到疲劳/危险标签目标框坐标和轨迹ID最终要转化成预警动作这里需要一段独立的时序判定逻辑不能把检测帧直接当结论。我常用的是“按ID累计滑动窗口”方案。拿疲劳检测举例。DeepSORT会给每个目标一个稳定的track_id我们维护一个字典key是track_idvalue是一个计数器。当连续帧里检测到同一个ID的closed_eye类别计数器就加1一旦检测不到计数器清零。当计数器超过阈值比如25帧说明司机闭眼时间已经超过一秒按25FPS算触发疲劳预警。这里有个关键细节计数器必须在“同一ID”上累计否则目标一旦被重新分配ID计数器清零疲劳状态就被打断了。危险行为判定也需要坐标参与。比如“手持电话”和“接电话”的区别不能只看类别标签还要看手机框和头部框的位置关系。手机框中心点落在头部框下方且持续多帧才是正常接电话手机框在方向盘区域移动频繁更像是在操作手机。这些规则看起来简单但实际能过滤掉大量误报比盲目提高YOLO置信度阈值有效得多。3. 训练YOLOv5检测器驾驶员行为数据集的标注与命令3.1 行为类别设计与标注格式先想清楚要预警什么训练之前最重要的一步是把类别定清楚。驾驶员分心驾驶数据集常见的类别设计是7类左右类别太多会互相干扰太少又覆盖不了危险场景。我一般这样设计类别ID名称含义备注0normal正常驾驶负样本防止误检1cellphone手持并使用手机危险行为2smoke吸烟危险行为3yawn打哈欠疲劳信号4closed_eye闭眼超过0.3秒疲劳信号5reach伸手去够中控台/副驾危险行为标注格式按YOLO的通用规范每张图片对应一个同名.txt文件每行是一个目标格式为class_id x_center y_center width height四个坐标值都除以图片宽高做了归一化。标注工具用labelImg就够画框时注意把手机连同手一起框进去不要只框手机屏幕——DeepSORT后续要做外观特征匹配框内信息太单一会让ID切换变得频繁。数据收集阶段要给每类足量正样本。驾驶舱场景里最缺的是closed_eye和yawn这类疲劳样本因为真实驾驶中很难拍到。常见做法是让受试者在模拟驾驶舱里做标准动作通过摄像头录制后抽帧。抽帧时不要只抽动作最明显的帧要把动作开始、持续、结束三个阶段都保留这样训练的模型才能应对真实场景里“动作未完全展开”的情况。3.2 跑通训练的最小命令集从yolov5s权重开始数据标好之后目录结构按YOLOv5惯例组织datasets/driver/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ └── driver.yamldriver.yaml是数据集描述文件告诉训练脚本数据在哪、有多少类别、类别名是什么# driver.yaml path: ../datasets/driver train: images/train val: images/val nc: 6 names: 0: normal 1: cellphone 2: smoke 3: yawn 4: closed_eye 5: reach训练命令是YOLOv5仓库的标准入口# 使用yolov5s预训练权重训练100轮 python train.py \ --data driver.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 100 \ --cache这里有个容易忽略的点--weights yolov5s.pt加载的是在COCO上预训练过的权重而不是从头训练。驾驶员行为目标和COCO的person、cell phone有重合用预训练权重做迁移学习能明显加快收敛一般60轮左右就能看到mAP超过80%。如果是完全冷启动100轮都可能不够。--cache参数作用是把训练图片预加载进内存能省掉每轮迭代都去磁盘读图的时间代价是吃内存。如果机器只有16GB内存开--cache前先确认数据集总量别超过10GB否则会触发系统换页训练速度反而更慢。训练完看两个指标mAP0.5代表粗定位精度至少要到85%以上mAP0.5:0.95代表精确定位能力目标越小这个指标越难提升。驾驶舱场景里手机、烟头都是小目标这个指标比mAP0.5更能反映真实效果。如果mAP0.5:0.95明显低于0.5优先去优化小目标而不是盲目堆训练轮次。3.3 必调的超参数与模型选型小目标多、实时性要求高YOLOv5的超参数在data/hyps/hyp.scratch-low.yaml里训练时可以修改或通过命令行覆盖。几个必须理解的lr0是初始学习率默认0.01。驾驶行为数据集通常只有几千张比COCO小得多学习率需要调低到0.003到0.005之间防止在少量样本上过拟合。fl_gamma是Focal Loss的gamma参数默认0.0表示不用Focal Loss。小目标检测时建议改成1.0它能让模型更关注难以分类的困难样本比如被遮挡的手机但注意调太高会让训练不稳定。anchors参数由另一个文件控制YOLOv5在训练前会自动用K-Means对训练集标注框聚类自动生成匹配当前数据集的Anchor。这就是autoanchor机制。小目标手机、烟头占比高时默认的640分辨率下标注框可能只有20x40像素左右自动聚类出来的Anchor会偏小这是好事。如果发现小目标漏检严重可以显式指定--noautoanchor关闭自动聚类手动调整Anchor尺度但前提是你对当前数据的先验分布非常熟悉否则不建议关。模型选型也是个取舍。yolov5s在Jetson Nano上大概能跑到25FPSyolov5m精度高一些但速度降到15FPSyolov5l和yolov5x基本不适合实时驾驶监控适合离线批量测试。我的建议是先在yolov5l上把数据集的检测上限跑出来确认算法可行性再切回yolov5s做实时部署。如果目标设备是树莓派4B这类边缘板卡还要考虑用TensorRT或ONNX导出配合半精度推理yolov5s的推理耗时可以压到40ms以内。4. 集成DeepSORT建立持续轨迹追踪参数与预警阈值4.1 DeepSORT对检测结果做了什么卡尔曼滤波与级联匹配DeepSORT全称是Deep Simple Online and Realtime Tracking它是在SORT基础上加入外观特征的分支。SORT只用运动信息位置、速度做关联目标一旦被遮挡再出现就很容易丢失ID。DeepSORT给每个检测框提取一个128维的外观特征向量同一目标的特征向量在相邻帧应当接近——用这个向量计算余弦距离和运动信息的马氏距离一起作为匹配代价。这样就算运动预测出现偏差只要外观特征还相似轨迹也能续上。实际流程可以拆成四步。第一步卡尔曼滤波给每条已确认轨迹预测下一帧的边界框位置第二步把新检测框和预测框做级联匹配优先匹配“最近一直在更新”的轨迹而不是老轨迹第三步剩余未匹配的检测框再和未匹配的轨迹做IOU匹配专门处理短时间遮挡第四步完全没有匹配上的检测框初始化为新轨迹连续n_init帧都匹配成功才升级为confirmed轨迹否则删除。有个容易误解的点DeepSORT并不是“把多次检测结果简单连起来”它内部维护的轨迹是卡尔曼滤波器的状态变量检测框只是用来做校正的观测值。所以即使某一帧YOLO漏检卡尔曼滤波器还能用前一帧的速度和位置预测出当前帧的目标位置轨迹不会立刻断开。这就是它能弥补检测漏检的关键机制。4.2 把YOLOv5和DeepSORT接起来的最小代码框架实际项目中我不太用原始论文代码而是用封装好的DeepSORT实现来降低集成成本。核心代码框架如下# 完整串联 YOLOv5 检测 DeepSORT 跟踪的最小框架 import cv2 import torch from deep_sort import DeepSort # 加载 YOLOv5 模型 yolo_model torch.hub.load(ultralytics/yolov5, yolov5s, pretrainedTrue) # 初始化 DeepSORT 追踪器 tracker DeepSort( model_pathdeep_sort/deep/checkpoint/ckpt.t7, max_dist0.2, # 外观特征余弦距离阈值 max_iou_distance0.7, # IOU 距离阈值 max_age30, # 轨迹丢失后保留的帧数 n_init2, # 成为已确认轨迹需要连续匹配帧数 nn_budget100 # 外观特征库上限 ) cap cv2.VideoCapture(0) while True: ret, frame cap.read() # 1. YOLOv5 推理 results yolo_model(frame) detections results.xyxy[0].cpu().numpy() # [x1,y1,x2,y2,conf,cls] bbox_xywh, confs [], [] for det in detections: x1, y1, x2, y2, conf, cls det # 只保留需要跟踪的类别这里以 cellphone 为例 if int(cls) ! 1: continue bbox_xywh.append([(x1 x2) / 2, (y1 y2) / 2, x2 - x1, y2 - y1]) confs.append(conf) # 2. DeepSORT 更新轨迹 if len(bbox_xywh) 0: outputs tracker.update(bbox_xywh, confs, frame) for out in outputs: x1, y1, x2, y2, track_id out # 3. 按 track_id 累加持续帧数 # 连续 30 帧约1.2秒持续检测到手机则触发预警 cv2.rectangle(frame, (int(x1), int(y1)), (int(x2), int(y2)), (0, 0, 255), 2) cv2.putText(frame, fid:{track_id}, (int(x1), int(y1) - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 0, 255), 2) cv2.imshow(driver monitor, frame) if cv2.waitKey(1) 27: break cap.release() cv2.destroyAllWindows()逐段说明。YOLO检测结果先做类别过滤只保留cellphone这样DeepSORT只需要管理一条或两条目标轨迹计算量小很多。DeepSORT的update()方法接收三个参数bbox_xywh、confs和原始帧。原始帧用来提取外观特征如果传成缩放后的帧特征会发生变化导致ID切换变多。输出每一行包含目标框坐标和track_id。最后一步算法根据ID累计是否超过阈值来产生预警事件。这个框架有个小坑不同DeepSORT封装版本的输出格式不太一样有的还会返回cls字段有的不返回。如果你的封装不返回cls要实现类别跟踪就得自己维护一个track_id - 类别的映射表在每次update之后根据检测框和轨迹框的IOU来确定轨迹属于哪个类别。这个映射逻辑必须在每一帧做完匹配后立刻更新否则累计器会拿到错误类别。4.3 追踪参数怎么调max_age、n_init、max_dist参数调优是DeepSORT环节最依赖经验的部分一组参数在不同场景表现差异非常大。我整理一份生效逻辑参数默认值作用驾驶场景建议max_dist0.2外观特征匹配阈值大于则拒绝匹配0.2到0.4特征质量差可放宽到0.4max_iou_distance0.7最后阶段IOU匹配阈值默认即可max_age30轨迹未更新时的保留帧数30到50摄像头帧率低时调到50n_init2初始化为确认轨迹所需连续匹配帧数2或3nn_budget100近期外观特征库大小100内存不够降到50设max_age时要看摄像头帧率。车载摄像头一般25到30FPSmax_age30意味着目标消失1秒后轨迹才会丢弃。拥堵时手机会被方向盘或手臂遮住1到2帧这个值足够维持轨迹不中断。但如果帧率只有10FPSmax_age就得调到50左右否则稍微一遮挡轨迹就断了疲劳计数器清零。反过来说max_age调太大也有副作用目标早走了轨迹还占着位置新检测框匹配不到ID会被推迟分配。nn_budget是一个容易忽略的内存敏感参数。它限制外观特征库的大小特征超出上限后会丢弃最早的样本。内存紧张时可以降到50。它的实际影响是轨迹稳定后特征库里的特征分布是否还能代表当前外观。驾驶舱里光照变化快特征库里保留太多“旧样子”反而会让匹配失效。最后注意DeepSORT里那个“deep”指的是一段独立的ReID特征提取网络不是YOLO的骨架。默认的ckpt.t7是大规模行人重识别数据集训练出来的在驾驶舱场景里效果只能说可用常见做法是用自己的驾驶员数据对ReID模型做微调这一步通常能让ID切换次数下降一半以上。5. 踩坑与排查让预警系统翻车的五个典型环节5.1 手机小目标漏检严重现象画面里手机肉眼可见YOLOv5的检测置信度却在0.25以下大量漏检。尤其在手机离摄像头超过1米、屏幕朝上时目标只有15x30像素大小检测框时有时无。原因训练分辨率640和Anchor尺度不匹配小目标。YOLOv5的autoanchor虽然会在训练时自动聚类Anchor但640分辨率下每个网格能表达的细节有限15像素宽的目标在网格上只有不到一个格子特征太弱。解决三步走。第一步把训练和推理的--img都提到960甚至1280小目标占的像素数变多检测置信度会立刻上升。代价是推理时间增加约30%。第二步检查训练结果里的Anchor分布用--noautoanchor关闭自动聚类手动添加更小的Anchor尺寸例如在P3特征层添加[4, 6, 6, 12, 10, 14]这组小比例Anchor。第三步如果还漏考虑单独训练一个手部检测模型把“手手机”作为特殊目标这个方案在KD-DD数据集上是验证过的比调Anchor更省事。5.2 ID频繁切换打乱疲劳计数现象视频里同一个驾驶员闭眼动作还没结束轨迹ID从id:3跳成了id:9。计数器监听到ID变化立刻清零疲劳预警永远触发不了。原因目标发生剧烈姿态变化司机低头再抬头时外观特征向量漂移余弦距离超过max_distDeepSORT判定为两个目标。也可能是n_init设太小新轨迹过早确认把正常轨迹挤掉了。解决最直接的方法是把max_dist从0.2放宽到0.3或0.4给特征匹配更大的容忍度。同时确认n_init不小于2避免单帧误匹配就确认轨迹。还可以用ReID特征模型微调数据让外观特征在低头抬头这种局部变化下保持稳定。如果这些手段都做了ID还是抖动就得改判定逻辑疲劳计数容错设计允许轨迹断2帧再续接而不是一断就清零。5.3 夜间与逆光条件下检测失灵现象白天mAP 0.85晚上识别率掉到0.4。逆光时整个驾驶员脸部是黑的closed_eye类别几乎全部漏检夜间仪表盘亮光还带来大量背景误检。原因训练数据大部分是白天采集的光照分布单一。深度学习模型对光照分布非常敏感目标出现在训练分布之外的场景时特征激活模式完全不可用。解决收集夜间数据是最根本的办法。没有渠道连夜录制就用图像增强方法模拟把训练集随机调低亮度、加高斯噪声、做直方图均衡化。OpenCV的cv2.convertScaleAbs做亮度抖动加上cv2.CLAHE做局部对比度增强这两步能让模型对光照变化的鲁棒性提升明显。部署时也可以在预处理阶段加CLAHE但要注意推理端的预处理必须和训练端保持一致否则又是一次分布漂移。5.4 帧率被DeepSORT拖到不可用现象单独跑YOLOv5有30FPS接上DeepSORT掉到10FPS。驾驶员头部快速转动时画面明显卡顿警报晚1秒才出。原因DeepSORT的update()里包含一个独立的ReID特征提取网络每一帧都要对每个检测框跑一次前向推理。检测框数量多或者特征网络偏重时这块计算量甚至超过YOLO本身。解决最有效的方案是“跳帧跟踪”——不是每帧都做DeepSORT更新而是每隔2帧更新一次帧率立刻提升。跳掉的中间帧依然做YOLO检测检测结果只画框不送追踪器。这个方案的逻辑是驾驶舱行为变化幅度有限2帧间隔约66ms内目标位移不会超过半个框宽卡尔曼滤波器依然能接上轨迹。另外只对需要跟踪的类别做特征提取比如只跟踪cellphone不跟踪normal计算量可以再降一半。更彻底的办法是用TensorRT合并YOLO推理再对ReID网络做INT8量化这在Jetson和RK3568板卡上都有成熟流程。5.5 预警误报与迟报的逻辑问题现象司机只是伸了个懒腰系统判定为疲劳真正闭眼1秒了系统却要等2秒后才给出声音警报。前者是误报后者是迟报两个问题同时出现。原因判定逻辑过于简单。疲劳判定只看closed_eye持续帧数完全不看其他类别信息yawn、closed_eye这类动作本来就容易被混淆。另外连续帧计数只认一个ID在ID切换频繁时实际累积时间远低于设定阈值。解决预警逻辑改成滑动窗口统计不依赖单个ID。在最近N帧比如50帧里统计该驾驶员区域出现closed_eye和yawn的帧数占比。这个统计可以放宽到不同ID之间只要目标区域位置连续ID变了也能继续累积。判定条件也改成多条件组合闭眼帧数占比超过30%且伴随头部低垂才触发疲劳预警纯yawn单独出现不触发需要连续出现3次以上才升级为疲劳信号。这样改完误报率能降一半以上迟报问题也缓解了——警告不需要等完整的单一轨迹持续而是看滑动窗口内的证据密度。6. 评估与进阶验证预警准确率、跑上边缘设备的优化路径系统跑通了接下来要回答“它到底可不可靠”。我的验证习惯是录制一段包含白天、夜间、强逆光三种场景的5分钟视频逐帧人工标注“应该触发预警”的时间段。然后把系统跑一遍把输出预警事件的时间点和人工标注对齐统计精确率和召回率。精确率看的是“触发的预警里有多少是真危险行为”召回率看的是“真危险行为里有多少被系统抓住了”。这两个指标通常是一对矛盾阈值设宽松召回率上去了精确率下来了阈值设严格精确率上去了漏报多了。驾驶场景宁可多报不可漏报我一般把阈值定在召回率95%对应的位置再接受精确率70%左右后续用更细的时序逻辑把误报慢慢压下来。预警时间窗口也要作为超参数做一组实验。我通常会对比“连续10帧闭眼触发”、“连续20帧触发”、“连续30帧触发”三档每档都统计一次误报率和平均响应延迟。结论往往是10帧档误报率太高30帧档响应太慢20帧档对25FPS摄像头来说大概是0.8秒综合表现最好。这个0.8秒也符合人因工程常识——既不会对短暂眨眼过敏也不至于在真正疲劳时耽误救援。进阶路径上建议做两件事。第一件是把时序判定从帧计数升级为Temporal Convolutional Network或LSTM让模型自己学习“闭眼-低头-哈欠”这类行为序列代替手写规则。这个改进在State Farm数据集上能稳定提高分心行为分类的F1分数但需要你先积累一批标注好的行为切片起步成本不低。第二件是边缘设备部署。模型导出成ONNX后用TensorRT或RKNN工具链转成板卡格式YOLOv5s在Jetson Nano上能跑到20FPS以上在RK3568上做INT8量化后也能维持15FPS满足基本的实时监控需求。最后讲一条自己反复踩过的教训这套系统里最难的不是模型本身而是把数据分布覆盖全。第一次做的时候我只用了白天同角度的数据训练测试时换个摄像头位置mAP直接掉了20个点。后来强制要求数据里必须有不同光照、不同座位高度、不同驾驶员体型模型才真正变得可用。这个项目没有捷径数据多样性比调参重要得多——这个认知至少帮我省下了两个月返工时间。希望帮到你祝早点把预警系统跑起来。本文还有配套的精品资源点击获取
返回列表