简介基于YOLOv5与Deepsort的驾驶员分心驾驶预警系统面向计算机视觉方向的毕业设计、课程设计及期末大作业解决驾驶途中疲劳状态与危险行为的自动识别问题。项目涵盖疲劳检测与分心行为识别两大模块源码包含完整工程、模型配置、预训练权重、交互界面及配套文档并附演示视频和人脸关键点模型文件便于对照运行效果。压缩包共59个文件以脚本、配置、编译文件为主另有说明手册、演示视频、权重、模型与界面文件整体约118MB。代码提供详细注释模块划分清晰涵盖检测、疲劳判断、界面控制等流程从环境配置到本地部署均有说明项目源自广受导师认可的高分毕业设计适合需要快速上手完整项目的新手与高年级学生参考。已有106人学习下载对期望获得高分或落地算法演示的读者这一整套代码加文档的组合能节省大量调试时间适合直接借鉴与二次扩展。1. 先想清楚这套系统检测的是“行为”不是“姿态”“基于深度学习YOLOv5Deepsort的驾驶员分心驾驶行为疲劳危险行为检测”这个标题在毕业设计里非常经典但它最容易让人误判难度。很多人以为核心是“把人框出来”实际做下来你会发现分心驾驶检测的难点从来不在检测而在“判断这个人正在干什么”。YOLOv5负责把驾驶员、手机、烟、水杯这些物体从视频帧里定位出来Deepsort负责给同一个驾驶员分配一个持续编号而“打电话”“打哈欠”“低头看手机”这些行为结论要靠最后一层行为判定逻辑去综合推断。本文按照“选型→训练→接入→避坑→验证”的顺序把一套能跑、能演示、能答辩的完整方案拆开讲。适合正在做毕业设计、课程设计或课题预研的从业者照着复现也适合第一次接触YOLOv5Deepsort组合的读者弄清楚这套管线每一条线缆到底接在哪。2. 为什么非要用YOLOv5Deepsort方案选型与系统架构分心驾驶检测这个题目理论上可以走三条路图像分类、人体姿态估计、目标检测多目标跟踪。三选一的答案直接影响后面所有代码和训练成本。这里直接给结论毕设和工程预研场景下YOLOv5Deepsort是性价比最高、最容易出成果的组合原因在于分心行为天然带有“空间时间”双重属性。2.1 分心行为的时间特性为什么单帧检测永远不够打电话不是一个瞬间动作而是“手机靠近耳朵”这个状态持续数秒打哈欠是嘴部张开并持续1到2秒低头看手机通常也有一个稳定的低头姿态。单帧图像分类模型只能回答“这一帧里驾驶员有没有拿手机”回答不了“他拿手机已经多久了”更区分不了“正在接通电话”和“刚拿起手机看一眼就放下”。后一个问题更致命单帧模型会把“正在喝水的1秒”和“拿起水杯但没喝”判成同一类。真实驾驶场景里短暂的姿态扰动特别多如果系统只看一帧就报警误报率会高到完全不可用。所以这个标题里“行为”两个字才是灵魂——行为是时间序列上的状态不是单帧快照。YOLOv5在这个架构里只负责“空间定位”输出每个目标的外接框、类别和置信度Deepsort负责“时间关联”给驾驶员、手机、水杯分配稳定的编号行为判定模块再基于编号和类别序列做连续帧统计。三层各干各的活互不干扰调试的时候能单独定位问题出在哪一层。这也是为什么不用端到端行为识别网络——那东西在黑匣子里出了问题你都不知道是该改标注还是改网络结构。2.2 数据流架构一条从摄像头到报警的完整管线把整个系统拆成五个模块每个模块的输入输出边界必须清晰否则后面联调会疯掉。模块输入输出技术选型视频采集USB摄像头/视频文件/车载录像单帧彩色图像OpenCV VideoCapture目标检测单帧图像bbox列表 类别 置信度YOLOv5多目标跟踪检测结果序列track_id 轨迹 类别Deepsort行为判定轨迹与类别序列行为状态正常/分心/疲劳规则引擎/连续帧计数预警输出行为状态画面标注 声音/界面报警OpenCV GUI 可选PyQt关键点在第2、3模块之间。YOLOv5的输出是一帧一帧独立的帧与帧之间没有“这个框是上帧那个框”的信息Deepsort要做的就是把相邻帧的检测框关联起来输出一个带track_id的稳定轨迹。track_id一旦稳定行为判定模块就有了“同一辆车里的同一个驾驶员连续做了什么事”的数据基础。这里有一个容易混淆的点Deepsort本身不知道驾驶员在做什么它只负责“把目标编好号”。实际项目中很多人把Deepsort当成行为识别器用结果调半天调不出结果。记住它的职责边界检测器负责看跟踪器负责记编号行为判定才是那个做决定的老板。2.3 类别体系设计这一步决定你的数据集怎么标类别设计是毕设里最容易被忽视、后期返工最痛的一步。建议用State Farm数据集和3MDAD数据集的类别体系做蓝本结合疲劳检测需求合并出一个8到10类的方案。# driver_behavior.yaml train: ../datasets/distracted_driver/images/train val: ../datasets/distracted_driver/images/val nc: 8 names: 0: safe_driving 1: texting_right 2: texting_left 3: calling_right 4: calling_left 5: drinking 6: reaching_behind 7: yawning 8: eyes_closed类别设计有三个原则类间互斥、数量克制、区分姿态与行为。互斥的意思是“喝水”和“手持电话”同时出现时你必须在标注规范里定一个优先级否则训练时模型会分裂数量克制是因为每新增一个类别就要对应几百张标注图10个类对毕设来说已经是上限。上面方案把“打哈欠”和“闭眼”单独拆出来是因为疲劳检测最核心的两个视觉特征就是嘴部张开持续和眼睑闭合持续建议单独成类而不是混进“其他”里。为什么把左右手打电话分开这是沿用State Farm的原始标注也方便后续做左右手行为对比分析。如果嫌类别多可以合并成phone_call。但注意一旦合并数据集的标注文件和训练配置文件要同步改漏改的话训练时会出现“标签索引越界”的报错这个坑后面避坑章节细说。3. 用YOLOv5训练驾驶员行为检测器从数据集到模型导出很多毕业设计卡在“环境配置好几天”“训练不收敛”“导出不会做”这三件事上。这一章直接给出一条能完整走通的路数据集怎么选、标注怎么转、训练参数怎么设、模型怎么导出。全程以yolov5s为基线资源有限也能跑。3.1 数据集准备公开数据集与自采数据的取舍分心驾驶方向的公开数据集不少毕设建议优先用两块State Farm Distracted Driver Detection静态图像、10类、约2.2万张和3MDAD真实车载视角视频、含驾驶员姿态。State Farm类别覆盖发短信、打电话、喝水、化妆、伸手够物等常见分心行为和2.3节的yaml结构几乎一一对应3MDAD是视频适合做Deepsort跟踪的验证集。需要注意版权和引用规范。公开数据集的标注格式通常是VOC或CSV比如State Farm用CSV文件描述类别和文件名并没有直接给出YOLO格式的txt标签所以需要写转换脚本。自采数据在毕设答辩里是加分项比如用手机前置摄像头模拟驾驶位视角采集几百张“喝水”“看手机”图片但一定要让被试签署知情同意书并且在文档里说明数据用途避免伦理问题。建议数据规模每个行为类别500到1000张总计6000到10000张。yolov5s在6000张规模上能达到不错的精度足够支撑行为判定模块的输入质量。数据太少就做增强YOLOv5自带的Mosaic、随机仿射变换在配置文件里默认开启不要额外写数据增强代码。3.2 格式转换VOC/CSV转成YOLO训练格式YOLOv5要求标签为纯文本txt每行一个目标类别id、归一化中心点x、归一化中心点y、归一化宽、归一化高。如果数据集给的是VOC XML可以用下面这个脚本批量转换。import os import xml.etree.ElementTree as ET # class_map 要和 driver_behavior.yaml 里的 names 保持一致 class_map {safe_driving: 0, texting_right: 1, texting_left: 2, calling_right: 3, calling_left: 4, drinking: 5, reaching_behind: 6, yawning: 7, eyes_closed: 8} def voc_to_yolo(xml_path, out_dir): tree ET.parse(xml_path) root tree.getroot() img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) lines [] for obj in root.iter(object): cls obj.find(name).text if cls not in class_map: continue box obj.find(bndbox) x_min max(float(box.find(xmin).text), 0) # 防负坐标 y_min max(float(box.find(ymin).text), 0) x_max min(float(box.find(xmax).text), img_w) y_max min(float(box.find(ymax).text), img_h) # YOLO 要求中心点和宽高都做归一化 x_center (x_min x_max) / 2.0 / img_w y_center (y_min y_max) / 2.0 / img_h w (x_max - x_min) / img_w h (y_max - y_min) / img_h lines.append(f{class_map[cls]} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) if lines: out_path os.path.join(out_dir, os.path.splitext(os.path.basename(xml_path))[0] .txt) with open(out_path, w) as f: f.write(\n.join(lines))转换逻辑里有三个细节值得注意。一是坐标裁剪原始VOC标注偶尔出现越界坐标比如x_max大于图片宽度如果不裁归一化后会超过1.0训练时YOLOv5会直接丢弃这个标签造成正样本损失。二是类别映射的稳定性class_map必须和yaml里的names一一对应改names不改map是训练出问题的常见根源。三是输出目录结构YOLOv5要求图片和txt同名并且train和val分开目录结构最好预处理成下面这样。训练集和验证集的划分建议按每类比例抽样不要直接随机切所有图片否则可能出现某一类全在验证集的情况。3.3 训练命令与超参数yolov5s如何快速收敛环境配置是毕业设计的第一个坎。YOLOv5官方仓库对Python版本要求比较严格建议直接用Anaconda建独立环境Python 3.8到3.10之间PyTorch按CUDA版本装。先把基础环境准备好再管训练不要用老环境硬跑新代码。# conda 环境建好之后训练命令如下 python train.py \ --data driver_behavior.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 100 \ --hyp hyp.scratch-low.yaml \ --patience 15 \ --workers 8关键参数逐个说--img 640是输入分辨率毕设级别640够用不要盲目上1280显存和速度都扛不住--batch 16在6GB显存左右能跑yolov5s显存不够就先降到8但batch太小时BatchNorm统计不稳定loss曲线会剧烈震荡--epochs 100配合--patience 15可以提前停止loss在15个epoch内不下降就自动结束省时间。超参数文件里最值得调的是数据增强强度。hyp.scratch-low.yaml里的hsv_h、hsv_s控制色彩增强degrees控制旋转translate控制平移。驾驶行为数据集的背景是车内环境颜色变化不大hsv_h建议从0.015调到0.01减少颜色扰动degrees保持默认0就好因为驾驶员很少倒立开车。翻车最惨的是degrees调太大模型把倒置的驾驶员也学进去了推理时正常坐姿反而检不出。训练结束看runs/train/exp/weights/目录取best.pt而不是last.pt。best.pt是验证集指标最优的权重last.pt是最后一个epoch的权重后者很可能已经过拟合。3.4 模型导出与部署选型从pt到onnx再到边缘设备毕设最终要演示大概率要在CPU机器或笔记本上跑。PyTorch模型在CPU上推理速度很慢yolov5s在纯CPU上640分辨率大约只有2到5FPS所以必须导出成更高效的中间表示。python export.py --weights runs/train/exp/weights/best.pt --include onnx --opset 12导出ONNX后用onnxruntime做CPU推理速度能提升2到3倍。如果部署目标里有树莓派或者Jetson Nano这类边缘设备还需要进一步转成TensorRT engine或者rknn格式但这些转换工具链跟硬件绑定比较深毕设阶段导出onnx并在onnxruntime里跑通就够了。模型导出容易踩的坑是opset版本。opset 11以下对某些算子的支持不完整比如Focus层和SiLU激活在旧版本上可能报错opset 12是相对稳妥的选择。导完之后用onnxruntime跑一遍同一张测试图和PyTorch输出对比确认精度没有明显下降再接入后面的跟踪模块。4. 接入Deepsort让检测框变成一条条稳定的“行为轨迹”检测模型训练好之后下一步就是把逐帧的检测结果交给跟踪器。Deepsort这个开源实现有很多变体毕设建议直接用deep_sort_realtime库接口清晰支持Python直接调用不需要自己写卡尔曼滤波和匈牙利匹配。但接入方式决定了系统能不能稳定工作——这一章讲清楚原理和参数再给一个能跑通的最小主流程。4.1 Deepsort核心机制与必调参数Deepsort的底层主要做四件事卡尔曼滤波预测目标位置、IoU和外观特征计算相似度、匈牙利算法做最优匹配、级联匹配优先处理持续可见的目标。这一套组合解决的是“遮挡后目标ID不丢”的问题。用deep_sort_realtime初始化跟踪器五个参数需要重点理解。from deep_sort_realtime.deepsort_tracker import DeepSort tracker DeepSort( max_age30, n_init3, max_iou_distance0.7, max_dist0.2, nn_budget100 )max_age控制轨迹丢失后保留多少帧。驾驶员偶尔转头、遮挡时检测框消失如果max_age太小重新出现后会被当成新目标track_id就会变行为判定随之断裂太大则会出现“鬼影”轨迹驾驶员明明已经下车了ID还在画面里飘。毕设场景30左右比较合理。n_init是轨迹确认帧数检测框连续出现3帧才正式确认目的是过滤掉单帧误检。max_iou_distance是匹配时允许的最大IoU距离值越大越容易把相隔较远的目标匹配上也会增加ID Switch。4.2 检测跟踪行为判定最小主流程给一段能直接跑通的示意代码注意这是教学简版真实工程要考虑状态重置、多目标并发判断等后面避坑章再补。逻辑上分成检测、跟踪、判定三段。import cv2 import torch from deep_sort_realtime.deepsort_tracker import DeepSort # 1. 加载检测器用本地权重不用 torch.hub 在线拉取 model torch.hub.load(ultralytics/yolov5, custom, pathruns/train/exp/weights/best.pt, force_reloadTrue) model.conf 0.4 model.iou 0.45 # 2. 初始化跟踪器 tracker DeepSort(max_age30, n_init3, max_iou_distance0.7) # 行为状态容器track_id - {类别id: 连续出现帧数} behavior_state {} ALARM_FRAMES 8 # 连续8帧命中某类行为才报警 cap cv2.VideoCapture(demo.mp4) while cap.isOpened(): ret, frame cap.read() if not ret: break results model(frame) detections [] for det in results.xyxy[0].cpu().numpy(): x1, y1, x2, y2, conf, cls det if cls 0: continue # 跳过 safe_driving detections.append(([x1, y1, x2, y2], conf, int(cls))) tracks tracker.update_tracks(detections, frameframe) for track in tracks: if not track.is_confirmed(): continue track_id track.track_id ltrb track.to_ltrb() track_cls track.det_class behavior_state.setdefault(track_id, {}) state behavior_state[track_id] state[track_cls] state.get(track_cls, 0) 1 if state[track_cls] ALARM_FRAMES: cv2.putText(frame, fALERT: cls {track_cls}, (int(ltrb[0]), int(ltrb[1]) - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 0, 255), 2) cv2.imshow(driver_behavior, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()代码逻辑拆开讲。检测段把YOLOv5输出转成deepsort需要的格式格式是[x1, y1, x2, y2, 置信度, 类别]注意bbox坐标必须是像素值而非归一化值Deepsort内部的卡尔曼滤波直接操作的是图像坐标很多初次接入的人在这里翻车归一化坐标没还原整个轨迹全是乱的。跟踪段调用update_tracks传入检测列表和当前帧图像内部会做预测、匹配、更新三步返回的track对象里带track_id。行为判定段的连续性计数是最简单的方案同一track_id连续8帧出现某类别行为就触发报警。但这里有个问题——不同行为类别之间要独立计数比如驾驶员喝口水后马上看手机两个动作的计数应该分别是各自状态的累计而不是共用一个计数器。上面代码用字典嵌套实现了按类别独立累计但还没有做重置逻辑这个在第5.4节补。4.3 行为判定阈值设计不同行为要不同的持续帧数行为判定阈值直接由报警及时性和误报率决定。太灵敏会误报太迟钝则漏报。给出我调过的参考值帧率按30FPS算。行为判定条件阈值参考说明打电话连续检测到calling类8帧约0.27秒手持电话靠近耳朵的稳定姿态喝水连续检测到drinking类8帧喝水动作通常持续1到3秒打哈欠连续检测到yawning类5帧哈欠张口幅度大但持续时间不长低头看手机头部bbox中心下移超过20%且持续15帧15帧0.5秒需要结合上一帧头部位置判断疲劳闭眼连续检测到eyes_closed类30帧1秒单帧闭眼可能是眨眼1秒闭眼才是疲劳信号表格里的帧数要按实际帧率换算。如果更换视频源或者摄像头帧率是25FPS阈值不能照抄需要按时间重新算。更稳妥的做法是直接用系统时间戳判断比如“eyes_closed累计时长超过0.8秒”而不是数帧。这样代码在不同帧率设备上表现一致。5. 避坑与排查从“跑通”到“跑对”的5个关键问题毕业设计最常见的状态是“代码跑起来了但结果不对”。下面五条都是YOLOv5Deepsort组合里真实高发的翻车点按现象、原因、解决三段式给出照着排查能省下大量时间。5.1 现象训练loss正常下降但验证集mAP始终为0原因绝大多数是标签类别索引错位。YOLOv5读txt标签时第一列数字是类别ID如果你的class_map从1开始计数而yaml里names从0开始那么所有标签都会被当成越界类别正样本一个都匹配不上mAP自然为0。解决写一个检查脚本遍历所有txt标签打印最大类别ID确认小于yaml里的nc值。同时抽一张图可视化标签框用YOLOv5自带工具python detect.py --weights runs/train/exp/weights/best.pt --source test.jpg如果检测结果里框的位置明显不对再检查图片与txt文件名是否一一对应后缀是否一致。这是最容易被忽略的图片是a.jpg标签是a.txt大小写不同也会漏读。5.2 现象驾驶员ID频繁变换同一个人的track_id一会是1一会是5原因检测框抖动导致IoU匹配失败或者低置信度检测框混入跟踪器。Deepsort依赖检测质量框抖得厉害前后帧重叠不够匈牙利匹配就会判定为新目标。解决提高检测置信度阈值到0.4以上过滤掉低置信度噪声框同时调大max_iou_distance到0.7允许前后帧框的偏移范围更大n_init保持3确保只有稳定出现的框才确认轨迹。如果还跳ID就从“检测平滑”入手对连续几帧的bbox做加权平均让框的输出更稳定而不是调跟踪器。5.3 现象CPU上推理只有2FPS视频卡成幻灯片原因一是在PyTorch上用CPU推理没有导出onnx二是输入分辨率过高三是用了yolov5m以上规格。驾驶员行为检测对实时性有一定要求纯PyTorch CPU方案基本不可用。解决先导出onnx改用onnxruntime推理yolov5s在640分辨率下CPU能跑到8到12FPS还不够再加跳帧策略每两帧检测一次行为判定用时间戳累积而不是逐帧累加。树莓派或Jetson设备上则必须转TensorRT同时把分辨率降到480甚至320检测精度下降但行为判定受影响不大因为行为判定本来就需要连续性不依赖单帧极高精度。5.4 现象疲劳检测疯狂误报正常驾驶也报“闭眼疲劳”原因把单帧检测到的“眼睛闭上”当成了疲劳。YOLOv5对眼睛小目标检测能力有限眨眼瞬间、戴墨镜、逆光时很容易误检成eyes_closed单帧误检直接触发报警必然误报率高。解决疲劳判定必须基于连续帧和时长推荐结合PERCLOS算法——统计单位时间内眼睛闭合帧数占总帧数的比例超过0.4才判定疲劳。具体到代码里给每类行为一个“激活清零”逻辑某类别连续出现才累加一旦该类别中断计数清零。上面4.2节的简版代码缺少这个重置实战里一定要补上。戴墨镜的情况根本检测不到眼睛需要加红外补光或放弃眼部检测改用头部姿态判断这个属于模块扩展毕设不一定做。5.5 现象答辩时被评委问“创新点在哪里”答不上来原因只把YOLOv5官方detect.py跑通没有任何属于自己的模块。检测器是通用的跟踪器是开源的行为判定只是几行计数整套系统等于公开代码拼接。解决把行为判定模块单独抽象成一个类写清楚判定规则和可调参数文档里放规则表再加一个可视化预警界面比如用PyQt叠加显示报警类别和时间戳如果时间和算力允许做一个消融实验——只用YOLOv5逐帧判断 vs YOLOv5Deepsort连续帧判断对比两类方案的误报率这个对比结果很有说服力能让答辩评委看到你理解“时序关联”的价值而不仅仅是会调库。6. 最后一步让系统成为能演示、能答辩的完整闭环6.1 一套可复现的验证体系检测、跟踪、行为三级指标先跑数据说话。检测级看mAP0.5用YOLOv5自带val.py评估跟踪级看ID Switch数量手动统计同一驾驶员在整段视频里的编号变化次数或者用MOT数据集评估工具计算MOTA行为级看行为判定的准确率和召回率——自己标注若干分钟视频片段比如“前60秒正常、中间30秒打电话、后30秒喝水”记录系统的报警时间戳对比人工标注时间戳计算正确率。三级指标分别对应模型、跟踪器、判定逻辑三个模块哪一级出问题就能直接定位到对应模块。指标含义参考达标线mAP0.5各类检测的平均精度0.85以上ID Switch次数驾驶员编号变化次数整段视频少于3次行为判定准确率报警类别与人工标注一致0.90以上6.2 一个让系统“更像系统”的进阶技巧滑动窗口多信号融合上面4.2节的连续帧计数是最小可用方案但它有两个缺陷阈值是固定的、只看单一信号。进阶做法是用滑动窗口做统计把“连续帧数”换成“时间窗口内行为占比”再接一个车速信号做联动误报率能再降一大截。from collections import deque # 每个track_id维护一个2秒窗口用上下文做综合判定 WINDOW 60 # 60帧约2秒 30FPS behavior_buffer {} def judge_behavior(track_id, cls): buf behavior_buffer.setdefault(track_id, deque(maxlenWINDOW)) buf.append(cls) ratio {c: buf.count(c) / len(buf) for c in set(buf)} if ratio.get(7, 0) 0.6: # 打哈欠占窗口60%以上 return fatigue_yawn if buf.count(8) 40: # 2秒内闭眼超过40帧 return fatigue_eye_close if ratio.get(3, 0) ratio.get(4, 0) 0.4: return distracted_calling return None滑动窗口的好处是平滑了偶发误检单独一两帧的“打哈欠”不会触发报警但连续两秒内多次打哈欠就非常可疑。窗口长度和阈值是绑定关系窗口越大判定越稳但报警滞后越明显40到60帧是一个平衡点。如果还想更进一步可以接OBD读取车速信号车辆静止或低于10km/h时驾驶员看手机属于正常操作不报警高速工况下同样的行为才报警。这一步不做也不影响完成度但做了它会在答辩时成为一个实实在在的加分项——它让“分心驾驶”从单纯的视觉概念变成了结合驾驶工况的工程系统。我最早做这个方向时只把YOLOv5的检测框叠加到视频上就去演示结果评委老师说“这只是一个目标检测demo不是分心驾驶行为检测系统”。后来把行为判定模块单独提炼出来用滑动窗口统计类别占比再接入车载信号联动系统才真正有了“行为检测”的样子。整套方案的价值其实就在这条从“看见”到“理解”的补全路径上希望帮到你。本文还有配套的精品资源点击获取