☰
YOLOv5交通标志检测实战:从数据集构建到模型部署全流程
2026/10/20 18:38:22 网站建设 项目流程

交通标志检测与识别,这几个字放在一起,做视觉的朋友应该都不陌生。高速上的限速牌、市区里的禁转标志、路口的斑马线提示,背后都依赖YOLO系列模型在毫秒级时间内把标志框出来再认出类别。这个项目就用YOLOv5把这条链路完整走通了——从数据集构建、标注处理、模型训练,到推理验证、多格式导出,每一步都有对应的源码和配置可以直接参考。如果你正在入门目标检测,或者想找一个“小而全”的项目做技术验证,这篇文章值得看完。

多说一句,现在YOLOv6/v8/v9都已经满天飞了,为什么还要谈v5?因为v5的生态成熟度、资料密度、部署路径三点都是最稳的。对一个要快速落地、还要把原理讲清楚的项目来说,v5是把“工程性”和“教学性”平衡得最好的版本。

1. 项目定位:交通标志检测到底难在哪,YOLOv5凭什么能打

1.1 交通标志检测的三个硬骨头

交通标志检测和通用物体检测不太一样,它有几个特别别扭的地方。第一是小目标问题。一个限速标志在真实道路画面里往往只占几十分之一,输入640x640分辨率,很多标志只有20x20像素左右,特征信息非常少。第二是类别间的细粒度区分。限速30/40/50/80,外形都是白底红圈,区别只在数字,眼神不好的人都要凑近看,模型区分起来也不轻松。第三是环境干扰。逆光、雨雾、树枝遮挡、标志老化褪色,都会让检测难度直线上升。这些因素叠加在一起,决定了这个任务不能简单套用一个“下载现成权重、跑个demo”的流程。

1.2 为什么在这个项目里选YOLOv5

选YOLOv5有几个很务实的原因。首先是推理速度。交通标志检测大量用在车载和路侧设备上,对实时性要求高,v5的CSPDarknet骨干加PANet颈部结构在精度和速度之间拿捏得比较平衡。其次是部署链路。v5官方仓库自带导出脚本,ONNX、TensorRT、OpenVINO、NCNN都有现成支持,不管后面要跑在工控机还是嵌入式设备上,路径都是通的。再次是材料的丰富程度。不管遇到什么问题,社区里基本都有对应的讨论,对一个做工程落地的人来说,这比任何理论优势都重要。实际上,在很多实际项目里,v5仍然是生产环境里使用率非常高的选择,不是因为它最新,而是因为它最稳。

1.3 这个项目能做什么、适合谁

从应用角度看,这套检测能力可以接在辅助驾驶系统里做超速预警,可以用在路政巡查里自动盘点缺失标志,也可以做公交专用道的违规抓拍前端。从一个学习项目的角度看,它把“数据治理、模型训练、部署优化”三个关键环节完整串起来了。适合三类人:一是刚学完目标检测理论、想拿真实数据集练手的学生;二是要快速给业务方交一个检测demo的工程师;三是想深入了解迁移学习和模型调参细节的研究者。当然,如果完全没接触过深度学习,建议先把卷积神经网络的基本原理过一遍,否则后面调参会比较吃力。

2. 数据集准备:决定效果上限的第一个坎

2.1 开源交通标志数据集怎么选

这个项目的上限从一开始就不是模型决定的,而是数据集决定的。目前能拿到的开源交通标志数据大致分三类。第一类是城区道路实拍数据,标志类型最贴近国内真实场景,但短板是很多标志在画面里特别小,训练难度高。第二类是欧洲乡镇与高速路的标志数据,类别覆盖多、图像画质干净,多数图片是标志居中的特写,跟真实道路感知的检测场景有一定偏差。第三类是街景连续帧序列,目标多、角度丰富,但帧间重复多,直接训练容易对某一帧过拟合。

我给这个项目的建议做法是:以第一类数据为主干,用于训练检测主体;以第二类数据补充细分类别的样本量;第三类数据留着做验证集,检验模型在不同摄像头角度下的泛化能力。这样搭配出来的数据分布,比单用任何一个数据源都更贴近真实使用。

2.2 标注格式转换与数据清洗

YOLOv5用的标签格式是每张图片对应一个txt文件,每行表示一个目标:类别ID 中心点x 中心点y 宽度 高度,坐标全部归一化到0到1。很多开源数据给的是VOC的XML格式或者COCO的JSON格式,转换是绕不开的第一步。转换逻辑不复杂,就是把绝对坐标框换算成归一化坐标:

def convert_xml_to_yolo(xml_path, out_path, classes): 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.findall('object'): cls = obj.find('name').text if cls not in classes: continue box = obj.find('bndbox') xmin = float(box.find('xmin').text) ymin = float(box.find('ymin').text) xmax = float(box.find('xmax').text) ymax = float(box.find('ymax').text) cx = (xmin + xmax) / 2 / img_w cy = (ymin + ymax) / 2 / img_h w = (xmax - xmin) / img_w h = (ymax - ymin) / img_h lines.append(f"{classes.index(cls)} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}") with open(out_path, 'w', encoding='utf-8') as f: f.write('\n'.join(lines))

转换完成之后,务必做三件事。第一件事是可视化检查标注。用脚本把标注框画回原图上,人眼扫一遍,重点看框是否紧贴标志边缘,有没有把标志外圈的无关像素框进来,有没有标错的类别。第二件事是过滤异常目标。有些图片里标志只有十几个像素,人眼都难辨认,这类目标留在数据集里只会给模型增加噪声,建议直接过滤。第三件事是划分数据集,我习惯按8:1:1划分训练、验证、测试集,而且分层抽样,保证每个类别在各集合里的比例大致一致,避免某些类别全部集中到验证集的情况。

2.3 数据增强与类别不均衡处理

交通标志数据天然存在类别不均衡,“限速”类别的图片远多于“施工”类别。应对办法有几个:一是做过采样,对样本少的类别重复读入;二是在loss层面加类别权重;三是用增强手段合成新样本。YOLOv5默认开启Mosaic增强,把四张图拼成一张再随机裁剪,对提升小目标和遮挡场景的泛化很有帮助。我另外还会加一层Copy-Paste增强,把样本少的标志单独抠出来,随机贴到其他道路场景上,这个操作对缓解不均衡问题效果非常直接。

注意:Copy-Paste贴图的时候,一定要避免标志被贴在不合理的位置,比如悬空在天空中间。否则模型学到的是“交通标志出现在空中”,到了真实场景反而会误检。

3. 环境搭建与源码结构:先把战场摸清楚

3.1 环境配置的要点与硬件要求

YOLOv5官方仓库对环境的要求并不苛刻,Python 3.8以上配PyTorch 1.8以上就行。显卡方面,8GB显存可以跑s和m尺寸,16GB显存可以上l,如果只有CPU也能训练,就是慢得让人怀疑人生。我实际测试过,一张入门级显卡训150个epoch,包含几千张图的数据集,大概四到六小时能完成一轮跑通验证;换成CPU可能要翻十倍时间。所以如果你手头只有CPU,建议先把训练流程跑通一个微型数据集,确认逻辑没问题,再去找GPU资源跑正式实验。安装依赖的时候注意,官方requirements文件里的某些包版本偏保守,如果和现有环境冲突,优先保证torch和torchvision匹配即可,其他包用最新版本通常也不会出问题。

3.2 YOLOv5源码目录的关键文件

拿到源码后不用急着改代码,先把改动点分布搞清楚。train.py和detect.py分别是训练和推理的主入口;data/hyps目录下是超参数配置文件;models目录里是yolov5s.yaml这类模型结构定义文件;runs/train目录存放每次训练产生的权重、曲线图和日志。了解这些位置的意义在于:当你接到“需要调整检测头输出”这类需求时,能快速判断改哪个文件、会不会影响其他模块。我自己改得最多的其实是data目录下的配置文件和utils/autoanchor.py,后者是用来做锚框自动聚类的,后面会专门讲到。

3.3 迁移学习策略:借力预训练权重

交通标志检测这个项目没有必要从零训练。直接在官方提供的COCO预训练权重上做迁移学习,相当于让模型先掌握通用的识别基础,再针对交通标志做精调。具体到命令就是:

python train.py --data traffic_sign.yaml --weights yolov5s.pt --img 640 --batch 16 --epochs 150 --device 0

注意如果更换了类别数量,模型最后的检测头会重新随机初始化,这一步由源码自动处理。迁移学习的收益非常直观,我在同样数据量下对比过,从COCO权重开始训练,150个epoch就能稳定收敛;从零训练即使跑到300个epoch,mAP仍然低好几个点。所以只要有预训练权重,一定优先用。

4. 模型训练实操:配置、参数与全流程落地

4.1 数据配置文件详解

训练前要准备好一个yaml配置文件,告诉YOLOv5训练集和验证集的图片路径、类别数量nc、类别名称names。简化版本是这样:

train: data/traffic_sign/train/images val: data/traffic_sign/val/images nc: 8 names: ['speed_30', 'speed_40', 'speed_50', 'speed_60', 'speed_80', 'no_left', 'no_right', 'no_entry']

一个容易忽略的细节是,图片路径建议写绝对路径,或者用相对当前工作目录的路径。如果路径写错,启动训练时会提示找不到数据。训练之前先看一眼数据加载日志里的图片数量,确认训练集和验证集的图片数量与预期一致,再进入正式训练。数据量检查这一步虽然简单,但能帮你避免“训练了一天,发现数据集压根没读对”的悲剧。

4.2 训练超参数与Anchor策略选择

超参数里影响最明显的是img、batch和epochs。img是输入分辨率,640是默认值,如果小目标漏检多,可以尝试1280,代价是显存占用和训练时间几乎翻倍。batch在显存允许范围内尽量大,太小的话BN层统计不稳定,训练容易震荡。epochs一般设到150到300,配合早停机制使用,val mAP连续若干轮不提升就自动停止。还有个值得单独说的是autoanchor机制,YOLOv5训练启动时会用K-Means对训练集标注框做聚类,自动计算出更匹配当前数据集的anchor尺寸,输出类似“AutoAnchor: 3.4 anchors/target”的信息。如果训练日志里显示这个值比默认anchor对应的值更低,说明自动anchor确实在起作用。

学习率方面,YOLOv5默认的lr0是0.01,配合余弦退火调度。如果发现训练一开始loss就剧烈震荡或者出现NaN,优先把lr0降到0.001再试。另外可以开启AMP混合精度训练,在多数显卡上显存占用能省不少,训练速度也有提升,精度损失基本可以忽略。

4.3 训练过程监控与结果解读

训练过程中终端会持续输出box_loss、cls_loss、obj_loss以及precision、recall、mAP等指标。我的判断习惯是这样的:前20个epoch主要看loss有没有稳步下降,mAP是否初步抬升;中期看val/box_loss和val/cls_loss是否还在刷新新低;后期重点看val精确率和召回率的平衡。训练结束后在runs/train/exp*/weights目录下会出现best.pt和last.pt,best.pt是验证集表现最好的权重,last.pt是最后一个epoch的权重。发布或部署时一律用best.pt。测试时如果想多保留一些目标,可以把conf_thres调低到0.1到0.15,但误检也会相应增加。

5. 推理验证与多平台导出:让模型真正跑起来

5.1 推理测试与阈值调节

训练结束后,用detect.py跑一批真实场景的图片,看看模型在没见过的数据上表现如何:

python detect.py --weights runs/train/exp/weights/best.pt --source data/traffic_sign/test --conf-thres 0.25 --save-txt

source参数可以是单张图片、文件夹、视频流或摄像头ID。对交通场景,我建议测一段连续的视频,而不只是静态图片,这样才能看到模型在目标忽远忽近、画面抖动时的稳定性。阈值方面,conf-thres控制置信度门槛,调低会召回更多目标但误检也会变多;iou-thres控制NMS的IoU门槛,调高会让邻近的重复框被保留更多。实际调配时没有绝对标准,要看业务方更在意漏报还是误报。如果漏报代价高,就把conf调低,再用业务逻辑过滤明显不合理的检测结果。

5.2 导出ONNX与其他部署格式

模型验证没问题,就该考虑走部署链路了。官方export.py一步到位:

python export.py --weights runs/train/exp/weights/best.pt --include onnx

ONNX是中间格式,后面可以根据硬件平台继续转。NVIDIA平台转到TensorRT,Intel平台用OpenVINO,移动端和嵌入式设备用NCNN。转换时注意几点:第一,ONNX导出后建议用onnxruntime做一遍推理,对比原模型的输出,确认NMS之外的网络输出一致;第二,如果目标平台不支持某些算子,可能需要改动模型结构或者降级YOLOv5版本;第三,导出的时候别把NMS一起打进模型,NMS单独写算法实现,这样在端侧更好维护和调试。

5.3 精确率与召回率的实际预期判断

以几千张标注数据、8个类别、YOLOv5s模型在640分辨率下实测,mAP@0.5做到0.85到0.92是正常水平,mAP@0.5:0.95则通常在0.6到0.75之间。推理速度要看硬件,入门级独显上640分辨率单帧大约10毫秒左右,CPU上可能要100到300毫秒。这里提醒一句:指标一定是在自己数据集上重新测的,别人报的mAP只反映别人数据的特点。我在一次项目里见过某个公开模型报告mAP很高,但放到真实城区路上,很多日常标志压根不在它的类别表里。所以先跑一遍自己的数据,再谈指标对比。

提示:除了整体mAP,我还会额外统计“小目标子集”的AP。交通标志里小目标占大头,这个子指标更能反映模型在实际道路感知场景中的可用性。很多模型整体mAP看着不错,小目标AP掉得厉害,那在真实场景基本不能用。

6. 常见问题与调试经验:五个高频问题实测排查

6.1 高频问题速查表

整理一个我实际踩过、也在社区里反复看到的问题表,按排查优先级排列:

现象可能原因排查与解决
小目标漏检严重输入分辨率低 / anchor不合适把img提到1280;开启autoanchor重新聚类;适当调低conf-thres
相似类别互相误判类别样本不均衡 / 标注有错框按类统计样本量,补样本;抽查错误样本,修正标签
训练loss一直不降学习率不合适 / 数据异常lr0降到0.001;检查标签坐标是否越界、类别ID是否超出nc
验证mAP忽高忽低验证集过小 / 分布不均扩大并固定验证集;按场景分层抽样
推理速度不达标模型过大 / 后处理耗时换更小尺寸的模型;开启half精度;考虑TensorRT加速

6.2 排查思路:数据问题优先

训练效果不好时,第一反应应该是去看数据,而不是调模型。我排查的顺序基本是:先可视化一批训练样本和标签,确认数据没问题;再看训练曲线,判断是欠拟合还是过拟合;最后才动模型结构和超参数。这个顺序看起来简单,但在着急出结果的时候特别容易被人跳过。我自己在这个顺序上吃过亏——某次模型对“禁止左转”频繁漏检,我花了一天调整网络结构,效果几乎没有变化,最后发现是那一类别的训练样本里有一大半标注框套在了错误的标志上。数据修好后,同一个模型直接提升了近四个点的mAP。

6.3 关于“先用小样本跑通流程”的实操建议

最后分享一个我一直会执行的习惯:拿到项目的第一天,先用训练集的十分之一跑一个5到10个epoch的微型训练,目标不是追求精度,而是验证整条链路——数据读取有没有问题、训练能不能正常启动、推理和导出能否走通。这个流程跑通之后,再启全量训练。表面上看多花了一两个小时,实际上省掉的是全量训练跑到一半才发现数据路径写错的返工时间。对新手来说,这个习惯尤其重要,因为每一步都可能出错,而分段验证远比一次性排查要省时间。

这个内容后续还能怎么扩展?我个人觉得,在检测模型稳定之后,下一步值得做的是接一个分类分支来识别数字类标志,比如把限速标志裁剪出来后用轻量分类模型区分具体数字。这样既能保持检测模型的泛化能力,又能把细粒度识别交给更擅长的小模型去做,整套系统会更稳定。如果有余力,还可以尝试用知识蒸馏把大模型的效果迁移到更小的模型上,进一步压缩推理延迟。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询