2026/10/17 11:56:10

基于YOLO的驾驶员危险行为识别:从14K数据集到端侧部署

基于YOLO的驾驶员危险行为识别:从14K数据集到端侧部署 做危险驾驶行为识别这件事我一开始也没想到实际需求会这么具体。上周一个做车队管理的熟人跟我说他一个月能收到二十几条关于司机行车途中低头玩手机的投诉但车队监控视频有几万个小时靠人工翻根本翻不过来。他问我的问题很简单这东西能不能自动判断司机是不是在接电话、是不是犯困做完整条链路以后我得说类似需求的核心就是把一份14000张高清标注的驾驶员行为数据集用YOLO训练成一套能落地的危险驾驶行为识别模型。这篇文章我会把从数据集清洗、格式转换、YOLO训练选参到调优迭代和端侧部署的完整过程都拆开讲适合正在做驾驶舱视觉分析、车队主动安全系统或者准备入行边缘端目标检测的开发者参考。我拿到这份14K数据的第一个感受不是“终于有数据了”而是“数据工程才是这个项目真正的大头”。下面按我实际推进的顺序来写每一段都会给出能直接套用的做法。1. 需求拆解驾驶员行为识别为什么必须走视觉方案1.1 驾驶舱里的真实场景比想象中更复杂驾驶员行为识别说白了就是从车内摄像头画面里实时判断司机当前有没有危险操作。日常覆盖的违规行为大概有这么几类手持电话接打、低头看手机、吸烟、喝水、瞌睡闭眼、视线长时间偏离前方、未系安全带偶尔还会遇到遮挡摄像头、和副驾过度交互这类边界情况。真正去现场看过就会发现实际需求不是“识别一个动作”而是要回答“司机现在是否处于不安全状态”。比如手持电话和喝水从画面上看都是手部靠近面部区域的短暂遮挡区别可能只在于手里拿的是手机还是水杯闭眼和正常眨眼之间的界限又要靠连续帧来确认。所以这块识别任务天然对图像的清晰度、拍摄角度、光照一致性要求很高剪裁得干干净净的单张检测图反而不是重点画质和视角覆盖才是。1.2 为什么不用方向盘传感器或座椅压力垫很多人会问直接在方向盘和座椅上装传感器不是更简单吗确实接触式方案对“手有没有握方向盘”检测很灵敏但有两个硬伤一是无法区分司机是把手搭在方向盘上玩手机还是正常扶着方向盘二是无法判断视线方向和闭眼状态。而对于车队管理来说玩手机、打瞌睡恰恰是最需要证据的两个场景没有图像后端管理人员就没法确认报警是否属实。视觉方案的优势在于摄像头给的是原始事实可以回溯、可以做成事件录像也方便后续扩展到乘客区域安全检测。这也是YOLO这类目标检测算法在这个场景里被大量选用的原因它能直接输出目标类别和位置再加上驾驶舱内目标类别相对固定不需要像通用场景那样考虑几百类互相干扰的问题训练成本可控。2. 数据集准备14000张标注图的格式转换与质量排查2.1 各家标注格式不一样第一步先把标签统一成YOLO结构我拿到的这份驾驶员行为数据集原始标注并不是YOLO格式而是偏VOC风格的XML标注另外一部分是JSON格式的坐标文件。YOLO训练时要求的标注是每个图片对应一个同名txt文件每行一个目标格式为类别id 中心点x 中心点y 目标宽 目标高注意这里的中心点坐标和目标宽高都是归一化到0到1之间的相对值不是像素坐标。比如一张1920x1080的图像里某个目标的像素中心在(960, 540)宽300高200对应YOLO标签应该写成0 0.5 0.5 0.15625 0.18518计算方式很简单cx (xmin xmax) / 2 / img_w cy (ymin ymax) / 2 / img_h bw (xmax - xmin) / img_w bh (ymax - ymin) / img_h建议写一个批量转换脚本把VOC的xml或各平台的json统一读进来对照类别映射表输出txt。我当时的类别映射大致是这样的类别ID行为名称备注0正常驾驶用于负样本1手持电话含接听和拨号2吸烟含点烟动作3喝水含水杯、饮料瓶4闭眼疲劳单帧闭眼状态的辅助标签5视线偏离看侧方、看下方6未系安全带斜跨胸前区域检测转换完以后还要按 images/train、images/val、images/test 加 labels/train、labels/val、labels/test 的结构摆放文件。我习惯的做法是先把所有图片随机打乱按8比1比1切分再强调一遍一定要单独留出测试集方便最后做一次真实盲测。训练和验证可以用同一个随机种子切避免两张相似图片被分到两个集合里造成评估虚高。2.2 14000张里藏着的三个质量坑第一眼看数量14000张高清图挺充足但真正清洗以后发现能直接喂进模型的数据远比表面少。坑之一是小目标占比失衡。驾驶舱画面里手机这类目标在整张图像里往往只占千分之几的面积和未系安全带这种大目标相比标注框大小差了几十倍。如果模型输入分辨率设640很多手机在缩略图里只有十几个像素网络很难学到有效特征。坑之二是亮度分布明显偏“白天过曝”。一大部分样本是在强日照环境下拍的仪表台反光严重前挡玻璃上还有外部景物投影真正需要的夜间、隧道、地下车库场景样本占比偏低。实测下来单靠白天样本训练的模型在夜间画面上的mAP会掉8到10个点。坑之三是有少量标注框错位或目标被遮挡。比如司机低头看手机时手和手机叠在一起部分标注只框了手没框手机或者把水杯标成了手机。这类噪声标签会让模型在边界样本上摇摆不定。我的处理办法是抽出置信度阈值附近的样本人工过一遍宁可删掉部分争议样本也不要让模型去学错误的“标准答案”。3. 模型与超参选型种子、学习率、图片尺寸的第一轮博弈3.1 环境与基础版本组合这个项目我用的是一套比较稳定的技术栈PyTorch 2.x 搭配 YOLO训练框架。先说环境准备这里有一个很容易翻车的地方——CUDA和PyTorch版本必须匹配。装好以后先在命令行跑一下import torch print(torch.__version__) print(torch.cuda.is_available()) print(torch.cuda.get_device_name(0))如果cuda.is_available()返回False基本可以确定是驱动和torch版本对不上。我踩过坑以后的经验是无论机器装了什么先查驱动支持的最高CUDA版本再回头去装对应编译版本的torch。环境配好以后的工作路径基本就是三件事组织数据目录、写data.yaml、运行训练脚本。data.yaml是这个流程里最容易被忽略的文件它定义了数据集根路径、训练验证图片路径、类别数和类别名列表。写法大致是这样path: /home/user/driver-behavior-dataset train: images/train val: images/val test: images/test nc: 7 names: 0: normal_driving 1: phone_call 2: smoking 3: drinking 4: eye_closed 5: gaze_away 6: no_seatbelt很多新人习惯把train和val写成绝对路径这个没问题但要注意path字段一旦生效子路径尽量只用相对路径。另外类别顺序和训练时使用的顺序要保持一致我见过有人因为改了类别顺序却没有同步重排标注txt导致模型训练时标签错位最后mAP变成一团乱麻。3.2 模型骨架选n、s还是mYOLO模型按参数量从小到大分成了n、s、m、l、x几档。在驾驶员行为识别这个场景里我的选型结论是可以直接给出来的优先从n或s开始试。原因是驾驶舱内的目标类别不算多手机、水杯这类目标虽然小但外观变化有限不需要超大规模模型去记忆过于复杂的模式。n模型单张推理速度快方便后续部署到性能受限的边缘设备如果发现n模型精度确实不够再升级到s往往能换来3到5个点的mAP提升部署压力还在可接受范围内。但有一个细节值得注意模型预训练权重来自通用目标检测数据集那里的“手机”“人”和驾驶舱里的“手机”“人”在视角上有明显差异。驾驶舱摄像头是从斜上方俯拍手机经常被方向盘遮挡一部分手部轮廓也容易干扰边界框回归。所以就算用到了预训练权重也一定要在驾驶舱数据上做充分的微调训练不能只训几十个epoch就急着部署。3.3 训练参数的第一轮初始值以YOLO系列框架为例我第一轮训练用的超参数大体如下参数初始值说明imgsz640先跑通基线后续考虑提到960batch16根据显卡显存调整epochs100配合早停使用lr00.01使用官方默认即可optimizerSGD稳定适合中小数据集mosaic1.0开启马赛克增强seed42固定随机种子保证可复现这里有一个容易被忽视的点固定seed不是锦上添花而是必须做。尤其当数据集存在标注噪声时同样的代码不固定seed换一台机器跑出来的精度差距可能达到两三个点你很难判断是模型问题还是随机性造成的波动。在项目初期就用固定seed可以省掉后期一大半的自我怀疑。4. 训练与修订loss、mAP曲线是怎么把我劝回数据集的4.1 看loss曲线不能只看“下降”第一轮训练启动以后我在第5个epoch左右看到box_loss和cls_loss都在下降看起来一切正常。大约第20个epoch训练集的损失还在平滑下降验证集的指标却开始上下抖动val mAP50-95卡在0.72附近不再爬升。这个现象非常典型模型已经开始记住训练集里某些特定驾驶舱场景的细节而不是学习“手机”“吸烟”这些行为的通用特征。这时候正确的动作不是继续加训epoch也不是盲目调学习率而是回数据集找原因。我把验证集里表现最差的图片挑出来看了一遍发现主要问题集中在夜间场景和倾斜角度偏大的样本上。也就是说模型在白天、标准视角的样本上已经学得不错但到光照变化、视角偏移的样本上泛化不足。这时候单纯加迭代次数已经没有意义我选择回到数据层面做两件事一是晚间的欠曝光样本做亮度均衡增强后重新加入训练集二是把白天样本做一部分随机HSV扰动减少对特定色温的依赖。4.2 类别不均衡要从损失权重和采样两端同时下手我在训练过程中专门统计过各类别标签数量占比差距确实大。正常驾驶这类负样本是最多的而视线偏离、喝水这类样本数量偏少。类别不均衡带来最直接的后果是少数类在训练后期精度低甚至被大类别“吃掉”。处理手段我试过两条路。一条是在数据层面做欠采样和过采样的混合少样本类别可以通过水平翻转、小幅旋转、亮度抖动生成变体扩到占总数10%到15%左右。另一条是调整损失函数里各类别权重在官方默认配置里找到cls loss对应的权重项适当调大少数类的权重。实际对比下来两个手段同时用比单独用一个效果好少数类mAP能提高5个百分点左右。但这种平衡不能做得太猛比如把吸烟类扩到和正常驾驶一样多模型反而会对烟雾干扰产生过敏反应误报变多。4.3 早停与继续训练的平衡点训练到大约60个epoch时验证集mAP50-95达到0.78后就基本持平了。我这边开了早停忍耐度设定为20个epoch也就是连续20轮没有改善就自动停止。用早停的时机很关键如果停止位置正好是平台期后面还有可能通过更换优化器或降低学习率续命如果停止位置是过拟合上升期再继续训练只会更糟糕。续命训练我也试过一次把SGD换成AdamW学习率降到0.001在最后一轮权重基础上再跑了30个epoch。这个操作带来的提升大约有2个点mAP但代价是训练时间变长而且模型对学习率变化更敏感。我的建议是如果时间预算不紧张可以把“AdamW续命训练”作为固定备选方案如果赶着上线那就接受基线结果优先做数据补采和推理侧优化。5. 典型误检与调优小目标、逆光、遮挡这三座山5.1 混淆矩阵里最刺眼的永远是手机被认成水杯训练结束以后我用测试集跑了完整评估画出了混淆矩阵。整体精度很不错但问题也很集中手机类和水杯类之间存在明显的混淆。从图像特征上看这两个目标在低分辨率下轮廓都是一个小矩形颜色又可能接近最要命的是手拿着它们的姿态几乎一样。光靠目标检测的单帧特征很难做到100%区分。解决这个误检我做了两个尝试。第一个是把输入分辨率从640提升到960手机类的小目标在feature map上会多出两倍以上的像素mAP50-95能提升2到3个点。代价是推理耗时增加但部署端如果用GPU倒还撑得住。第二个是增加一个基于人体关键点的辅助规则先检测司机的手部关键点如果手部区域出现一个尺寸较小的物体类目标再结合手机靠近头部区域的特征做二次确认。这一招能把手机和水杯的误检率降低一半。5.2 逆光和遮挡靠数据增强只能解决一部分驾驶舱摄像头最怕的物理环境是逆光。下午四点到六点太阳从侧后方打进来人脸部分过曝仪表台产生大量反射光斑。我针对这种场景做了专门的亮度均衡预处理相当于把局部过亮区域压暗、把暗部提亮之后再喂给训练阶段做增强。但说实话这类预处理只能缓解不能根治。如果项目的摄像头安装位置允许调整尽量压低摄像头角度让光源直射的概率小一点这比任何算法优化都有效。遮挡问题的核心是标注一致性。方向盘会挡住手部局部区域标注人员有时把手部整体框进去了有时只框了露出来的部分导致模型学到的目标形状不稳定。我的做法是定一条统一规则目标被遮挡超过40%就不标注除非它是画面里唯一的有效目标。标注规则写清楚后重新抽检了部分样本模型对遮挡样本的检测稳定性明显提升。5.3 连续帧比单帧更靠谱部署时要有逻辑驾驶员行为识别在真实场景里还有一个单帧检测给不了的能力就是时序判断。司机偶尔低头看一眼导航闭眼一次都未必等于危险驾驶。如果把单帧检测结果直接当报警误报率会高到让车队管理员直接把系统关掉。部署时我给系统加了三级判断逻辑单帧检测结果先送入一个长度为5到10帧的滑窗目标类别需要连续出现N帧才置为有效对于闭眼疲劳类还要额外统计单位时间内的累计时长占比对于手持电话类则要求在连续帧中边界框稳定性达到一定阈值后才触发报警。这套逻辑不复杂但能把误报率压到可接受范围也让后端人工复核的负担大大降低。6. 落到车端之前模型导出与工程化收尾经验6.1 从PyTorch权重到端侧引擎的转换链条训练完成的最终权重不能直接拿来部署要经过转换流程。我在导出阶段做了两件比较关键的事第一是把模型固定成RGB输入、固定输入尺寸避免部署端出现尺寸不一致导致的推理异常第二是用框架自带导出工具转成ONNX再用运行时优化成TensorRT引擎。导出前最好在命令行里先做一次模拟推断把输入图片resize到目标尺寸确认前后处理的预处理逻辑一致。我在这个环节栽过跟头训练时图像归一化用的是半精度模式导出ONNX后转成int8量化同一张图推理出来的坐标偏移了几个像素排查了很久才发现是归一化系数不一致。这个细节建议在导出脚本里写死并用同一张图回归测试。6.2 端上的算力预算和采样频率车端设备算力紧张是常态。我的方案是把模型运行在边缘推理设备上控制帧率不低于15FPS。如果硬件性能勉强优先做两件事一是将推理分辨率固定在训练时用过的640或960不要动态改二是每帧检测会带来浪费我按每5帧推理一次的方式降低算力占用中间帧沿用上一帧结果。显存不够的时候我会选择把batch size改成1再做一次引擎优化。这里有个经验模型转换到固定batch size为1的引擎往往比动态batch小一半显存占用而单帧延迟反而更低。做这套优化后实际部署设备的推理耗时降低了30%左右。6.3 隐私合规与误报处理的最后防线驾驶员行为识别系统天然会采集车内人脸信息所以工程化阶段一定要把隐私问题放在前置位置。我的建议是推理尽量全部在端侧本地完成原始图像不轻易回传只把结构化的事件结果上报给管理端。所有涉及面部特征的数据处理要在明确告知并取得同意的前提下进行。这不是流程形式而是整个项目能不能长期稳定运行的基础。事件触发后的复核机制也要想好。我的做法是模型判断出危险行为后自动截取触发前10秒到触发后5秒的视频片段并附上每帧的检测框和置信度交给后台人工确认。这样做既减少误报对司机的影响也给管理方留下可审计的证据。实际运行一段时间以后发现司机主动投诉“被误判”的次数下降了因为后台可以拿出画面证据。最后分享一个具体建议驾驶员行为识别这类项目永远把20%的精力花在模型网络结构调整上把80%的精力花在数据质量、场景覆盖和报警逻辑设计上。14000张高清标注数据只是起点真正决定模型上限的是你对驾驶舱光照变化、小目标分布和异常动作边界这三个问题的理解深度。把这几个问题想清楚了YOLO训练本身反而是整个项目里最顺的一段路。