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

资讯详情

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

YOLO驾驶员行为检测实战:从数据集构建到训练调参

YOLO驾驶员行为检测实战:从数据集构建到训练调参 前阵子刚把一个驾驶舱内的驾驶员行为检测项目跑完用到的就是一套22600张的YOLO智能驾驶数据集。从最开始的采集、清洗、标注到后来用YOLO训练和落地部署整个链路走下来踩了不少坑也整理出了一些常规文档里不太会写的实操经验。这篇文章就沉下来把这些东西讲清楚这个数据集怎么设计、标注规范怎么定、训练参数怎么调、哪些环节最容易翻车。这套经验不仅适用于DMS驾驶员监控凡是手里拿到一批YOLO标注数据、想自己训练检测模型的读者基本都能用得上。先说一点背景。所谓驾驶员行为检测简单说就是在车载摄像头画面里实时识别驾驶员是不是在打电话、看手机、喝水、抽烟、打哈欠、闭眼或者视线偏离。它并不是一个单纯的目标分类问题除了要判断有没有这个行为还得知道行为发生在图像里的哪个区域。所以工程上前期把数据整理成YOLO格式的检测数据集是最稳妥的路径后面训练、评估、导出部署都有现成链路。这套22600张的数据集类别覆盖了正常驾驶和七类危险行为配上YOLO的标注格式基本可以支撑一套完整的DMS感知模型从零训练到装车验证。1. 项目整体思路为什么攒22600张检测数据集1.1 驾驶员行为检测到底检测什么这个项目的核心目标不是做一个通用目标检测器而是做一个面向座舱场景的专用检测器。所以第一件要做的事情就是把驾驶员行为拆成模型能学习、能标注、能评估的具体类别。我最终定下来的是下面这8类normal正常驾驶、目视前方、双手或单手在方向盘上calling手持电话放在耳边通话phoning低头/斜视看手机屏幕drinking拿起水杯或水瓶喝水smoking吸烟、手部持烟靠近嘴部yawning打哈欠通常伴随大张嘴eyes_closed闭眼可能是疲劳驾驶的强信号looking_away视线明显偏离前方转头看侧窗、后视镜或副驾这里要特别提醒类别定义不是拍脑袋定8个就完事而是要在采集数据之前写成一页规范文档。比如calling和phoning看起来都是玩手机但一个是接打电话、一个是在手机上滑动/浏览动作姿势差异很大。如果标注人员和算法人员对这两个类的判断标准不一致后面训练出来的模型会非常飘。我在项目里就把这两类做了很明确的分割手机贴近耳朵算calling手机在胸前或者大腿上方、视线向下就算phoning。另外这个检测目标的边界也要设计清楚。DMS摄像头一般装在方向盘上方或A柱附近视角覆盖驾驶员上半身、方向盘区域和部分车窗。模型输出的是行为主体对应的边界框也就是人形框、手部框、手机框、水杯框、烟支框这类目标框。这类检测框的存在本质上是在给后面的行为判定逻辑提供空间信息。有了框才好判断手和手机是否重叠手是否靠近嘴眼睛是否处于闭合状态。1.2 为什么选YOLO而不是分类网络或两阶段检测器做驾驶员行为检测其实存在三种技术路线纯分类网络、两阶段检测器、YOLO这类单阶段检测器。先看纯分类比如用ResNet训练一个图像分类模型输入一整张截图输出正常/打电话/喝水这样的标签。这种方案在自动驾驶行业早期见过不少但问题在于它完全丢失了位置信息。驾驶员在画面中可能只占比较小的区域手部动作更小分类模型很难告诉系统这个动作发生在哪里。而且座舱内不同的驾驶员坐姿、体格差异会让分类模型把注意力放在背景或身体整体上而不是真正的手部和设备交互细节上。再看两阶段检测器Faster R-CNN这类结构精度是够的但推理速度在嵌入式平台上不太理想。DMS系统通常跑在车规级芯片上资源非常有限实时性要求起码是视觉部分推理能在30 FPS左右。两阶段检测器在这个约束下有些吃力。所以最后选了YOLO。实际上YOLO单阶段检测的速度天然占优尤其YOLOv8之后检测头、损失函数、训练策略都很成熟有COCO预训练权重可以直接微调。更关键的是它的生态太完整了训练要用到的数据增强、评估指标、模型导出整个工具链都是现成的对于数据工程型团队来说省掉很多自研成本。现在社区里还有YOLOv9、YOLO11以及一些结合Transformer或Mamba的变体在研究讨论作为工程落地YOLOv8目前仍然是稳的选择。1.3 22600张数据集的规模与划分逻辑很多刚接触检测项目的人会有一个疑问22600张图到底够不够训练答案要看场景复杂度和类别数。对于驾驶员行为检测这种固定机位、场景相对受限的任务来说2万张级别是完全够跑出可用模型的前提是采集的光照覆盖度足够。如果全是白天RGB图像、全是同一个驾驶员那一万张和五万张差别不会太大但如果白天、夜间、逆光、隧道、戴墨镜戴帽子等情况都要覆盖样本量就需要堆上去。我当时的划分方案是训练、验证、测试按大约8:1:1切。精确计算的话train 22600 约 80% 18080张val 22600 约 10% 2260张test 22600 约 10% 2260张实际执行时我留了大概300张作为极端困难样本的测试集所以验证集和测试集加了个余量总数仍然对应22600张总量。这样划分的好处是验证集足够稳定地反映训练过程中的模型状态测试集则完全从调参循环里隔离出去用来做最终指标评估。这里有一个经验按类别比例进行分层切分不要直接对整个文件夹随机shuffle。如果random shuffle某些类别在整个数据池里本身就少验证集里可能出现一类只有十几张的情况。我的做法是先把所有图片按最小类别统计保证train/val/test每个类别都保持接近8:1:1的比例再在每一类内部做随机抽取。这样后续看每个类别的AP曲线时才不会被验证集样本过少带偏。2. 采集与标注决定模型上限的前置工作2.1 舱内采集场景怎么搭数据质量的第一步是采集设备的安装位置和成像条件。DMS摄像头一般装在两个位置内后视镜后方和驾驶员侧A柱俯视角度大概10到15度视野里应该看到方向盘、驾驶员的上半身和面部侧前方。我踩过的一个坑是摄像头装太高俯角太大结果手部动作被方向盘遮挡严重喝水动作基本看不到。后来把镜头角度微调之后标注的困难程度一下降低了很多。光照条件是座舱场景最大的变量。白天从车窗进来的自然光会在驾驶员面部形成很强的明暗对比夜晚车内几乎没有可见光。所以采集方案必须同时覆盖可见光和近红外。实际做法是使用带红外截止片切换功能的摄像头白天输出RGB图夜间开启红外补光后输出近红外图。注意近红外图本质上是单通道灰度信息但很多相机封装出来仍然是三通道只是三个通道数值接近。这一类图在训练阶段如果直接和白天彩色图混在一起会让网络产生域漂移。我自己的方案是训练前把RGB图也全部转成灰度三通道让模型不再依赖颜色信息。除了光照还要考虑装饰物和人体姿态差异。采集样本时尽量找不同体型的驾驶员、让驾驶员穿不同颜色的衣服、戴帽子、戴墨镜、戴口罩甚至复杂阴影下都要有覆盖。建议把采集时间段分布在早中晚和夜间并记录每张图的元信息比如光照环境、驾驶员ID、行为类型。有了这些元信息后面做域分析时才不用靠肉眼猜数据构成。2.2 行为类别定义与边界规范标注规范写得好不好直接决定标注一致性和模型精度上限。前面已经列了8个类别这里重点讲边界情况的处理。首先是同时发生多个行为怎么办。例如驾驶员一边打电话一边喝咖啡。我的标注规则是只要同一帧里多个行为在图像上表现明显就分别标注对应的目标框。也就是说一个类别实例是一个检测框人和手可以有多个框同时出现并不强制把整个帧打成一个标签。这样做的好处是模型学到的是独立的行为框而不是学习整张图的综合状态后处理阶段再根据框和框之间的位置关系做行为判定。然后是动作刚开始和即将结束怎么界定。这属于时序切片问题。例如拿起水杯往嘴边送的过程中在哪个时间点算drinking我在规范里写的是水杯与嘴部的横向距离小于一个拳头的距离或者杯口有倾斜进入嘴部区域的动作才算drinking。如果没有这层约定标注人员会在动作临界帧上产生巨大分歧。项目里的做法是先在每个视频序列中采样关键帧由一个人统一标注然后另一个标注人员做交叉验证。关于目标框的大小限制也有规则。驾驶员实际在图像里的尺度不会太大但也不至于小到不可辨。我们要求标注框的最小边不小于20像素遮挡超过80%的目标不标模棱两可时宁可漏标也不要错标。原因很简单漏标只会让模型在这类样本上召回率低一点错标却会让模型学到错误特征后面在真实场景中出现把正常驾驶识别成打电话这种不可接受的误报。2.3 标注流程与YOLO格式细节标注工具上我推荐支持YOLO格式的开源工具。之前的项目里用过LabelImg和X-AnyLabeling后者对大数据量更友好一些支持自动保存、快捷键、预标注模型导入。用YOLO格式存储时每个txt文件对应一张图片文件中的每一行是class_id x_center y_center width height其中坐标全部是归一化到0-1之间的值分别对应边界框中心点的xy和框的宽高。比如一行是2 0.3213 0.5487 0.1234 0.2678表示第2类phoning框中心在图像坐标的(0.3213, 0.5487)宽高分别是0.1234和0.2678。归一化的好处是不同分辨率图片可以共用同一套标签文件。如果摄像头分辨率从1080P换成720P标注文件完全不用动。标注流程我分三步走第一步用弱模型预标注生成初版伪标签第二步由人工在工具里修正边界框和类别第三步做交叉复检和抽检。预标注不一定看模型本身的精度而是帮标注员节省画框的时间人工只需要拖一拖边界效率能提升很多。抽检比例我设在5%左右如果发现某类标签的修正率超过一定阈值就退回重标该批次。这里还要提醒一个很容易翻车的问题标注过程中如果调整了图片尺寸标签坐标一定不能直接沿用要重新换算归一化坐标。我见过有同事把一批1080P的标签直接套到720P图片上结果所有框的位置都偏了约三分之一整个批次的训练直接跑偏。3. 用YOLO训练驾驶员行为检测从配置到评估全流程3.1 数据集目录组织与YAML配置拿到标注好的数据后第一步是按YOLO的标准目录结构整理数据。虽然YOLO也支持通过脚本动态读取但我还是建议直接落成目录文件方便后面交给任何一台训练机器复现。标准结构如下driver_behavior/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── driver_behavior.yamlimages和labels下面的文件名是一一对应的图片叫frame_000123.jpg标签就叫frame_000123.txt。一张图片如果没有任何行为目标对应txt文件是空的但文件本身要存在否则训练时YOLO日志里会报label not found一类的问题。这个问题虽然不影响最终结果却会拖慢训练速度、增加排查困扰。YAML配置文件是关键入口我写的配置如下path: /data/driver_behavior train: images/train val: images/val test: images/test names: 0: normal 1: calling 2: phoning 3: drinking 4: smoking 5: yawning 6: eyes_closed 7: looking_away需要注意names的索引必须和标注txt里的class_id完全对齐。这些看起来是小问题但实际项目里我就犯过把正常驾驶放在索引7而不是索引0的错误结果训练出来模型对normal这个类完全没反应日志里那个类别的AP直接是0。遇到这种情况不要怀疑网络结构先检查标签和names对不对齐。3.2 训练参数、预训练权重与损失函数怎么选训练使用的工具是Ultralytics YOLO框架已经封装好了数据读取、增强、损失计算和评估逻辑核心工作在于把参数选择解释清楚。以一个中等规模的DMS模型为例我的常用参数如下表参数推荐值说明modelyolov8s.pt预训练权重规模适中imgsz640输入分辨率兼顾精度与速度batch16单卡或双卡时常用值epochs120结合早停使用一般80-120内收敛lr00.01初始学习率YOLO默认值附近patience20连续20轮验证集不提升则停止seed2024固定随机种子方便复现预训练权重是第一个要强调的点。直接下载YOLOv8在COCO上预训练好的权重文件而不是从随机初始化开始训练非常关键。COCO虽然和驾驶员行为场景差异很大但它已经让网络学到了通用的边缘、纹理和形状特征微调阶段能快速收敛。我实测下来同样100个epoch用预训练权重从零头跑到mAP50约94%左右从零训练大概率只能到88%左右而且更加容易过拟合。热词里也常有人问yolo预训练模型下载实际上就是官方发布仓库里的yolov8n.pt、yolov8s.pt这些文件注意权重版本和要导出的模型架构保持一致就行。损失函数这一块YOLOv8的检测头同时包含三个分支边界框回归的CIoU损失、分类的BCE损失和DFL分布损失。DFL它把框的坐标预测拆成离散分布对边缘精度和小目标更友好。这些损失默认权重已经调得很均衡一般不需要改动。如果非要在DMS场景里调我会把分类损失里normal类的权重降一点因为正常驾驶类别前景框数量大、背景简单过强的梯度反而会让模型在危险行为类别上欠拟合。训练命令非常简单但要注意命令里的path路径要能在运行机器上访问到完整数据。命令写出来是这个样子yolo detect train \ datadriver_behavior.yaml \ modelyolov8s.pt \ epochs120 \ imgsz640 \ batch16 \ lr00.01 \ patience20 \ device0 \ seed20243.3 数据增强在座舱场景的取舍YOLO自带的增强策略默认是很激进的包括Mosaic、MixUp、HSV扰动、随机翻转、缩放等。Mosaic四图拼接和MixUp叠加都能显著提升模型对复杂环境的鲁棒性但在驾驶员行为检测场景里不能无脑全开。我踩过的坑是mosaic导致手部小目标被切得支离破碎反而拉低了喝水吸烟这种强依赖手部细节的类别。原因是Mosaic把四张图缩小拼到一张图里手上的手机、水杯本来就小缩小后彻底看不清。解决办法是把mosaic超参从默认1.0降到0.3左右让更多训练样本保持原始尺度。MixUp我是直接关闭的因为驾驶员行为检测里行为边界本来就比较精细叠图会让边界更模糊。HSV增强对RGB图有效但我前面说了会把所有图像转成灰度图训练所以颜色变化增强基本用不上。真正有用的是亮度对比度扰动适合模拟车内不同时间的反光。水平翻转我保留了但它存在一个驾驶位方向的潜在混淆问题如果采集车是左舵驾驶翻转后变成右舵视角打电话的手从右手变为左手这在实际部署时通常不是大问题因为模型学习的更多是手贴近脸的语义关系而不是绝对手性。但如果你的落地市场要求严格区分左右手就要考虑关闭水平翻转。缩放增强我建议重点开尤其scale在0.5到1.0的区间。这个操作能模拟不同身高的驾驶员在画面里呈现的尺度差异让模型对目标尺寸更鲁棒。translate和degrees的增强可以给一点但不宜大因为DMS摄像头是固定机位画面里目标不会大幅平移或旋转过度增强反而会让模型学习到不真实的位置分布。3.4 评估指标与模型导出训练结束后正式评估要在独立test集上执行。验证命令如下它会输出每一类以及整体的mAP50、mAP50-95、precision和recallyolo detect val \ modelruns/detect/train/weights/best.pt \ datadriver_behavior.yaml \ splittestDMS场景里我最看重的是两类指标一个是calling、phoning、drinking这些危险行为的recall也就是真正危险行为有没有被漏掉另一个是normal类的precision也就是正常驾驶有没有被误报成危险行为。误报这件事在产品里非常影响体验如果正常开个车突然报警说你在打电话用户很快就对该系统失去信任。所以我在训练时会把normal类的置信度阈值单独调高一点危险行为类的阈值调低一点做一个偏置。模型导出到部署平台一般走ONNX或TensorRT。导出命令是yolo export modelbest.pt formatonnx dynamicTrue opset12如果目标是Jetson这类嵌入式设备我会进一步转成TensorRT的engine文件用FP16精度。工程实践里YOLOv8s在Jetson Orin上跑640x640输入TensorRT优化后单帧推理大概在10到20毫秒完全满足实时要求。部署后还要接一个平滑后处理因为单帧检测可能偶尔抖动用简单的滑窗投票或者卡尔曼滤波对行为标签做时序修正误报率能再降一截。4. 训练实战中的常见问题与排查技巧4.1 训练不收敛或loss异常怎么查训练过程中遇到loss不收敛或者出现NaN是最让人头大的问题。我把它整理成一个速查表很多场景下可以直接对照处理。现象可能原因排查与处理办法train loss和val loss都降不下去标签坐标归一化错误或类别索引错位随机抽几张图把标签画出来可视化检查loss出现NaN学习率过大、batch过小、BN统计量崩溃降低lr、增大batch不行就换成预训练权重重新开始train loss很低但val loss高过拟合减少训练轮数、增加数据增强、降低模型规模或加dropout某个类别AP一直为0该类别在验证集里太少或标注类别名和names不对齐统计类别框数分布检查YAML names顺序训练日志里有大量label not found图片和标签文件名不匹配检查images和labels目录下文件是否一一对应最有效的排查手段不是盯着loss曲线猜而是把标签可视化。我一般在训练前写几行脚本把训练集里任意图的标注框画出来人眼扫一遍就知道类别和位置有没有问题。这一步成本很低但对后续训练效率的影响非常大。4.2 类别不均衡和BN崩溃的处理驾驶员行为数据集天然存在类别不均衡的问题。正常驾驶样本可能占了40%以上而吸烟、闭眼这类行为可能只有5%到8%。YOLO默认训练会把每个类别同等对待小类别会被大类别淹没。我的处理方式是两步走第一步在数据层面做采样调整把样本量最大的几个类别做欠采样让每类框数控制在一个比较接近的范围内第二步在训练时开启cls_loss的加权参数对小类别给略高的损失权重。这里要注意不要只依赖损失权重来处理严重不均衡数据层面的调整仍然是最直接的。再单独说说BN崩溃。所谓yolo训练中bn崩溃表现是训练刚开始loss还正常跑到几十个iteration后loss突然变成NaN或者验证集的指标突然从90%掉到0。常见诱发原因是batch size开太小。BN层需要在batch内部统计均值和方差batch为1或2的时候统计量抖动非常剧烈尤其是在数据呈现明显光照分布差异时。如果显存不够不要硬撑大batch可以降低输入分辨率或换更轻量的模型优先保证batch不低于8最好到16。另外如果发现loss在训练前几个epoch就出现震荡加剧也可以临时把lr0调到0.005试一下先让BN统计稳定下来再恢复正常学习率。4.3 混淆矩阵总和不唯一是怎么回事很多人在YOLO的训练结果里看混淆矩阵发现横着加、竖着加、总体加起来对不上就开始怀疑是不是代码bug。这里把逻辑讲透YOLO输出的混淆矩阵纵轴一般是真实类别横轴是预测类别单元格写的是样本数量或者归一化比例。每一行的总和等于该类别在数据集中的真实样本数每一列的总和则等于模型预测为该类别的样本数。由于存在漏检和误检行和与列和天然不相等矩阵本身也不是方阵或者数值不对称。所谓总和不唯一不是bug而是混淆矩阵同时表达了分类错误和检测遗漏两件事。我在项目里看到这个矩阵会重点看两个区域一个是predicted as normal这一列如果危险行为类别在正常驾驶列里有较多数量说明模型会把危险行为当成正常这是最危险的漏报另一个是predicted as calling这一类看是否存在把喝水误判成打电话的问题。只有把矩阵结合具体类别的语义来看它才有真正的调优价值。4.4 红外夜视数据与白天数据的域差异前面提到夜间红外图和白天RGB图在像素分布上完全不同。如果你把它们直接混在一起训练模型容易出现选择性遗忘某段时间偏向白天某段时间偏向夜间。我这边有两条解决路径可以按项目阶段选。路径一是统一灰度。在数据进网络前把所有RGB图转成单通道灰度再复制成三通道喂给网络。这样白天和夜间的图像在通道数值上处于同一个域模型的输入分布一致性大幅提升。缺点是损失了颜色信息但DMS任务本身对颜色依赖不大我们判断的是有没有打电话、有没有喝水而不是手机是什么颜色所以这个方案在我项目里效果很好。路径二是做光照扰动增强。训练时对灰度图随机改变对比度和亮度模拟夜色、隧道、逆光等不同亮度环境。YOLO的HSV增强里也有value扰动但那是针对彩色图的我在改用灰度图后会写一个自定义预处理把亮度扰动范围加大一点比如0.6到1.4倍这样网络对红外补光下偏暗的画面会更鲁棒。实测下来夜间测试集上的mAP50能提升3到5个百分点。4.5 手部小目标与遮挡检测优化手部在座舱图像里往往只有30到50个像素的宽度属于典型的小目标。小目标检测在YOLO里如果只靠提升输入分辨率虽然有效但推理成本会上去。我建议按三个层次去优化。先尝试提高imgsz。把640提升到800或960手部区域的分辨率会明显增加小目标召回率能涨一截。但这对嵌入式平台的算力要求也更高需要实测推理时间。再试试开启SAHI这类切片推理方案大图切块后分别检测再合并结果可以在不动训练模型的情况下提升小目标精度但推理耗时增加明显一般只用于离线分析或者后装高算力平台。最后是模型层面的配合训练时给手部相关类别更充分的标注框不要只标手连手机、水杯一并标出来让模型学习到手和物体互动的组合特征这样即使手部本身被遮挡物体框也能给出足够线索。遮挡是驾驶员行为检测里躲不开的话题。喝水时水杯会挡住嘴部接打电话时手会挡住耳朵。我的经验是遮挡严重的样本不要直接删掉它们反而是关键困难样本。可以在标注规范里单独建立遮挡但可辨识和遮挡不可辨识的区分标准前者保留标注后者删除。这样模型才能学会在真实遮挡场景下依然给出可靠检测而不是只见过完美无遮挡的样本一到路上遇到遮挡就失灵。最后分享一条个人体会比较深的经验整个项目跑下来我最大的教训是标注一致性远大于标注数量。如果你手里的数据来源复杂、多个标注人员批次的风格差异大模型AP再高也会在真实场景里翻车。所以哪怕时间紧张也要先抽一部分数据做一致性测试让两个人标同一批图统计框和类别的吻合度。这个步骤做好了后面的训练、调参、部署才会顺。另外一个小技巧训练时把白天图全部转灰度夜间红外图保持灰度输入能大幅降低夜间掉点的风险这个改动几乎零成本而且实测很稳值得直接抄进自己的流程里。
返回列表