基于YOLOV5的交通标志识别实战:数据集处理、训练调参与避坑指南
简介面向毕业设计与期末大作业场景的 Python 交通标志识别检测项目基于 YOLOv5 深度学习框架同时提供数据集、源代码与训练好的模型权重适合希望在短时间内搭建完整目标检测系统的学生和计算机视觉初学者。压缩包共 266 个文件整体约 423MB文件以 Python 源码、yaml 模型配置、jpg/png 图像样本、xml/txt 标注文件、pt 权重文件为主并包含说明文档、Docker 配置与训练日志等基本覆盖从数据准备、模型训练、评估验证到实时推理的完整流程代码注释细致目录结构清晰便于按模块学习和二次开发。目前已有 105 人学习下载项目源自个人完成的高分毕设经过严格调试可直接运行界面简洁、操作便捷功能覆盖交通标志识别的常用场景。对于毕业设计、课程设计或期末大作业而言这套资源既能帮助快速复现实验又能作为完善的项目模板实用价值较高。1. Python 基于 YOLOV5 的交通标志识别这个选题凭什么能拿到高分刚从考研复试里缓过来的人通常会把交通标志识别想得很简单拿摄像头拍一下再用模型认出来牌子完事。真正把训练跑起来才知道交通标志在画面里往往只有几十个像素还要扛住逆光、褪色、遮挡和动态模糊。YOLOV5 在这个场景下被大量毕业设计选中不是因为它精度天花板最高而是它的训练链路最完整、中文资料最多遇到玄学问题也最容易排查到根因。Python 环境、整理好的数据集、一套能改能跑的代码再加上训练完的 best.pt 权重就能从标注一路走到部署演示。这篇文章适合两类人拿它做毕业设计、需要短期出成果的本科生以及想用一个小目标数据集练手目标检测全流程的开发者。2. 数据集准备公开源怎么挑、标签格式怎么统一目标检测项目里数据集的整理时间通常占掉整个项目的一半这话一点不夸张。交通标志识别的公开数据源不少但每个数据集的标注格式、类别定义、图像分辨率都不一样直接拿来训练必然翻车。这一章把数据来源、格式转换和样本增强一次讲透。2.1 数据集从哪来三个公开源与各自的格式坑常见做法是优先用公开数据集做主力自采数据只用来补充模型会翻车的场景。交通标志方向有几个绕不开的公开源第一个是 TT100K全称是清华-腾讯百K交通标志数据集。它是国内场景标志类别是中文道路的真实标志bbox 标注以 JSON 形式组织涵盖限速、禁止、警告、指示这几大类。做中文交通标志识别TT100K 是最贴近实际用途的选择。但它的坑在于原始 JSON 里很多标志带有“符号类型”后缀直接读出来类别数会膨胀到几百个一般要人为合并到几十个主类。第二个是 GTSRB德国交通标志数据集。它的标注是一张图一个标志严格说更偏向分类任务而不是检测任务。如果强行用它训练检测模型需要把单个标志重新粘贴到背景图上做数据合成工作量不小性价比低。第三个是 LISA美国交通标志数据集以视频帧为主适合模拟真实行车视角但类别体系和国内标志差异较大不太适合直接对应国内毕设场景。我一般建议主力用 TT100K类别做映射合并再补充少量自己拍的视频关键帧。自采数据时用 OpenCV 批量抽帧几秒代码就能从一段行车记录仪视频里抽出几百张图然后用 labelImg 或 labelme 画框。自采样本不需要多关键是覆盖训练集里缺失的角度和光照。还有一条路是合成数据把标志 PNG 贴到随机背景上配上旋转、缩放、高斯模糊专门拿来补那些样本极少的类别这个方法后面会详细说。2.2 把 VOC/XML 标签转成 YOLO 格式一份能直接改跑的转换脚本YOLOV5 训练时读的标签不是 XML也不是 JSON而是每个图像对应一个同名 txt 文件每行格式是类别索引 中心点x归一化 中心点y归一化 框宽归一化 框高归一化。很多人第一次训练失败就是标签格式没转对。下面这段脚本把 Pascal VOC 的 XML 标注转成 YOLO 格式是工程里最常用的一个版本import xml.etree.ElementTree as ET import os # 注意类别顺序必须和后续 data yaml 里的 names 完全一致 class_names [speed_limit, warning, no_entry, stop, crosswalk] def convert_voc_to_yolo(xml_path, out_dir): tree ET.parse(xml_path) root tree.getroot() # 读取图像实际宽高不同数据集的字段名略有差异 size root.find(size) img_w int(size.find(width).text) img_h int(size.find(height).text) txt_name os.path.splitext(os.path.basename(xml_path))[0] .txt lines [] for obj in root.iter(object): name obj.find(name).text if name not in class_names: continue box obj.find(bndbox) x1 float(box.find(xmin).text) y1 float(box.find(ymin).text) x2 float(box.find(xmax).text) y2 float(box.find(ymax).text) # 边界防御有些标注的 x2/y2 会超过图像宽高必须 clamp x1 max(0, min(x1, img_w - 1)) x2 max(0, min(x2, img_w - 1)) y1 max(0, min(y1, img_h - 1)) y2 max(0, min(y2, img_h - 1)) # 转成中心点 宽高的归一化表示 cx (x1 x2) / 2 / img_w cy (y1 y2) / 2 / img_h w (x2 - x1) / img_w h (y2 - y1) / img_h lines.append(f{class_names.index(name)} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) with open(os.path.join(out_dir, txt_name), w, encodingutf-8) as f: f.write(\n.join(lines)) # 用法示例遍历某个标注目录逐份转换到 labels 目录 for xml_file in os.listdir(Annotations): if xml_file.endswith(.xml): convert_voc_to_yolo(os.path.join(Annotations, xml_file), labels)这段脚本的逻辑很直白先读 XML 里的图像宽高再遍历每个 object 标签把像素坐标的左上角和右下角换算成中心点加宽高的归一化坐标。这里有两个参数级别的细节必须强调。第一类别顺序绝不能随意训练时 data yaml 文件里的 names 列表顺序必须和这里 class_names 的顺序一一对应否则模型会把限速标志学成禁止标志loss 还会照常下降。第二clamp 那段边界防御不是多余代码实际数据集里经常出现标注框超出图像边界的情况不处理轻则 loss 异常跳动重则训练直接炸掉。如果你拿到的是 COCO 格式的 JSON 标注转换思路完全一样只是解析方式不同COCO 里每张图像的 bbox 是 [x, y, width, height] 的像素值和 XML 的 xmin/xmax 表示方式不一样但归一化公式不变。TT100K 的原始标注是 JSON我通常先写一个中间层把 JSON 转成 VOC 的中间格式再做上面这一步转换。中间多一层不是多此一举它能让你在切换数据集时只改一个函数而不是重写整个转换流程。2.3 数据增强做到什么程度才不“造假”交通标志识别有一类经典翻车场景白天阳光充足时模型表现优秀一转到傍晚或逆光视频立刻乱框一起。多数原因不是模型结构不行而是训练数据的光照覆盖太窄。YOLOV5 在训练时会自动做 Mosaic、HSV 扰动、平移和缩放这类基础增强这些手段对正常场景足够但对小目标交通标志还需要额外关注类别层面的干扰。Mosaic 增强值得多说一句。YOLOV5 默认把四张训练图拼成一张等于每张图里目标数量翻倍这对小目标检测效果提升明显。但如果你发现训练集里某个标志类别只有十几个样本Mosaic 帮不了它因为拼图不产生新样本只是让现有样本出现频率变高。真正的补救手段是样本过采样和 Copy-Paste把稀有类别的小图贴到其他训练图上同时修改标注坐标这种合成在交通标志上是合规的增强方式因为标志本身就是独立粘贴在道路上的物理实体合成出来的图像符合真实世界分布。增强别过头。有人为了让模型“更鲁棒”把训练图调得饱和度拉满、对比度锐化到看不清原图结果训练集 loss 降得很好验证集 mAP 也高一到真实视频全露馅。判断标准很简单增强后的图像人眼能不能辨认出标志类型。人眼都认不出来模型学到的就是噪声这叫增强造假。3. 训练自己的交通标志检测模型环境、超参数与训练闭环数据集准备停当接下来就是把 YOLOV5 的代码跑起来。这一章从环境搭建、训练命令、超参数调法到日志解读按照实际操作的顺序推进。3.1 环境搭建与依赖版本先避免三个“经典开局”第一个经典开局是直接在 base 环境里 pip install装到一半把系统 Python 环境装坏了。正确做法是先用 conda 新建一个独立环境Python 版本选 3.8 或 3.10 都行关键是和你要装的 PyTorch 版本对得上。torch 的安装命令依赖你的 CUDA 版本NVIDIA 官网和 PyTorch 官网都有对应关系表按表选命令即可不要凭记忆乱装。装完 torch 后验证一下conda create -n yolov5 python3.10 -y conda activate yolov5 pip install -r requirements.txt python -c import torch; print(torch.cuda.is_available())最后一行返回 True 说明 GPU 可用False 就是 CUDA 版本没配对老老实实回去重装 torch。第二个经典开局是 Windows 下编译某些依赖时提示缺少 C 构建工具报错信息里会带一串 MSVC 字样。解决办法是安装 Microsoft C Build Tools没有捷径。第三个经典开局是用 IDE 打开代码后解释器没切到 conda 环境训练脚本能跑但导入包时全红。在 VS Code 里按 CtrlShiftP执行“Python: Select Interpreter”选刚才建的 yolov5 环境即可。如果要用代码调试功能顺带排查问题建议启动时把断点打在 train.py 的 main 入口而不是模型定义里。3.2 数据配置 yaml 和训练命令参数怎么设才不玄学YOLOV5 训练时读的不是直接传一堆路径参数而是需要先写一个数据配置文件放在 data 目录下。这个 yaml 是整个训练的地基写错的优先级最高。下面这分是交通标志场景的一个典型配置# data/traffic_sign.yaml train: datasets/TT100K/images/train val: datasets/TT100K/images/val nc: 5 names: [speed_limit, warning, no_entry, stop, crosswalk]train 和 val 字段必须是图像所在目录的路径YOLOV5 会根据这个路径自动去相邻的 labels 目录找同名 txt 标签。如果目录结构是 resources/train/images 和 resources/train/labels那 train 字段就写 images 的路径不能写外层目录。nc 是类别数量names 是类别名列表这两个必须和转换脚本里的 class_names 完全一致顺序也不能乱。然后执行训练命令python train.py \ --data data/traffic_sign.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 100 \ --workers 8 \ --cache ram \ --device 0参数说明weights 用 yolov5s.pt 而不是随机初始化因为要在 COCO 预训练权重基础上做迁移学习模型收敛速度会快很多。img 设为 640 是精度和速度的平衡点如果显存紧张可以降到 416交通标志本身是小目标img 过小会进一步压缩目标像素数得不偿失。batch 要根据显存试先开 autobatch 跑一个 batch 试试上限再手动回填到训练命令里。workers 是数据加载线程数Windows 上有时 worker 数量过高反而卡死报错 Dataloader worker (pid) is killed by signal降到 4 或 2 即可。--cache ram 把图片缓存进内存对数据集不大但是反复读取的毕设场景能显著提速代价是占内存。如果你对超参数调整感兴趣YOLOV5 训练时自动读取的是 data/hyps/hyp.scratch-low.yaml 这个文件。里面最关键的是 lr0初始学习率、momentum动量、weight_decay权重衰减和 hsv_h/hsv_s/hsv_vHSV 增强幅度。默认学习率对大多数数据集是安全的但对标注质量不高、边界框偏小的交通标志数据集把 lr0 从 0.01 降到 0.005 可以避免 loss 在后期震荡。想要微调时用 --hyp 参数指到你的超参副本而不是去改原文件方便回溯。3.3 看懂训练日志和评估指标不只认 mAP 一个数字训练开始后每跑完一个 epoch终端会打印当前进度包括 box_loss、cls_loss、obj_loss、P、R、mAP50、mAP50-95 这几项指标。很多新手只盯着 mAP50 看等到训练结束跑到 0.9 就很高兴。这不对mAP50 只能说明“预测框和真实框的 IoU 大于 0.5 时模型表现好”而 mAP50-95 综合了从 0.5 到 0.95 多个 IoU 阈值下的表现更能反映定位精度。交通标志密集且尺寸小mAP50-95 通常比 mAP50 低不少这是正常的但如果两者差距超过 0.25就要怀疑定位框是不是偏得厉害。训练结束后runs/train/exp 目录下会生成一组关键文件train_batch0.jpg增强后的训练图检查有没有把标志增强成不可辨识的形状。labels.jpg标注框分布图确认每个类别都有样本且框的尺寸比例合理。PR_curve.pngPR 曲线曲线越贴近右上角越好。confusion_matrix.png混淆矩阵这是最有价值的诊断图。如果两个类之间互相误检说明它们的样本在外观上相似度太高需要补数据或者合并类别。val_batch_pred.jpg验证集的可视化预测结果人眼扫一遍能发现明显问题。验证集效果达标后用训练出来的 best.pt 跑一次推理python detect.py \ --weights runs/train/exp/weights/best.pt \ --source datasets/test.jpg \ --conf 0.4 \ --device 0sourece支持图片路径、视频路径也可以直接给摄像头设备号。conf 是置信度阈值0.4 是个不错的起点。如果测试图片里检测结果有大量漏框先降 conf 到 0.25 看是阈值问题还是模型问题如果降下去之后出来一堆误检框说明模型泛化不够问题出在训练阶段而不在推理参数。4. 常见问题与避坑记录从 loss 为 NaN 到实拍视频全程哑火训练 YOLOV5 的坑高度集中很多现象看似玄学根因都落在几个固定的点上。这一章按我自己踩过的顺序把最典型的四类问题拆开讲。4.1 训练到第 30 个 epochloss 突然变成 NaN现象box_loss、cls_loss 在第 30 个 epoch 前后逐渐下降突然某一步全部打印为 nan之后所有指标全部变成 nan训练进程不再恢复。原因最常见的有三种。第一学习率过大导致梯度更新在某个 batch 上溢出权重直接非数值化第二数据集里有损坏的图片或标注文件比如某一张图读到一半像素异常或者某个 txt 标注里出现 inf 坐标第三使用了不合适的混合精度训练部分老显卡在 fp16 下遇到梯度下溢也会触发 NaN。解决先把 --batch 调小一半--epochs 不变重新训练观察是否还会出现如果仍出现把 --device 0 换成 cpu排查硬件层面接下来查数据集写一个循环遍历 labels 目录凡是文件内容里出现 nan、inf 的标注直接剔除。最直接的做法是把训练命令里这两个参数加上--quad或--cache disk重新跑一次。大多数情况下高学习率是主因把 --hyp 里的 lr0 从 0.01 降到 0.005 就能稳定收敛。4.2 训练没报错loss 也下降但所有类别一个都检测不出来现象训练日志里 mAP50 一直是 0跑完推理在测试图上没有任何框或者所有框的置信度都低于 0.1。原因这是类别配置错位的典型症状。最常见的是 data yaml 里 nc 填了 80而数据集标注只有 5 个类别模型把预测头初始化为 80 类的结构标签文件里却全是 0~4 的索引另一种情况是 names 顺序和转换脚本的 class_names 顺序不一致模型学的类别和标注的类别对不上。还有一种隐蔽的标签 txt 文件里第一列是“类别名称字符串”而不是“类别索引数字”比如写着 speed_limit 而不是 0YOLOV5 解析时会直接忽略。解决先统计 labels 目录里的类别分布用一段循环统计每个 txt 的所有类别索引。然后对比 data yaml 的 nc 和 names确保一一对应nc 必须等于 names 的长度。再用上面转换脚本重新生成一次标签打开一个 txt 文件人工确认第一列是 0 开头的整数。这个坑的诡异之处在于 loss 完全可以正常下降因为数据本身不报错只是类别语义错位。4.3 白天测试集 mAP 0.92傍晚实拍视频全是误检框现象训练集、验证集全部用白天正常光照图片mAP50 高到接近 0.9。一旦拿傍晚或逆光的行车记录仪视频做演示画面里大量误检框乱跳限速标志被识别成警告标志甚至路牌、广告牌也出框。原因数据分布不匹配。训练集只有白天清晰图模型相当于记住了“白天交通标志”的纹理颜色发暗、对比度低的场景它在特征空间里找不到对应区域。YOLOV5 自带的 HSV 增强对白平衡漂移有一定覆盖但覆盖不了从日落到夜间的极端光照变化。解决从真实的傍晚和夜间行车记录仪视频里抽帧放进训练集重训这比任何数据增强都有效。另一个做法是在增强参数里把 hsv_h 从默认值上调比如从 0.015 调到 0.03让模型在训练阶段见过更多色温变化。如果项目时间不够重训也可以在推理侧做预处理先把输入帧转成 LAB 色彩空间做自适应直方图均衡化再送入模型这样能在不改权重的情况下缓解部分光照影响靠几个辅助函数即可实现。4.4 显存不够和训练速度慢是两件事现象训练时提示 CUDA out of memory或者用 batch 16 能跑但每 epoch 耗时飙升GPU 利用率只有 40%。原因前者是显存容量真的到极限了后者往往不是显存问题而是数据读取解码成了瓶颈。很多毕设项目把图片存在机械硬盘上每次训练都要重复读盘、解码、做增强GPU 在等数据自然跑不满。另一个隐藏负担是同时打开了多个 GUI 程序几 G 显存被占用也会触发 OOM。解决显存问题优先用 autobatch 自动找最大 batch 值然后把该值写死到训练命令里。数据瓶颈则用 --cache ram 打开缓存大型数据集用 --cache disk。如果都解决不了就在训练命令里按需降低分辨率把 --img 640 降到 512。实测中交通标志这种小目标在 512 分辨率下依然有可辨识的细节响应速度还能提升。5. 视频推理稳定性把模型变成答辩现场不翻车的演示系统训练完 best.pt 只是第一步大部分毕设答辩现场演示用的都是视频流而不是单张图片。但把模型接到视频上会暴露出比静态图评测更多的问题单帧闪烁、置信度抖动、同一标志连续几帧被识别成不同类别。所以这章讲一个立竿见影的稳定化技巧以及模型导出的选型思路。交通标志在连续视频帧里的位置变化是平滑的类别也不该突变。处理办法是做“帧间投票”在滑动窗口内记录前几帧的检测结果只有连续 2~3 帧在同一位置区域检测到同一类别的目标才输出最终结果。这样能滤掉大多数偶发误检。演示时把每帧的检测类别放到一个计数数组里超过阈值再画框代码量不大但观感提升非常明显。如果答辩老师问“这个模型能不能换到嵌入式设备上跑”答案是肯定的但要注意导出的格式选择。YOLOV5 自带的 export.py 可以导出 TorchScript、ONNX 和 TensorRT 引擎。TorchScript 部署最省心兼容性好ONNX 适合用 OpenVINO 加速 CPU 推理TensorRT 在 NVIDIA 显卡上跑得最快但只支持特定显卡架构换设备后要重新导出。我自己的习惯是训练完先导出 ONNX 格式因为它在树莓派、Jetson 上都有成熟运行时部署边界最宽。导出命令python export.py \ --weights best.pt \ --include onnx \ --simplify \ --opset 12导出完成后用 onnxruntime 做一次离线推理验证对比脚本检测结果是否一致。这个环节很多人会忽略直接拿去部署结果在导出时精度丢失。用上面的命令导出后跑同样的测试图、同样的 conf 阈值确保 mAP 曲线基本重合再放行。答辩演示的最后一关是阈值选择。对交通标志这类小目标不要静态跑 conf 0.4先把阈值从 0.25 调到 0.45对比实拍视频的漏检率和误检率取一个平衡点。如果视频里误检多优先提高阈值如果漏检多则要回头补数据而不是盲目降阈值。这套方法的逻辑是模型能力在训练完成时已固定推理侧能做的是尽量不掩盖训练阶段的问题。做毕设最怕的是模型在答辩现场对着实拍视频全程哑火提前把验证流程走到这一步基本能和它说再见了。希望帮到你。本文还有配套的精品资源点击获取