YOLOv5交通标志识别实战:数据集处理、训练调参与部署

发布时间:2026/9/11 21:35:57
YOLOv5交通标志识别实战:数据集处理、训练调参与部署 简介一套基于YOLOV5的交通标志识别检测完整项目专为毕业设计、课程设计与期末大作业设计。代码自带详细注释新手也能看懂下载后简单部署即可运行能稳定输出检测结果。压缩包共266个文件体积约423MB内容覆盖Python源码、yaml配置、预训练pt权重、jpg/png图像样本、txt标注文件以及训练日志csv等各类型文件搭配完整便于直接复现与二次开发。目前已有105人学习使用。项目还包含多种环境配置与Dockerfile并附有训练曲线和结果数据可帮助读者系统理解YOLOV5从数据准备、模型训练到评估推理的完整流程整体界面简洁、功能清晰具有较高的工程完成度是完成高分毕设或课程汇报的实用资料。1. 交通标志识别用 YOLOv5先想清楚四个问题交通标志识别是我见过最容易跑完一个 epoch 就以为完工的毕设方向。训练脚本一启动、loss 曲线往下走很多人就转到写论文去了等真正拿手机去拍一张路牌回来测试发现要么框不稳要么把限速 30 认成 60再回头调数据已经来不及。标题里的数据集代码模型不是三个下载项而是一条链路数据决定了模型能学到什么模型结构决定了小目标能不能被召回部署方式决定了演示现场会不会翻车。这篇文章把 YOLOv5 交通标志识别的每一步拆开TT100K 这类公开数据集怎么落成 YOLO 格式、网络里哪些模块吃算力、训练参数到底在调什么、最后怎么把 best.pt 导出成不依赖 PyTorch 的推理程序。2. 交通标志数据集选型与预处理TT100K、GTSRB 还是自建2.1 三个公开数据集的差异与适用场景交通标志数据集的选择基本决定了后面所有工作的上限。国内场景的毕设一般首推 TT100K腾讯提供的是真实街景全景图画面里包含大量小目标、遮挡和光照变化跟模型最终拿到的测试照片最接近常见版本是上万张全景图像标注用 JSON 保存每个目标带 category 和 bbox类别分成 151 个细类和 46 个父类按父类统计做识别已经足够支撑一篇完整的毕设。GTSRB 来自德国是 5 万张级别的单标志图像只有类别没有检测框没法直接喂给 YOLOv5更适合拿来做分类头预训练GTSDB 是有框的德国数据集规模比 TT100K 小很多适合先把管线跑通。自己采集数据也可以但采集-清洗-标注的时间成本要按真实工作量算别只计划一周。数据集场景标注形式适合做什么主要坑TT100K国内真实街景JSONbox151 细类检测识别主实验目标小、类不平衡、需合并父类GTSRB德国单标志仅有类别标签分类预训练/对照实验无检测框不能直接训练GTSDB德国街景框类别快速验证训练流程数据量小精度上限有限自建按需求定制自标注补充特定标志耗时大样本不均衡选 TT100K 时建议把细类到父类的映射表也一起下载这个映射关系在研究报告里必须交代清楚否则后面对照实验结果没法解释。我一般会先统计每个父类的实例数量把数量少于 150 的类并入相近类或直接丢弃不然这些小类会让 mAP 的计算结果波动很大。2.2 用脚本把 TT100K 的 JSON 标注转成 YOLO txtYOLOv5 要求每张图片对应一个同名 txt每行是类ID x_center y_center width height四个坐标全部归一化到 0-1。TT100K 的 JSON 里 bbox 字段是左上角坐标加宽高转换时主要做两次换算中心点坐标和宽高分别除以图片宽高。还要注意 TT100K 不是每张图都有目标没有 objects 的图直接跳过否则会生成空 txt 导致训练时报警。import json import os def tt100k_to_yolo(json_path: str, out_dir: str, class_map: dict) - None: os.makedirs(out_dir, exist_okTrue) with open(json_path, r, encodingutf-8) as f: ann json.load(f) for img_name, info in ann[imgs].items(): objects info.get(objects) if not objects: # 无目标的图不生成 txt continue h, w info[height], info[width] lines [] for obj in objects: cat obj[category] if cat not in class_map: # 未纳入映射的细类直接丢弃 continue x, y, bw, bh obj[bbox] # 左上角坐标 宽高 cx, cy (x bw / 2) / w, (y bh / 2) / h nw, nh bw / w, bh / h lines.append(f{class_map[cat]} {cx:.6f} {cy:.6f} {nw:.6f} {nh:.6f}) if lines: txt_name os.path.splitext(img_name)[0] .txt with open(os.path.join(out_dir, txt_name), w, encodingutf-8) as f: f.write(\n.join(lines))class_map 是细类名到新 ID 的字典这一步同时完成了类别合并。代码对标注是全量遍历建议转换前先打印数据集的 height/width 分布确认没混入特殊尺寸图片部分标注的 bbox 存在越界比如 xbw 大于实际宽度转换时把坐标 clamp 到 [0,1] 能避免训练时出现 NaN loss。转换完随便抽 20 张图把 txt 画回原图检查一遍这个习惯能省掉后面最痛苦的排查时间。2.3 按 8:2 划分 train/val并处理类不平衡划分时不能直接随机切整图建议以图片 ID 为粒度做分层采样避免同一场景的连续帧同时出现在训练集和验证集。YOLOv5 的目录结构固定为 images/train、images/val、labels/train、labels/val 四个目录训练时靠 data yaml 里的路径指向它们。交通标志数据里红色禁令标志通常占大头蓝色指示标志偏少若验证集按纯随机划分某些类可能出现训练集个数很少、验证集却集中出现的情况所以划分前跑一次类别计数按类比例采样更平稳。对自建数据我常用一个省事的补充办法把 TT100K 按上述流程处理好再混入自己拍的标志图通过缩放、旋转、亮度扰动保证每类不低于 200 个实例最后参与划分。这样做的好处是训练出的模型对手机拍摄视角的适应力明显强于只用公开数据的效果。3. YOLOv5 网络结构与交通标志检测头的关键差异3.1 Backbone-Neck-Head 三段式与 SPPF 的位置YOLOv5 的网络结构可以按 Backbone、Neck、Head 三段来理解。Backbone 用 CSPDarknet负责从 640×640 输入里逐层提取特征s 版本的基础通道数是 64模型宽度由 depth_multiple 和 width_multiple 一起控制Neck 里有两个关键模块SPPF 用多个 5×5 池化串联扩大感受野PAFPN 则把高层语义和低层纹理做双向融合让 8 倍、16 倍、32 倍下采样三个尺度的特征都能拿到足够上下文Head 是三个 Detect 分支分别在 80×80、40×40、20×20 的特征图上预测小、中、大目标。交通标志的像素占比普遍小80×80 分支的质量直接决定召回率而这正是从 COCO 预训练权重迁移过来时最需要关注的部分。3.2 为什么选 YOLOv5 而不是 YOLOv8 做毕设YOLOv8 出来后不少评审会问为什么不直接用新版本。回答要点其实很实际YOLOv5 的 Anchor-Based 检测头虽然传统但对小目标密集场景并不吃亏检测头输出结构固定转 ONNX 和 TensorRT 的资料最全YOLOv8 的 Anchor-Free 解耦头在 COCO 上精度略高但毕设要把整条链路做完整而不是只在 mAP 上多 0.5 个点。交通标志尺寸小、类别间外观差异大Anchor-Free 中心分支在特殊分布上不一定有优势。选型报告建议放一张小目标召回率对比表用数据说话而不是只说生态成熟。3.3 锚框预设与真实交通标志尺寸分布官方 YOLOv5 的锚框是从 COCO 上聚类出来的COCO 里中大型目标占多数直接拿来训练 TT100K 会浪费大量锚框在小目标分支上。YOLOv5 提供自适应锚框工具训练前执行python utils/autoanchor.py --data traffic.yaml --img 640脚本会对训练集所有标注重新聚类并输出 Best Whitebox 结果。用 TT100K 父类版本跑过几次聚类出的锚框和官方默认有明显差异示意如下。特征层官方默认锚框640TT100K 常见聚类示意P3/8小目标10,13 / 16,30 / 33,234,5 / 8,10 / 14,16P4/16中目标30,61 / 62,45 / 59,11924,22 / 38,42 / 62,58P5/32大目标116,90 / 156,198 / 373,32698,84 / 140,128 / 220,180注意这张表是示意不同标注质量下聚类结果会变。自适应锚框的目的不是提升 mAP而是让前几个 epoch 的框回归更平稳、loss 下降更快真正决定精度的还是数据量和增强策略。4. 用自定义交通标志数据集训练 YOLOv5 的完整流程4.1 Python、PyTorch 与 CUDA 版本怎么匹配环境配置是训练前的第一关核心原则是 PyTorch 和 CUDA 版本绑定安装。常见做法是用 conda 建独立环境Python 3.9 或 3.10 都可以PyTorch 选 1.13 或 2.x 对应版本的 wheelNVIDIA 驱动向下兼容不用装到最新。激活环境后进入 YOLOv5 目录执行pip install -r requirements.txt安装依赖。这里有个容易踩的坑requirements.txt 里的 torch 会被重新安装如果你已经手动装好 CUDA 版注意别被覆盖成 CPU 版用python -c import torch; print(torch.cuda.is_available())验证输出 True 再进下一步。4.2 写好 data yaml 与模型 yaml数据配置是训练管线的入口路径必须和目录结构对上。下面是一个按父类精简后的 traffic.yaml 示例# traffic.yaml path: ./datasets/traffic train: images/train val: images/val nc: 8 names: 0: no_entry 1: no_parking 2: speed_limit_30 3: speed_limit_60 4: school_zone 5: yield 6: stop 7: pedestrianpath 是数据集根目录建议写相对路径避免换机器后路径失效nc 和 names 的索引必须和 txt 的类 ID 一一对应顺序错一个训练不会报错但 mAP 会接近 0。模型 yaml 里新版 YOLOv5 会自动用 data 的 nc 覆盖不需要手动改 nc想改模型宽度深度时才去动 depth_multiple 和 width_multiple。4.3 用 YOLOv5 自带 train.py 启动训练训练命令不长真正影响结果的是参数取值。以 yolov5s 预训练权重为起点200 个 epoch 对小数据集偏多但配合早停不会浪费时间batch-size 按显存调8G 显存跑 640 输入用 1624G 可以到 32 或 64。建议加上--cache ram第一次读图慢之后训练一个 epoch 的速度能差三倍。python train.py \ --data traffic.yaml \ --weights yolov5s.pt \ --img 640 \ --batch-size 16 \ --epochs 200 \ --cache ram \ --hyp data/hyps/hyp.scratch-low.yaml \ --workers 4--weights 指定 COCO 预训练权重第一次训练前会自动下载--hyp 选择低增强策略比 medium 更保守适合数据量没那么大的情况。关键超参数的作用集中在这张表超参数默认值low在交通标志场景的建议lr00.01数据少可降到 0.005防止前期震荡lrf0.01保持默认余弦退火末期让 loss 落稳warmup_epochs3.0保持默认或降到 2mosaic1.0前 70% 轮次开启后期降为 0 见第 6 章mixup0.0不开交通标志重叠后语义混淆严重hsv_h / hsv_s0.015 / 0.7可加大饱和度扰动增强不同光照路牌4.4 训练中断恢复与显存不足训练到一半断掉是常态千万不要从头跑。train.py 支持 --resume 参数指定上次实验的 last.pt 所在 runs/train/exp 目录即可恢复优化器状态、学习率调度和轮次。显存不足时优先把 batch-size 减半其次关掉 --cache ram 改用默认 cache如果 mosaic 对显存压力大也可以在 hyp 里把 mosaic 调到 0.5。日志里出现 NaN loss 先查标签坐标是否越界再把越界 clamp 加进转换脚本。5. 模型评估、ONNX 导出与不带 PyTorch 的部署5.1 用 val.py 读 mAP、P-R 曲线和混淆矩阵训练结束后验证集评估命令别直接拿 detect.py 目测几张图就下结论python val.py \ --data traffic.yaml \ --weights runs/train/exp/weights/best.pt \ --img 640 \ --conf-thres 0.001 \ --iou-thres 0.6--conf-thres 设成 0.001 是为了把全召回域算进 mAP官方评估默认就是这个值--iou-thres 是 NMS 的 IoU 阈值0.6 保留更多重叠框给评估程序。结果目录里除了 mAP 数值重点看两样confusion_matrix.png 里多个类别互相误检是颜色相近还是样本少P_curve.png 在召回 0.8 附近是否还有平台期。交通标志常出现的左转标志和直行标志互认在混淆矩阵里会非常直观。5.2 导出 ONNX 并用 onnxruntime 推理演示环境可能没有 GPU交代码也不该强迫对方装整个 PyTorch。YOLOv5 官方 export.py 可以把 best.pt 转成 ONNXpython export.py \ --weights runs/train/exp/weights/best.pt \ --include onnx \ --opset 12 \ --simplify--simplify 依赖 onnx-simplifier去掉冗余算子CPU 推理时快一截--opset 12 是 onnxruntime 兼容性较好的版本。导出的模型是 (1, 25200, nc5) 的原始输出要自己做阈值过滤、坐标解码和 NMS。下面是精简可运行的推理代码import cv2 import numpy as np import onnxruntime as ort session ort.InferenceSession(best.onnx, providers[CPUExecutionProvider]) input_name session.get_inputs()[0].name img cv2.imread(street.jpg) h, w img.shape[:2] # 输入要求 RGB、归一化到 [0,1]、640x640 blob cv2.resize(img, (640, 640))[..., ::-1].astype(np.float32) / 255.0 blob blob.transpose(2, 0, 1)[None] outs session.run(None, {input_name: blob})[0][0] # (25200, nc5)outs 的行顺序与锚框一一对应前 5 列是 cx, cy, bw, bh, objectness之后是每个类别的概率。解码时把 cx, cy, bw, bh 乘上对应特征层的 stride再除以 640 归一化回原图尺度分类置信度用 objectness 乘类别概率阈值取 0.4 左右即可。NMS 可以复用 YOLOv5 仓库里 non_max_suppression 的思路也可以用 cv2.dnn.NMSBoxes 一次搞定。跑通后把读取图片路径改成摄像头或批量目录就完成了毕业设计中系统的部分。5.3 无 GPU 环境下的部署要点常见翻车点有两个。一是推理结果全为空先确认输入图片通道和归一化再用同一张图跑一次原版 detect.py 对照能定位是导出问题还是后处理问题。二是 ONNX 推理比 PyTorch 还慢CPU 上优先装 OpenVINO 执行提供程序Intel CPU 能提速一倍以上实在不行就把输入从 640 降到 512但交通标志小目标损失明显建议用第 6 章的技巧补回来。6. 把交通标志 mAP 再往上推的三个技巧6.1 训练后期关闭 mosaicmosaic 在数据量小时是神器把四张图拼成一张让小目标数量暴增但到了第 120 个 epoch 之后拼接造成的跨图上下文会干扰模型对真实路况的建模。YOLOv5 支持按轮次衰减增强把 hyp 里 mosaic 改为 0.0配合 --resume 继续训练比从头重跑更稳妥。实际操作是训练到 70% 轮次时手动生成一个 hyp.finetune.yaml只保留 mosaic0、copy_paste0然后接着跑。这个操作通常能提升 0.5-1 个 mAP对标志互相遮挡的案例改善最明显。6.2 验证阶段开启 TTAval.py 加上--augment会同时推理原图、翻转和缩放版本等效于测试时增强。毕设实验对比表里放一组基础 mAP和TTA mAP很有说服力前者 87.2 后者 88.6 这种差距就能说明增强策略有效。TTA 用于最终统计可以但实时演示场景别开三倍计算量换 1 个点不一定值。6.3 用 WBF 代替纯 NMS 合并重叠框交通标志密集时同一个标志会被相邻锚框打出三个置信度相近的框纯 NMS 直接删掉会损失定位精度。加权框融合WBF把重叠框按置信度加权求平均框的定位更可靠尤其适合小目标。实现可以直接用 ensemble-boxes 库from ensemble_boxes import weighted_boxes_fusion # boxes_list 的元素格式: [x1, y1, x2, y2, score],坐标已归一化到 0-1 # 下面输入来自 TTA 的三组解码结果,只列一组示意 boxes [[0.02, 0.03, 0.21, 0.19, 0.92], [0.03, 0.02, 0.22, 0.20, 0.88], [0.02, 0.04, 0.20, 0.18, 0.90]] labels [[0], [0], [0]] scores [[0.92], [0.88], [0.90]] fused_boxes, fused_scores, fused_labels weighted_boxes_fusion( boxes, scores, labels, iou_thr0.55, skip_box_thr0.001 )用 TTA 的三组输出做 WBF比单模型加 NMS 的定位稳定度高不少。把 6.2 和 6.3 合并成一条求值流程先开 --augment 导出三份检测 json再写二十行脚本做 WBF这就是毕设报告里最扎实的一个实验小节。本文还有配套的精品资源点击获取