交通标志识别这个需求最初来自我参与的一个行车辅助项目。合作方给了一批行车记录仪实拍视频要求把画面里的常见交通标志实时识别出来并把类别和位置叠加到显示界面上。当时我第一反应是直接用通用目标检测模型跑结果验证集一查限速标志之间互相混禁令标志和警告标志这类外形高度相似的类别误检率一直压不下去。后来反复梳理才发现这类任务的真正难点不在模型结构而在标志长得像、目标尺寸变化大和真实场景干扰多这三件事。这套系统最终的技术路线定为YOLOv5加Python识别范围固定为45种国内常见交通标志覆盖警告、禁令、指示三大类。经过三轮迭代测试集上的mAP0.5稳定在91%左右GPU工作站上单帧推理约12msCPU平台用OpenVINO转换后约85ms基本满足行车场景的实时性要求。下面我把整个项目从选型、数据准备、训练调参到部署排坑的完整过程都拆开讲一遍涉及到的数据策略、参数设置和排查方法都是我实测过的方案可以直接参考。1. 选型阶段为什么是YOLOv5而不是其他检测模型先说结论交通标志识别本质上是一个中等类别数量、强实时性、小目标较多、场景光照极端的检测任务YOLOv5是在这组约束下工程代价最低的方案。但为了说服合作方也为了让自己摸清底数我还是把主流的几个方向都跑了一遍对比。1.1 项目一开始就定好的硬性指标在动手之前我先把任务约束列成了几个硬指标后面所有选型和技术决策都以它们为准检测目标45类国内常见交通标志覆盖警告、禁令、指示三类标志输入源行车记录仪视频流分辨率为1280x720到1920x1080不等输出每帧中每个标志的类别标签和边界框坐标运行平台先在带GPU的工作站上做验证后续要迁到CPU设备或嵌入式平台精度目标mAP0.5不低于88%单帧推理时间在GPU上不超过100ms这里面有一条容易被忽略实时性要求直接排除了很多精度很高但速度跟不上的方案。视频流处理不像单张图片一帧慢了就可能导致标志框滞后实际驾驶过程中这是完全不可接受的。1.2 候选模型实测对比我把几类检测模型分别跑了小规模的实验数据统一用当时手头第一批1000张标注图结果整理成一张表方案推理速度GPUmAP0.5工程复杂度视频流场景适配Faster R-CNN约300-600ms/帧高高需单独管理RPN不满足实时SSD约50-80ms/帧中中等小目标表现一般YOLOv3约60-90ms/帧中高中等速度勉强够用YOLOv4约40-60ms/帧高中等可用但社区资料偏少EfficientDet约80-150ms/帧高高训练成本大速度与精度折中不够好YOLOv5约10-20ms/帧高低一体化工具链最合适这里要说明一下Faster R-CNN在单张静态图上的精度其实可能比YOLOv5还高一点尤其在目标密集的场景下但跑视频流时推理时间直接破防。SSD速度快可它对小目标的召回率偏低交通标志在远处往往只有二三十像素大小SSD容易漏检。1.3 YOLOv5最打动我的几点抛开跑分数据YOLOv5在工程层面有几个非常实际的优势这是我最终选择它的核心原因第一一体化做得足够好。训练、验证、推理、导出ONNX/TensorRT全链路都在同一个仓库里不用自己拼装流程。数据处理、锚框自适应计算、训练指标可视化这些都是开箱即用省下来的时间全部可以投入到数据清洗和调参上。第二模型规格选择灵活。YOLOv5提供了n/s/m/l/x五个规格我初期用s版本快速验证数据质量后期再切到m版本追求精度换模型只需要改一个权重路径参数不用改动任何其他代码。这种弹性在实际项目里价值很大。第三社区生态成熟。YOLOv5被各路开发者反复验证过基本你能踩到的坑网上都有对应的讨论和解决方案。我们做工程落地最怕就是方案冷门、出问题没地方查YOLOv5在这点上风险最小。第四对算力要求不高。消费级显卡8GB显存就能完成训练不需要租用昂贵的大显存实例。对于项目前期预算有限的情况下这个因素也很实际。2. 数据先行45类标志数据集的搭建与踩坑说实话这个项目真正花时间最多的地方就是数据。你去看YOLOv5官方提供的COCO预训练权重它已经把通用目标检测的知识学得很好了但交通标志这种细分类别预训练权重帮不上太多忙。尤其限速标志这种红圈白底加数字的结构类别之间就差一个数字模型能不能区分完全取决于训练数据质量。2.1 三类数据来源与取舍我把数据来源分成三块第一块是公开学术数据集。网上有一些整理好的交通标志数据集可以用但有人标注不够精细图片分辨率参差不齐类别定义也未必和我们的45类清单完全一致。我的做法是取前过滤一遍把所有标注框超出图片边界、类别名对不上号、图片明显模糊损坏的样本直接剔掉宁可少也不能脏。第二块是模拟驾驶环境截图。我在一个开源自动驾驶模拟器里跑了几十条线路把不同天气、不同光照条件下的截图批量导出再写脚本做抽帧。模拟图的优势是类别分布均匀、天气光照可控缺点是风格和真实行车记录仪差异明显。我的定位是补充类占比控制在15%以内防止模型在模拟风格上过拟合。第三块是实拍数据也是最核心的部分。我对合作方提供的行车记录仪视频做了抽帧处理具体流程是每秒抽1帧、相邻帧计算画面相似度相似度超过阈值的丢弃避免连续帧高度重复导致训练集冗余。最终留下约1.2万张实拍图再和公开数据、模拟图合并清洗得到总数约2万张、标注框约3.4万个的数据集。这里要特别强调一个原则交通标志数据集的质量比数量重要得多。我做过对照实验同样的模型结构用1.2万张干净标注训练出来的mAP比用2万张脏数据边框不贴合、部分标注错误高出约5个百分点。标注不干净模型学习的特征就有噪声后面怎么调参都救不回来。2.2 标注格式与工具选择标注格式我用的是YOLO格式每张图片对应一个同名txt文件每一行是类别ID 中心点x坐标 中心点y坐标 框宽度 框高度所有坐标都归一化到0-1之间。标注工具用的是LabelImg的YOLO模式标注操作本身不复杂但有几个细节很容易出错框要尽量贴合标志边缘但不要把标志外侧的白边全部包进去。白边信息对模型区分同类别内部的样式变化有帮助全包进去会损失这部分线索一个框只标一个标志。两个标志重叠时宁可标两个相邻框也不要把它们合并成一个框遮挡超过一半的目标要慎重。我的做法是保留但这类样本在训练时容易被模型当成难例如果数量超过总量的5%就需要人工复核再决定去留标注完成后我做了两轮质量检查。第一轮是抽图检查按2%的抽样率把标注结果叠加回图片人工看框和目标的贴合程度第二轮是统计类别分布按每个类别单独输出数量和标注框尺寸分布重点排查有没有类别标注错位的情况。2.3 数据增强的边界在哪里YOLOv5自带的增强已经很强包括mosaic拼接、HSV色彩扰动、随机翻转、随机缩放、随机平移等。但交通标志数据集有一个特殊性标志内部的文字和图形方向性很强。举个例子限速标志里50和05在你做水平翻转后其实变成了完全不同的视觉特征如果训练时做了翻转模型可能会把50和05两个不同类别搞混。更典型的是注意儿童这类含有人物图形的警告标志翻转后图形的左右方向变化会让特征学习产生混乱。我最终只保留了水平翻转并且把旋转增强角度控制在±15°以内颜色增强的强度也调低了。原因是交通标志的颜色本身具有强语义性——红色代表禁令、黄色代表警告、蓝色代表指示如果HSV扰动太狠把红色调成橙色反而会抹掉模型依赖的类别关键特征。2.4 类别不平衡的几种处理方式交通标志数据集天然存在类别不平衡限速标志、禁令标志这类常见类别样本充足但一些少见的警告标志比如注意横风、注意落石实际路面上很少见采集到的样本可能只有几十张。我的处理方式是按类别做统计把样本量低于200张的类别列为少数类然后分三步走第一步对少数类做复制增强。把同一张图做轻度几何变换和亮度扰动生成多份变体加入训练集但变换幅度控制在很小范围内避免破坏标注框与目标位置的对应关系。第二步调整类别权重。在计算loss时对少数类的预测误差乘以一个略大的系数让模型在训练时更重视这些类别的分类正确性。具体做法是在损失函数层面加一个基于类别频率的权重向量。第三步也是最有效的监控每个类别的召回率。训练一轮后单独导出各类别的召回率报表对召回率明显偏低的类别手工补充数据而不是简单地增加训练轮数。我试过纯靠加训练轮数去压类别不平衡结果只是让模型在多数类上过拟合少数类召回率纹丝不动。3. 训练过程环境、参数与收敛判断数据准备好之后就是训练环节。YOLOv5的训练流程本身已经封装得很完善但真正把模型训到目标精度需要在环境版本、超参数和训练监控上做细致的工作。3.1 环境版本怎么搭最稳我用的环境组合是Python 3.8、PyTorch 1.10、CUDA 11.3训练显卡是一块消费级8GB显存GPU。这个组合是踩过几个坑之后确定的太新的PyTorch版本有时在特定算子上的行为变化会影响训练稳定性尤其对复现别人的实验结果不友好。依赖安装直接按YOLOv5官方要求的pip包列表装就行注意一点不要把torchvision版本随意升级到和torch不匹配的版本否则推理导出阶段会报很多奇怪的错误。如果显存不够优先调小batch-size而不是调小输入分辨率这是我实测后的建议。分辨率下降会导致小目标特征丢失严重对交通标志这种小目标为主的检测任务影响很大。3.2 数据划分、训练命令与数据结构数据划分我按7:2:1分成训练、验证、测试三部分。这里有一个很容易忽略的细节视频抽帧得到的连续帧内容高度相似如果随机划分同一段视频的不同帧可能同时出现在训练集和测试集导致指标虚高。我是按视频段为单位划分的把同一段视频抽出来的所有帧放进同一个集合这样测试集面对的是真正没见过的场景。训练命令用的是官方标准的train.py入口python train.py --data data/tsr.yaml --weights yolov5s.pt \ --img 640 --batch-size 16 --epochs 120 \ --cache --project runs/train --name tsr_v1对应的data/tsr.yaml文件内容大致长这样train: datasets/train val: datasets/val test: datasets/test nc: 45 names: 0: speed_limit_40 1: speed_limit_50 2: speed_limit_60 3: no_left_turn # ... 其余类别按清单依次列出来这里有一个关键习惯类别名称的命名要清晰规范。因为后面对混淆矩阵分析、错误案例复盘全靠类别名称来定位问题命名含糊会浪费大量排查时间。3.3 核心超参数设置的逻辑在超参数上我并不是直接套用默认配置而是针对交通标志任务做了一些调整输入分辨率设为640。我对比过1280分辨率mAP提升不到2%但训练速度和推理速度都明显下降性价比太低batch-size设为16这是当前8GB显存能稳定跑的上限。开启--cache参数把图片预加载到内存训练速度能提升30%以上初始学习率lr0用0.01配合前3轮warmup预热。YOLOv5默认的warmup机制很关键直接高学习率起步容易在前面几个epoch就把模型参数带偏预训练权重用yolov5s.pt而不是从零训练。迁移学习的收敛速度快得多尤其前20轮loss下降非常明显最值得注意的一点我在最后10个epoch把mosaic增强关闭了。原因是mosaic生成的是四张图拼接的拼图图和真实单图场景分布有差异最后阶段强制模型从真实单图分布上学习能提升实际场景的表现3.4 训练监控与收敛判断训练过程中我主要看几个指标box_loss、obj_loss、cls_loss三条曲线以及每个epoch结束后的验证集mAP曲线。loss曲线下降但mAP不动说明模型在训练集上拟合了但泛化能力没有同步提升train的loss还在降、val的loss开始回升就是过拟合信号这种时候果断停掉继续训练只会让验证集表现更差。三轮迭代后最终结果mAP0.5约0.91mAP0.5:0.95约0.68Precision约0.93Recall约0.90。把模型从s规格换到m规格后mAP0.5又提了约1.5个百分点单帧推理速度仍在可接受范围内所以我最终部署版本用的是m规格。4. 识别率提升的实际操作不光是调参那么简单模型训练到90%之后再往上提就不是简单的多跑几个epoch能做到的了。我做了几轮针对性优化每一步都是基于数据和实际错误样本的。4.1 混淆矩阵暴露出的核心问题训练完第一步就是看混淆矩阵。我给45个类别拉了一张完整混淆矩阵主要问题集中在三个地方限速标志内部互相混淆、部分禁令标志之间混淆、个别警告标志易被漏检。限速标志的混淆最典型。限速50和限速60的结构几乎完全一样都是红圈白底黑字区别仅仅是一个数字。模型在两者之间犹豫说明数字区域的特征在卷积过程中没有被充分捕捉。这说明小型目标上的细节判别力不足单纯加深网络不一定能解决需要从数据侧加强。4.2 难例挖掘从错误样本里找答案我把测试集上所有漏检和误检的图片单独导出逐张查看。总结下来两类问题最常见第一类是小目标漏检。标志在画面远处只有15到20像素大小卷积下采样后特征几乎丢光了。针对这个问题我在增强策略里加入了裁剪放大随机选取图像中的目标区域裁剪后放大回原图尺寸等同于制造了一批近距离大目标样本让模型在不同尺度上都能稳定看到标志。第二类是复杂背景误检。城市路边的红色广告牌、消防栓上的红色圆形标识、甚至一些红色车尾灯都可能被模型判成禁令标志。这些非标志物和禁令标志在颜色、形状上有相似性模型仅凭外观特征无法完全区分。解法是收集了一批负样本——画面中没有标志、但含有类似圆形或三角形物体的图片——加入训练集进行难例挖掘让模型学会抑制这类响应。4.3 推理侧参数调整与多帧投票单帧独立检测在视频流场景容易产生抖动和误检。我把推理参数中的conf_thres从默认的0.25提高到0.3iou_thres保持0.5这样低置信度的预测框被过滤掉误检减少。更重要的是引入多帧投票机制同一位置连续出现5帧、其中至少3帧检测到同类标志才输出最终结果。这个逻辑非常简单但对稳定性的提升非常大实现起来也只需要维护一个记录框位置和类别的滑动列表。代价是标志首次出现到确认输出有几百毫秒延迟对于行车辅助这类应用场景完全可接受。4.4 颜色空间辅助校验这算是我的一个取巧设计。YOLOv5输出的检测结果先不直接展示而是对检测框内的像素做一个HSV空间统计计算红色、黄色、蓝色占比。国内交通标志的颜色语义非常固定禁令标志红圈为主、警告标志黄底为主、指示标志蓝底为主。当检测框内的颜色分布和类别预期严重不一致时直接丢弃这个框。这个后处理逻辑成本极低每帧增加不到1ms但能有效过滤广告牌、车辆灯光这类外观上有欺骗性的误检。实测下来误检率下降约30%是我这轮优化中性价比最高的一步。5. 部署落地时踩过的几个坑训练和验证都跑通了真正的考验在上线。很多问题只有到了真实场景才会暴露这里记录几个印象最深的坑。5.1 视频流处理从逐帧检测到队列缓冲最开始我直接在摄像头回调里逐帧调用检测结果帧率波动很大画面卡顿明显。原因是检测耗时不恒定环境复杂时耗时更长直接阻塞了视频采集线程。后来改成生产者-消费者模式采集线程把帧放入队列检测线程从队列中取帧处理再通过一个滑动显示窗口同步输出。同时在CPU推理较慢的场景下做隔帧检测即每两帧检测一次缺失帧用上一帧检测结果加简单的线性位置预测补上。标志在连续帧中的位移通常很小这个近似处理经验证完全够用。5.2 小目标与多尺度问题同一个标志在远处可能只有20像素靠近后占到画面四分之一尺度跨度很大。YOLOv5的anchor默认设计对COCO这类通用目标比较友好但对交通标志的尺度分布不一定匹配。我在训练时开启--multi-scale参数每10个batch随机将输入尺寸在0.5倍到1.5倍之间变换增强模型对尺度变化的鲁棒性。这里有个注意事项多尺度训练会放大batch间的梯度方差batch-size小于16时容易不稳定不建议开启。5.3 模型导出与CPU加速训练好的PyTorch权重在GPU工作站上表现很好但迁到无GPU的机器上直接推理就非常吃力。我用YOLOv5自带的export.py先把模型导出为ONNX格式然后用OpenVINO的模型优化器转成IR格式再走精度校准量化到INT8。量化后精度损失约2%但CPU端推理速度从约350ms直接降到85ms左右提升非常可观。这个过程中最容易踩的坑是ONNX导出的动态轴设置。导出时如果没设置正确的动态batch轴后面部署时灵活性会受限。我的建议是直接按固定batch1导出检测任务通常不需要动态batch。5.4 一个隐蔽的坑光照泛化不足训练数据集中在白天晴天场景导致阴天、夜间和逆光条件下识别率明显下降。这类问题在训练指标上几乎看不出来因为验证集同样是白天样本占主导只有到了真实路测才发现夜间漏检率特别高。解决方法是在数据增强中加重曝光扰动和色温扰动模拟不同光照条件下的画面观感同时补充一批真实的夜间、黄昏视频帧做finetune。补了大约2000张夜间样本后夜间场景的召回率从65%提升到80%以上。6. 从单帧识别到应用系统的扩展思路检测模型只是整个系统的感知前端真正形成可用功能还需要和跟踪、增量学习以及外部信息源联动。6.1 加入目标跟踪消除框闪烁单帧检测在视频流中的表现不够稳定偶尔一帧漏检会导致标志框闪烁消失。我加入了一个轻量级的多目标跟踪模块对检测框做帧间匹配输出稳定ID的同时在漏检帧用上一帧的位置信息预测补框。加入跟踪后整个视觉体验顺畅了很多也为后续的帧间投票和置信度累计提供了基础。6.2 新类别增量训练交通标志体系会不断更新每次新增类别就全量重训显然不现实。我的做法是冻结backbone部分参数只解冻检测头用新类别数据配合少量旧类别数据混合训练初始权重直接加载原有模型。实测用这种迁移方式新增类别只需要训练30到50轮就能达到可用精度旧类别的召回率基本不受影响。6.3 和导航地图等外部信息交叉验证单个视觉模型输出直接使用会有一定风险。比如限速标志识别和导航地图里的限速数据不一致时我会选择双源交叉验证两个来源一致才输出最终限速值不一致时输出低置信度状态并提醒驾驶员注意实际路况。这种多源融合策略在实际系统中能显著降低误报带来的体验问题。最后说一点个人体会。这个项目的精度能到91%大半功劳在数据分析45类标志里很多类别之间的差异就是几像素的数字或者一条斜杠标注稍微不干净性能掉得非常明显。很多人上手就调模型结构但先花几天把数据清洗、难例分析和类别分布统计做好收益往往更大。另一个深刻感受是部署环节的问题只能靠真实现场暴露——夜晚、逆光、雨刷遮挡、跟在大车后面大半个标志被挡住这些都是训练时容易忽略、但实际行驶中天天遇到的工况。做类似项目的朋友建议尽早把模型放到真实道路上跑一遍别只对着测试集追求数字好看。
阅读完成 · 觉得有帮助?