简介:本资源面向智能交通与车载安全领域的算法工程师、高校研究者及计算机视觉初学者,提供一套完整的驾驶员分神行为检测解决方案,聚焦抽烟、打电话、喝水、吃东西四类高风险驾驶行为识别。压缩包含2000个文件,主体为1994个YOLO格式txt标签文件及配套的3个Python主程序(含PyQt5可视化界面)、3份PDF环境配置与使用指南,整体大小356.88MB。已有628人学习下载,说明其在DMS(Driver Monitoring System)实际落地场景中具备较强参考价值。资源附带已划分好的5000+张高质量图像数据集(train/val/test三级目录完备),并提供标准data.yaml配置文件,支持YOLOv5至YOLOv9系列模型直接训练;PyQt界面可一键加载模型、实时视频流检测与结果可视化,大幅降低部署门槛;两份环境配置PDF覆盖主流YOLO版本适配要点,显著减少环境搭建踩坑成本。 前一阵把YOLOv5和DMS(Driver Monitoring System,驾驶员监控系统)凑到了一起,做了个能识别抽烟、打电话、喝水、吃东西四类分神行为的完整工程,外带一套PyQt界面。说实话,这类需求最近咨询量很大——两客一危、网约车平台、物流车队、驾校考试都在推,但网上教程大多只教你怎么跑通官方demo,距离“能用的工程”还差着数据集、训练调优、界面联动、模型部署一大截。这篇我打算把整个链路从头到尾捋一遍,讲讲踩过的坑、看过的指标、以及一些常规文档里不会写的小门道,适合手里有基础Python/PyTorch概念、想把这个项目做成毕设或真实产品的朋友参考。
DMS本身不是一个新概念,但YOLOv5的成熟度让它的实现门槛低了一个量级。以前做驾驶行为识别,要么靠传统图像处理做手势/特征判断,要么上姿态估计,复杂度高、鲁棒性差;现在直接用目标检测框住手部区域和物体区域,再从逻辑层面判断行为状态,工程上简单不少。不过简单归简单,真正要交付一个能稳定识别抽烟、打电话、喝水、吃东西的DMS,还是会碰到一系列现实问题,下面逐个拆。
1. 项目拆解:DMS检测到底在解决什么问题
1.1 四类行为的检测逻辑:本质上是在找“物”和“手的交互”
先说一个关键的概念:抽烟、打电话、喝水、吃东西这四类行为,如果单纯靠姿态估计或者行为识别模型来做,训练数据量要求高、类别容易混淆,而且司机座椅角度不同、体型不同,泛化会很差。但如果你把问题降维成“目标检测+关系判断”,难度马上不一样了——你需要检测的目标是什么?是手、嘴部区域,以及香烟、手机、水杯、食物这类物件。检测模型输出的就是若干个边界框,然后根据框与框的位置关系做行为判定。
这也是为什么选YOLOv5而不是直接上时空卷积网络或Transformer的重要原因。DMS场景是实时视频流,需要低延迟、高帧率推理,YOLOv5在边缘设备(Jetson、RK3588、RV1106这类平台)上都有成熟的部署方案,量化、裁剪、TensorRT转换的坑基本被踩平了,社区资料丰富,任何一个环节出了问题都能搜到对应解法。
不过在真正标注数据前,我建议先把行为判定规则定义清楚,不然标注员会疯掉。比如“打电话”和“手持手机”怎么区分?“喝水”和“吃东西”在瓶口/食物靠近嘴部时怎么界定?我的做法是定义一套基于检测框的判定规则:只有当“目标物体框”与“嘴部区域框”发生重叠且持续若干帧时,才判定为对应行为。“手持手机但没靠近耳边”算手持状态,不算打电话。这样做的好处是,模型只需要输出物体、手、人脸的稳定检测框,行为逻辑交给后处理,系统的可解释性和调试性都强很多。
1.2 功能范围与痛点:单模型多任务还是多模型协作
刚开始不少人会想:直接把抽烟、喝水、吃东西、打电话当成四个类别,训练一个四分类的目标检测模型行不行?理论上可以,实际操作中你会发现一个很典型的问题——类别之间的数据极度不平衡。抽烟的人比喝水的人多得多,因为喝水这个动作本身就很快,几帧就结束了,图像样本难采集。另外,“吃东西”和“喝水”的手部动作高度相似,单纯靠物体类别区分容易把食物和水杯搞混。
我做的时候采用了“基础检测模型 + 后处理状态机”的方案:训练一个模型,检测类别包括person(或face)、hand、phone、cigarette、cup/bottle、food,然后由后处理逻辑判断当前行为。虽然任务还是多类别检测,但每个类别都很独立、边界清晰,比直接把“smoking”、“calling”、“drinking”、“eating”作为类别训练更容易收敛。特别说明:如果你的标注数据量不大,这种方式对单类别的检测准确率提升非常明显,因为每个类别的特征更纯粹。
1.3 界面层的作用:从“算法demo”到“可用系统”
PyQt界面在这个项目里不能简单看成是一个“显示框”。真正落地DMS,界面至少要有三块功能:实时视频显示与检测结果叠加、报警事件记录与图片抓拍、参数配置(摄像头源、检测阈值、报警延迟等)。如果做车队管理系统,还需要对接数据库或云端API,把报警事件推送到管理后台。界面是算法与用户之间的唯一交互层,做得不好,哪怕模型99%准确率,客户也会觉得“这系统不行”。
2. 数据集构建:决定DMS模型上限的关键环节
2.1 数据来源与数量规划:别指望一次性找齐
DMS数据集最尴尬的地方在于:公开的驾驶行为数据集(比如State Farm Distracted Driver Detection、3MDAD)不少,但大多是分心驾驶分类数据,不是目标检测格式,而且场景偏向欧美人种和国外车型。直接拿来做国内DMS,泛化存在明显问题。我的建议是组合方案:先用公开数据集做预训练,再用自采数据做微调。
具体来说,第一阶段跑通流程时,可以用公开数据集里的图像,转成YOLO格式后训练一版基线模型,看看类别是否可分、loss能不能正常下降。第二阶段再针对实际场景自采数据,重点采集不同光照(白天、逆光、夜间红外)、不同姿态(司机高矮胖瘦、座椅前后)、不同设备安装角度(仪表台、后视镜附近)的图像。如果项目预算有限,自采数据建议至少保证每个类别1000~2000张有效图像,注意这里的“有效”指标注框清晰、包含典型动作姿态、有背景多样性,不是同一段视频连续抽帧。
题外话,别忽视合成数据。用3D模型渲染或图像合成(把手机、香烟、水杯等物体贴到真实驾驶图像上)可以快速扩充边缘案例,比如手机在方向盘下方、香烟在嘴角的瞬间。合成数据在DMS这种场景中效果不错,因为物体本身纹理清晰、遮挡关系比较简单。
2.2 数据标注规范:这几条规则定下来再动手
标注质量直接决定模型训练效果,比调参重要十倍。围绕DMS场景,我认为有以下几条核心标注规则:
- 边界框要贴紧目标轮廓,尤其是香烟、水杯这类小目标,框得越准,小目标检测效果越好。
- 严重遮挡的物体要标注吗?我的经验是:可见面积小于30%的物体不标,避免给模型传递错误信息。
- 嘴部区域建议单独标注为“face”或“mouth”,后处理判断行为时要用到。如果模型能稳定输出嘴部框,后续“喝水/抽烟/吃东西”的行为判定就简单很多。
- 打电话和手持手机不要混标,类别就是“phone”,是否打电话由后处理判定,否则标注员的主观判断会引入大量噪声。
- 夜间红外图像和白天可见光图像建议分目录存放,或者统一做灰度增强后再训练,不然模型会在颜色特征上过拟合。
2.3 数据增强策略:DMS场景最需要的几种
YOLOv5自带的数据增强(mosaic、mixup、随机透视、HSV变换等)对通用目标检测很有效,但DMS场景有它的特殊性。我建议在默认增强基础上,重点强化以下策略:
- 亮度/对比度扰动:模拟不同时间段的光照变化,尤其是逆光和隧道出入口的明暗突变。
- 高斯噪声:模拟低照度下的传感器噪声,对夜间红外场景有正向帮助。
- 随机遮挡:模拟方向盘、A柱对物体和手部的遮挡,增强模型在部分遮挡情况下的鲁棒性。
- 小目标复制粘贴:把尺寸较小的手机、香烟样本随机复制到图像的合理区域(比如中控台、方向盘附近),解决小目标样本不足问题。
注意:不要对整个训练集无脑使用过强的mosaic增强。DMS场景中手部、手机这类目标尺度变化不大,mosaic过度反而会让模型学到奇怪的上下文关联,我在实验中就遇到过mosaic比例过高导致夜间样本检测率下降的情况。
3. YOLOv5训练全流程:从环境配置到模型收敛
3.1 环境搭建:版本匹配才是最大的坑
YOLOv5的安装步骤并不复杂,常遇到的问题几乎都集中在版本匹配上。以我常用的组合为例:Python 3.8 + PyTorch 1.10.0(或1.12.0)+ CUDA 11.3,clone官方仓库后直接跑pip install -r requirements.txt,基本能一次性通过。
这里需要重点提醒:很多初学者在Windows上搭环境,会直接conda install pytorch装了CPU版本,训练速度慢还不报错,跑了一个epoch才发现不对。建议装完先执行python -c "import torch; print(torch.cuda.is_available())",如果是True再继续。
另外,如果目标设备是RV1106、RK3568这类边缘芯片,训练阶段最好还在PC上做,训练环境生成.pt权重后再转成对应平台的推理格式。边缘平台一般不支持大batch训练,强行在板子上训练既慢又容易内存溢出。这类板子通常需要你针对特定芯片做模型转换,比如RV1106的RKNN模型转换,对算子的支持有限,最好提前查一下YOLOv5使用的算子是否都被支持,不然部署阶段会被卡住。
3.2 超参数配置:一组能直接用的起步参数
YOLOv5提供了很多超参数,默认配置在COCO上表现不错,但DMS场景相对简单,类别少、目标类型固定,需要调整的地方不多。我的初始参数如下:
- imgsz: 640(DMS场景下目标不算太小,640足够;如果用TensorRT量化,可以尝试480或416,精度损失不大,速度更快)
- batch: 16或32(取决于GPU显存,先看训练过程GPU利用率是否大于90%,再用nvidia-smi观察显存余量,不要一上来就选最大batch)
- epochs: 100~200(数据量不大的话100轮足够,关键是观察val loss是否在后期回升)
- hyp: 保留默认hyp.scratch-low,只调两个:fl_gamma设置为0.0(即不使用focal loss,因为类别相对均衡),mosaic设置为1.0但配合我前面说的自定义增强使用
具体训练命令通常是:
python train.py --img 640 --batch 16 --epochs 150 --data dms.yaml --weights yolov5s.pt --cache--cache参数可以一次性把图像缓存到内存中,小数据集下能大幅缩短训练时间。个人经验是先跑50个epoch快速验证数据标注和配置有没有问题,再跑完整150个epoch,避免拿着有问题的数据傻等几个小时。
3.3 训练过程监控与收敛判断
训练时不要只盯着那个花花绿绿的loss曲线,你真正要关心的是三个东西:
- train loss和val loss的距离:如果两者差距越拉越大,说明过拟合开始,需要增加数据增强或提前停。
- P/R曲线:DMS场景更看重召回率(漏报比误报更可怕),所以在模型调优阶段可以适当牺牲一点precision,把conf_thres从默认的0.25降到0.15甚至0.1。
- 每个类别的AP单独看:不能只看mAP,因为“喝水”这类少见类别的AP往往远低于“抽烟”,如果喝水AP明显偏低,优先去补数据,而不是盲目调参。
贴一组我的实际训练记录:数据集共8000张左右,四类行为相关目标共20000个框,训练150轮后mAP@0.5达到0.92,mAP@0.5:0.95在0.68左右,其中phone和cigarette的AP较高,food和bottle略低。在Jetson Orin Nano上实测推理速度约25ms/帧(TensorRT FP16),完全满足实时性要求。
3.4 小目标优化:手机、香烟这类小物体怎么提升召回
DMS场景中手机和香烟常常只占图像面积的很小一部分,尤其在1080P视频流中,可能只有几十个像素宽。针对这个问题,我认为优先做两件事:
一是把输入分辨率从640提到960。虽然推理速度会下降约30%,但小目标召回率提升明显。如果你用的是RTX 3060以上显卡做推理,这个开销可以接受;如果部署在边缘盒子,建议配合TensorRT做FP16或INT8量化来弥补速度损失。
二是调整anchor。YOLOv5会自动计算anchor,但如果你用的是自定义数据集,训练前建议先跑一下检查anchor是否合理。可以在训练日志中看到kmeans_anchors的数值,如果绝大多数anchors都在10~30像素区间,说明默认anchors对数据集来说偏大,需要调整anchor数量或尺寸。
4. PyQt界面开发:把模型能力变成用户可用的产品
4.1 界面框架设计:三个页面打底
PyQt界面的功能规划,我建议按以下三个页面来组织:
- 实时监控页:显示摄像头/视频流画面,叠加检测框和类别标签;下方放状态栏,当前行为、报警等级、连续报警帧数。
- 事件记录页:展示历史报警事件列表,包含时间、类型、抓拍图片缩略图,支持点击查看大图。事件记录可以本地存SQLite,轻量、无需额外部署数据库服务。
- 参数设置页:选择视频源(本地摄像头/RTSP流)、设置检测阈值、报警延迟帧数、是否开启声音报警、模型文件路径。
这套结构基本能覆盖DMS从单机演示到后台管理对接的绝大多数需求。如果只是毕设展示,第一页和第三页就够了;如果是商用demo,事件记录页一定要有,这是给客户演示时最能加分的功能。
4.2 多线程处理:Qt界面卡死的根源与解法
PyQt + YOLOv5最容易犯的错误就是直接在UI线程里跑模型推理。YOLOv5前向推理虽然只要几十毫秒,但视频解码、图像预处理、NMS这些操作累加起来,加上Python GIL的限制,会让界面明显掉帧甚至完全卡死。
标准解法是采用QThread + 队列/信号槽机制:
- 视频采集线程:负责从摄像头或视频文件读取帧,放入队列。
- 推理线程:从队列取出帧,执行预处理、模型推理、后处理,通过信号将带检测结果的帧发回主线程。
- 主线程(UI线程):只负责接收结果帧并更新画面,同时响应用户操作。
我在代码里用了一个带最大长度的queue.Queue(比如maxsize=5),如果队列满了就先丢最旧的帧,保证推理线程总是处理最新画面。这样界面始终流畅,也不会出现延迟越积越严重的问题。另外要提醒:PyQt中信号槽的跨线程连接要指定Qt.QueuedConnection,否则结果帧的传递可能出问题。
4.3 模型加载与推理封装:别把YOLOv5的detect.py直接塞进界面
YOLOv5官方detect.py写得很好,但它是一个完整的命令行工具,塞进PyQt里会很别扭。我建议把推理过程封装成一个独立的Detector类,类初始化时加载模型,类方法中实现frame级别的检测:
class Detector: def __init__(self, weights_path, device='cuda:0', conf_thres=0.25, iou_thres=0.45): self.model = torch.hub.load('ultralytics/yolov5', 'custom', path=weights_path, force_reload=False) self.model.conf = conf_thres self.model.iou = iou_thres self.device = device def detect(self, frame): results = self.model(frame, size=640) boxes = results.xyxy[0].cpu().numpy() # 返回boxes、classes、scores列表 return boxes使用torch.hub.load加载模型的优点是你不用手动处理权重文件和yaml的匹配,类内部已经搞定。后续如果切换到ONNX或TensorRT模型,只需修改Detector类内部的推理实现,界面部分完全不动。
4.4 行为判定逻辑:把检测框变成报警事件
模型输出的是“有哪些目标”,而DMS产品需要的是“当前是什么行为”,中间这道逻辑需要自己写。我实现的判定流程是:
- 当前帧检测结果中,phone/cigarette/cup/food等物体框检测到至少一个。
- 对每个物体框,计算与嘴部区域框(face框的一个子区域,取下半部分)的IoU或中心点距离。
- 如果物体框与嘴部区域重叠面积超过一定阈值(比如物体框面积的20%),标记为“疑似行为”。
- 连续N帧(根据经验设为5~10帧,即约0.2~0.4秒)都判为“疑似”,才触发报警。这个延迟设置很关键,能有效过滤掉拿水杯但没喝、拿手机但没打电话的瞬间。
这套后处理逻辑写起来很简单,但调试时你会发现很多边界case。比如司机把手机放在耳边打电话,但是头转向窗外,face框和phone框还是能正常检出并判定;但司机用手捂着嘴打电话,phone可能被手完全遮挡,检测不到phone框,导致漏报。这时就需要结合hand框——如果hand框在face框附近,且持续了较长时间,也可以触发“疑似电话”报警。灵活运用多类别检测结果,是DMS系统鲁棒性提升的关键。
4.5 打包成exe:PyInstaller的坑点复盘
用PyInstaller把PyQt程序打包成exe,网上的教程很多,但DMS项目有几个特殊的地方要注意:
- YOLOv5模型是.pt文件,在打包时容易遗漏。建议在spec文件里显式加入模型路径,或者把模型放在一个单独的models目录中,程序运行时通过相对路径加载。
- PyQt本身很大,打包出来动不动就几百MB。可以用UPX压缩,或者用Nuitka替代PyInstaller,体积更小、启动更快,但配置复杂度高一些。
- 摄像头驱动和相关DLL经常被忽略。一个典型的坑是:打包后的exe在自己的电脑上能正常打开摄像头,换个电脑就打不开,因为缺少MSVCP140.dll或类似运行库。建议打包时把VC++运行库一起带上,或者在目标机器上先安装对应运行库。
- 如果是按“窗口程序”方式打包(没有控制台),注意日志信息会丢失,排查问题会很难受。我的做法是加一个简单的日志文件,把关键阶段信息写入日志。
我用PyInstaller打包一个包含YOLOv5s模型和PyQt的项目,最终exe体积在400MB左右(因为torch和CUDA的Python库体积太大)。如果系统对安装包体积敏感,建议把模型推理改成ONNX Runtime版本,依赖库会小非常多。
5. 部署与性能优化:从开发机到边缘设备的距离
5.1 模型导出:ONNX/TensorRT的选择
DMS项目最终若是在车载设备上运行,直接用.pt权重跑肯定是效率最低的一种方式。我的建议是:
- 如果设备是Jetson系列,首选TensorRT。用YOLOv5官方export.py导出为engine格式,推理速度能比PyTorch原生快3~5倍。导出命令:
python export.py --weights yolov5s.pt --include engine --device 0 --half- 如果是瑞芯微RK3588/RV1106这类芯片,需要先导出ONNX,再通过RKNN-Toolkit转成rknn模型。注意,YOLOv5新版本可能用了某些运算符,在RKNN转换时不一定支持,建议用v6.0或v7.0的官方版本,转换工具链相对成熟。
- 如果目标机器只有CPU,建议导出ONNX后用ONNX Runtime推理,并开启INT8量化(需要校准数据集),速度提升非常明显。
5.2 夜间红外场景的特殊处理
DMS必须支持夜间开车场景,而红外摄像头和可见光摄像头拍摄的画面差异很大。红外图像通常是黑白/灰度图,如果训练集里混入了大量彩色图像,模型在红外画面上容易出现误检或漏检。我踩过的坑是:模型在白天测试效果非常好,一到晚上各种乱报。
解决办法是:在训练集里按一定比例(我用了约20%)加入红外灰度图像,同时对红外图像做直方图均衡化作为增强,让模型同时适应两种域。如果条件允许,用同一批行为场景分别录制白天和夜间版本,配对扩充数据,效果最理想。
5.3 性能瓶颈分析与优化方向
DMS系统的性能瓶颈通常是“视频解码+预处理”和“模型推理”两个环节,而非后处理。视频解码在Jetson上可以用GStreamer硬解码,推理用TensorRT,预处理如果用PyTorch的letterbox实现,在CPU上会占不少时间,可以考虑改用opencv或numpy实现。
一个典型的优化是把图像预处理从Python循环改成批量操作,避免在for循环里逐张做resize和归一化。另外,如果DMS同时接多个摄像头(比如主驾和副驾各一个),建议使用线程池并发推理,而不是线性逐路处理,实测在Jetson Orin上两路并发推理的帧率比串行高出40%以上。
6. 常见问题与排查技巧实录
以一个速查表的形式,把我在这个项目里遇到的典型问题和解决方案整理出来,方便你照着排查:
| 现象 | 可能原因 | 排查思路与解决 |
|---|---|---|
| 训练loss不降 | 学习率过大、数据标注错误 | 先把学习率降到1e-4,检查几个标注框是否错位 |
| 训练正常但val mAP很低 | 过拟合、类别不平衡 | 增加数据增强,重点补充AP低的类别样本 |
| 白天准、夜间不稳 | 红外/可见光域差异 | 训练集加入红外灰度图,做直方图均衡 |
| 界面卡顿、视频延迟大 | 推理放UI线程,或队列积压 | 改用QThread,队列最大长度限制,丢弃旧帧 |
| 小目标检测不到 | 输入分辨率低,anchor不合适 | imgsz调到960,检查anchor分布并调整 |
| 打包后exe打不开摄像头 | 缺少运行库或摄像头DLL | 安装VC++运行库,把推理逻辑做成日志可查 |
| 报警太多误报 | 判定阈值太低、状态机缺失 | 提高重叠面积阈值,增加连续帧确认逻辑 |
| 手机被手挡住,漏报警 | 模型检测不到遮挡目标 | 结合hand检测框辅助判断,不单纯依赖phone框 |
| TensorRT推理结果不对 | 预处理不一致、动态shape问题 | 保证letterbox方式一致,尝试固定batch size导出 |
| 多个司机体型差异大 | 检测框通用性不足 | 数据集中增加不同体型、不同座椅位置的样本 |
在实际操作中,我还有一个关于“调试状态机”的小技巧:不要每次修改行为判定逻辑都去跑真实视频,那样效率太低了。可以写一个模拟器,把视频逐帧喂给模型,模型输出检测框后,通过配置化的行为规则进行判定,同时把每一帧的判定中间变量(物体框坐标、嘴部区域、重叠面积)全部打印到日志。这样在调试报警阈值时,你能清楚地看到在哪一帧、因为什么条件被触发了报警,逻辑排查效率提升非常多。
7. 最后再分享一点工程心得
项目做到后期,你会发现最难的不是把YOLOv5跑起来,也不是把界面画出来,而是“如何让系统在杂乱的真实场景中稳定工作”。DMS会被装在各种各样奇怪的位置:后视镜后面、A柱旁边、中控台上方,每辆车的光线条件和遮挡物都不同。如果只用一个固定模型和一套固定阈值,很难做到万无一失。
我个人比较推荐的路线是:先快速跑通一个端到端demo验证客户诉求,然后再花大量时间在数据和场景适配层面,包括针对性采集、标注规范和阈值调优。模型结构反而是最不需要频繁折腾的部分,YOLOv5s足够应付大多数DMS场景。
对了,还有件事值得提一下——如果你打算把这个项目作为毕设,建议在答辩时重点讲清楚“数据标注规范”和“行为判定状态机”的设计,这两块是最能体现工程思维的,比单纯讲“我跑通了YOLOv5”要有说服力得多。
本文还有配套的精品资源,点击获取