基于YOLOv5与TT100K的交通标志识别:从数据预处理到模型部署全流程实践
简介本资源是一个基于YOLOv5实现的交通标志牌识别高分项目面向人工智能、自动化、电子信息等专业的在校学生、教师及工程技术人员适用于毕业设计、课程设计、竞赛原型开发与深度学习入门实践。项目完整复现了TT100K数据集上的检测流程涵盖数据预处理、模型训练、验证评估与推理部署全流程代码经实测可直接运行配套文档详述环境配置、参数调优与结果分析方法。压缩包共149个文件包含56个Python主程序与工具脚本、49个YAML/YML配置文件定义模型结构、训练超参与数据路径、6个Shell部署脚本、6个Markdown说明文档以及Dockerfile含CPU/ARM64多平台支持、.gitignore等工程化文件整体仅1.12MB轻量易部署。已有79人下载学习提供从零复现到二次开发的完整支撑特别适合需要快速构建CV落地案例的学习者与项目开发者。 说实话这类“基于yolov5的交通标志牌识别项目”我见得太多但从标题就能看出它不是一个随便跑跑demo的玩具项目而是一个能真正落地、拿去交作业甚至写进简历的完整工程。yolov5作为目标检测里上手最快、生态最成熟的一套代码库配合TT100K这个专门面向中国交通场景的数据集组合起来的项目价值非常高——无论是毕业设计、课程设计还是入门实际工程这条路线都值得认真走一遍。我这次就结合自己做过的实践把这个项目从环境搭建、数据转换、模型训练到推理部署的完整链路全部拆开来讲清楚顺带把那些文档里不会写、但实操时一定会踩的坑也一并交代了。1. 项目整体设计与思路拆解1.1 为什么选yolov5做交通标志识别选yolov5不是因为它最先进而是因为它最“够用”。交通标志检测本质上属于中小尺寸目标检测任务标志牌在画面中往往只占几十到几百像素对模型的小目标感知能力有一定要求。yolov5提供了s/m/l/x四种规格s模型只有7M左右参数在CPU上都能跑出可用的速度同时精度又不至于拉胯这对学生项目和嵌入式部署都非常友好。更关键的是yolov5的生态成熟度。从数据标注格式到训练脚本从模型量化到TensorRT、ONNX导出全套工具链都齐了。这意味着你可以把80%的精力花在数据和调参上而不是去啃一个自研框架的源码。对大多数做毕设或入门工程的人来说这个选择是性价比最高的。yolov5本身是一个单阶段目标检测算法它把目标检测问题拆成了“在特征图上做密集回归”这件事。简单理解就是输入一张图网络同时预测出大量候选框的位置和类别然后通过NMS非极大值抑制去掉重复的框留下最终结果。相比两阶段的Faster R-CNNyolov5速度更快结构更简洁这也是它在工业界被广泛使用的原因。1.2 TT100K数据集的选型理由TT100K是腾讯发布的交通标志数据集全称是Tencent Traffic-Sign 100K包含10万张街景图像其中标注了3万多个交通标志实例覆盖5个大类、45个小类包括禁令、警告、指示、指路等类型。这个数据集最大的特点是“中国本土化”——里面的标志都是国内道路上真实出现的样式比如“限速40”“禁止通行”“注意行人”这些跟国外数据集里的标志风格差异很大。如果你用国外的GTSRB或者LISA数据集训练模型拿到国内路况上测试效果往往会打折扣因为标志的配色、形状、文字排版都有差异。TT100K直接就是国内的真实街景数据贴合度很高这也是毕设和竞赛里它被大量使用的原因。不过要提醒的是TT100K虽然名义上有10万张图但有标注信息的实际只有约1万张而且这1万张也不是全部都能直接用。光照变化、遮挡、小目标、类别不均衡这些问题在TT100K里都很明显做数据处理时需要花不少功夫去清洗和筛选。这部分我会在第三章详细讲。1.3 项目模块划分与资料包结构一个完整的yolov5交通标志识别项目通常包含五个核心模块环境与依赖、数据准备、模型训练、推理验证、文档资料。我在整理自己的项目时目录结构大概是这样的yolov5/核心检测代码库官方源码可保持原样data/TT100K/数据集存放目录包括images和labelstools/自写的脚本主要是标注格式转换、数据集划分、类别统计runs/训练日志、权重文件、检测结果图docs/项目文档、实验报告、答辩PPT素材这样的组织方式最大的好处是职责清晰。官方代码库保持原样后续拉新版本不冲突自己写的工具脚本单独放方便复用训练产出统一进runs目录出问题排查也方便。很多同学喜欢把所有东西堆在一个文件夹里结果训练到一半想找某个脚本都费劲这种坏习惯最好一开始就改掉。2. 环境搭建yolov5安装与依赖配套2.1 yolov5源码获取与环境要求yolov5的安装流程本身不复杂但版本兼容问题很容易让人头大。我自己的建议是不要用最新版直接用官方v6.0或v7.0版本的稳定分支因为网上大多数教程、预训练权重和踩坑经验都是基于这些版本积累的能帮你节省大量排错时间。下载源码的方式可以直接从GitHub上clone也可以下载zip包本地解压。clone的好处是后续可以随时git pull拉更新但对毕设项目来说意义不大。环境方面Python版本建议3.8到3.11之间。PyTorch的安装要和你的CUDA版本对应这一点特别关键。一般来说如果你的显卡是NVIDIA的建议先装CUDA 11.8以上然后安装对应版本的PyTorch。GPU环境不行的话CPU版本也能跑只是训练速度会慢很多推理勉强能用。这里要顺带说一句很多人会忽略requirements.txt里各依赖库的版本关系。yolov5对OpenCV、numpy、matplotlib这些库的版本敏感度其实不高但torch和torchvision的版本一定要严格匹配否则会出现_C导入报错之类的问题。2.2 安装步骤与依赖验证安装的时候我习惯分两步走。第一步先创建独立的Python虚拟环境避免污染系统环境这一步在conda下就是一行命令的事conda create -n yolov5 python3.9 -y conda activate yolov5第二步进入yolov5目录安装项目依赖cd yolov5 pip install -r requirements.txt如果CPU版本的PyTorch已经安装好了这一步只需要装剩下的依赖。如果是GPU环境建议先手动安装好对应版本的PyTorch再执行pip install -r requirements.txt避免依赖自动装上CPU版的torch。装完之后验证安装是否成功最快的方式是跑一次官方自带的检测demopython detect.py --weights yolov5s.pt --source data/images/bus.jpg第一次运行会自动下载yolov5s.pt预训练权重如果能看到输出图像里检测出的公交车和人说明整个环境就打通了。我习惯跑通这个demo之后再开始下一步因为后续的训练、推理都依赖这个基础环境的正确性早验证比晚验证省事得多。2.3 硬件配置建议与训练规划关于硬件配置我的建议非常朴素有NVIDIA显卡就用GPU显存8G以上可以比较舒服地训练yolov5s模型12G以上能尝试yolov5m或更大的模型。没有显卡的话用CPU训练也不是不行但要做好心理准备——同样的数据集GPU跑两个小时CPU可能要跑两天。如果条件比较受限有一个妥协方案直接用官方预训练权重yolov5s.pt做迁移学习只冻结backbone层、训练检测头这样显存和训练时间都能大幅下降。另一个方案是用GitHub Actions或者云GPU平台跑训练不过对新手来说门槛略高这里不展开。3. TT100K数据准备从原始标注到yolo格式3.1 TT100K数据格式解析TT100K的原始标注是JSON格式这是整个项目里最容易卡住人的一个环节。它的目录结构通常是这样的train/训练图片test/测试图片train.json训练标注文件val.json验证标注文件annotations.json所有标注文件其中train.json的结构大致是{ imgs: { 0.jpg: { height: 2048, width: 2048, objects: [ { bbox: [x1, y1, x2, y2], category: i100, id: 1 } ] } } }注意这里的bbox是[左上角x, 左上角y, 右下角x, 右下角y]的格式跟yolov5要求的[中心点x, 中心点y, 框宽, 框高]不一样而且数值是像素坐标没有归一化。如果直接把原始标注拿来训练模型会直接学崩。所以数据准备的第一步就是写一个脚本把JSON转成yolov5能识别的TXT格式。3.2 标注转换脚本的编写要点转换脚本的核心逻辑其实不复杂就是把每一张图片的标注信息从JSON里读出来计算归一化坐标然后写入到与图片同名的TXT文件中。yolov5对TXT格式的约定是每行代表一个目标格式为类别id 中心点x 中心点y 框宽 框高所有坐标值都是归一化到0到1之间的浮点数。我写的转换脚本大概长这样import json import os def convert_tt100k_to_yolo(ann_file, image_dir, save_dir, class_names): with open(ann_file, r) as f: data json.load(f) for img_name, img_ann in data[imgs].items(): height img_ann[height] width img_ann[width] image_id img_name.split(.)[0] txt_path os.path.join(save_dir, image_id .txt) lines [] for obj in img_ann[objects]: category obj[category] if category not in class_names: continue x1, y1, x2, y2 obj[bbox] cx ((x1 x2) / 2) / width cy ((y1 y2) / 2) / height w (x2 - x1) / width h (y2 - y1) / height class_id class_names.index(category) lines.append(f{class_id} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) if lines: with open(txt_path, w) as f: f.write(\n.join(lines)) if __name__ __main__: class_names [i100, i110, p100, p130, w100] # 自定义类别列表 convert_tt100k_to_yolo(train.json, train, labels/train, class_names)这个脚本里有几个细节值得注意。第一class_names的顺序决定了类别id的映射关系一旦确定了就不能轻易改动否则训练好的模型类别会全乱。第二如果一张图片里所有目标都不在选定的类别列表里这张图片和它的TXT文件都会被跳过后续训练时要注意图片和标签的对应关系。第三原图是2048×2048的大图但yolov5训练时会resize到640×640或1280×1280小目标的标注在resize后可能只剩几个像素这对检测精度影响很大后面会专门讲怎么处理。3.3 数据集划分与类别筛选策略TT100K的官方划分是训练集和验证集但实际做训练时我发现官方验证集的图片数量和标注质量参差不齐建议自己重新做一次划分。我的做法是把有标注的图片全部汇总按照8:1:1的比例随机划分训练集、验证集、测试集并且保证同一条路段上的图片尽量落在同一个集合里避免数据泄漏导致评估指标虚高。类别筛选方面TT100K全量45个类别里相当一部分类别的标注数量只有几十个属于典型的长尾分布。如果全类别训练模型会对样本量大的类别过拟合对样本量小的类别基本学不到特征。我的建议是优先保留标注数量最多的前20到30个类别比如限速标志、禁止通行、注意行人这些高频类别这样训练出来的模型在实际场景中更有实用价值。具体筛选时可以先写个脚本统计每个类别的实例数量画个柱状图看看分布再决定保留哪些类别。这一步虽然耗时但直接决定了模型最终能学成什么样值得花时间去做。4. 模型训练与超参数调优4.1 数据集配置文件TT100K.yaml编写yolov5训练前需要准备一个YAML配置文件告诉训练脚本去哪里读数据、有多少类别、类别名叫什么。这个文件是训练的入口写错一个路径或参数训练就会报错或者跑出一个完全无效的模型。我常用的配置文件模板# TT100K.yaml train: data/TT100K/images/train val: data/TT100K/images/val nc: 20 names: [i100, i110, i120, p100, p130, p150, w100, ...]注意这里train和val指向的是图片目录yolov5会自动在同级目录下找labels文件夹里的TXT标注文件。所以标准的目录结构是data/TT100K/ ├── images/ │ ├── train/ │ └── val/ └── labels/ ├── train/ └── val/如果你的数据集图片和标签不在这个标准结构下一定记得在YAML里写对路径或者用软链接把它们组织好。很多同学训练时提示found no labels90%的情况下都是因为这个目录结构对不上。4.2 训练命令与关键超参数解析我在训练时用的命令大概是这样的python train.py \ --data TT100K.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 200 \ --device 0 \ --name tt100k_train逐个参数解释一下--data指定数据集配置--weights指定预训练权重--img指定训练图片尺寸--batch是批大小--epochs是训练轮数--device 0是用第一块GPU--name是给这次训练起个名字方便后续在runs/train/目录下区分不同的实验。超参数里最值得花心思的是--img和--batch。--img默认是640对TT100K这种小目标较多的数据集我建议至少提到1280这样小标志的细节能保留得更好代价是训练时间几乎翻倍、显存占用翻倍。--batch受显存限制24G显存跑yolov5s的1280分辨率batch大概能开到8到16如果你的显存不够可以尝试用--batch 4配合--noplots减少显存占用或者干脆用yolov5n这个更小的模型。还有一个很多人忽略的参数是--cache加上这个参数会提前把图片缓存到内存里大幅减少训练时读磁盘的开销。如果你的数据集不是特别大强烈建议加上实测能提速30%以上。4.3 训练过程监控与结果分析训练一旦跑起来yolov5会自动在runs/train/tt100k_train/目录下记录日志和评估结果。我习惯每隔几十个epoch就打开终端看一下输出重点看三个指标的变化趋势box_loss、obj_loss、cls_loss。这三个损失值应该逐步下降如果某个损失值震荡剧烈或者长时间不降说明学习率设置不合适或者数据标注有问题。训练完成后weights/best.pt和weights/last.pt两个文件会自动保存。best.pt是验证集上mAP最高的权重last.pt是训练结束时的权重一般做推理和部署都用best.pt。另外results.png这张图非常关键它汇总了训练过程中所有损失和指标的变化曲线包括mAP_0.5、mAP_0.5:0.95、精确率、召回率等。如果你的mAP_0.5能到0.8以上说明模型已经相当不错了放到实际街景里检测常见标志基本够用。5. 推理实践与工程化部署5.1 图片、视频与实时检测模型训练完成后最直观的验证方式就是用detect.py脚本跑推理。测试单张图片python detect.py \ --weights runs/train/tt100k_train/weights/best.pt \ --source test.jpg \ --conf 0.4 \ --device 0--conf参数控制置信度阈值默认0.25。交通标志检测建议调到0.4到0.5之间因为标志牌的纹理相对简单低置信度的误检会比较明显。--source参数非常灵活可以传图片路径、视频文件路径甚至摄像头设备号比如--source 0就是用第一个摄像头做实时检测。实际测试的时候我建议你去找几张TT100K测试集之外的街景图最好是不同光线、不同天气条件下的这样可以更客观地评估模型的泛化能力。如果你发现自己训练的数据集上效果很猛换到真实场景效果暴跌那大概率是过拟合了需要回炉做数据增强或者正则化。5.2 模型导出与嵌入式端部署方向yolov5最让我满意的一点就是模型导出的便利性。训练好的best.pt可以直接导出成ONNX格式python export.py \ --weights runs/train/tt100k_train/weights/best.pt \ --include onnx \ --opset 12导出ONNX之后就可以继续转换成各种推理引擎需要的格式。如果是做边缘端部署现在很多同学会把它部署到瑞芯微RK3568/RV1106这类带NPU的板子上流程基本是PyTorch权重 - ONNX - RKNN格式然后调用RKNN的Python SDK做推理。这个过程里最常遇到的问题就是某些算子不兼容需要回头调整模型结构或者换一个更简单的模型变体。如果你的项目只是毕设或者演示用完全不需要走到嵌入式部署这一步用detect.py做离线推理已经足够展示效果。但如果你想让项目在简历上有更大的亮点部署到开发板甚至做一个Demo小应用绝对是个加分项。6. 常见问题与排查技巧实录6.1 显存不足与训练中断显存不足CUDA out of memory是训练yolov5最常见的报错之一我几乎每次带新人都会遇到这个问题。解决策略从优到劣排列尝试降低--batch从16降到8再降到4直到能正常跑起来同时降低--img分辨率从1280降到640显存占用大概能降一半以上如果在1280分辨率下训练小目标太吃力建议换用多头注意力更好的yolov5m模型而不是强行上大图如果是长时间训练中断恢复训练也很简单找到之前训练保存的last.pt用--resume参数恢复即可python train.py --resume runs/train/tt100k_train/weights/last.pt6.2 精度低、不收敛怎么办模型训练出来mAP只有0.3或者损失值震荡不降这是我被问得最多的问题。排查思路可以参考下面这张速查表症状可能原因解决方案损失值降不下去学习率过大或过小检查训练日志里的lr尝试降低到0.001或使用warmupmAP很低但损失正常类别映射错误或标注坐标错误验证TXT标签内容和图片目标的对应关系小目标完全检测不到训练分辨率太低把--img提升到1280或使用SAHI切片推理训练集精度高、验证集低过拟合增加数据增强尝试--hyp自带的增强配置或降低训练轮数换场景效果暴跌数据集过拟合增加TT100K之外的补充数据或者只保留通用性强的类别这里要特别说一下很多同学把标注格式转换脚本写完没有做任何可视化校验直接训练结果训练完才发现类别框全部偏离目标。我的建议是转换脚本写完之后一定要用OpenCV或yolov5自带的utils/plots.py把标注框画出来肉眼检查几组图片确认没问题再开始训练这个习惯能帮你省下一天的时间。6.3 标注转换与数据对齐的坑TT100K还有一个比较隐蔽的坑原始图片文件名和JSON里的imgs键名可能存在前缀不匹配的问题比如文件名是0.jpg但JSON里是0。如果这种不一致没有处理好转换脚本生成的标签就会张冠李戴。我在转换的时候会先打印几个样例确认键名格式再写正式的转换逻辑。还有一个问题是图片和标签文件的数量要对齐。yolov5训练时如果一张图片没有对应的TXT标签文件它会被直接跳过。如果你发现训练日志里参与训练的图片数量比总数少很多就去检查是不是标签文件名和图片名没对上。文件名前缀不一致是很常见的原因。6.4 资料包文档组织建议既然是“源码详细文档全部资料”的形态文档质量往往比代码本身更影响评估结果。我自己写毕设类文档的习惯是至少包含六个部分项目背景与需求分析、技术方案与整体设计、环境搭建与数据准备、模型训练与调优过程、实验对比与结果分析、总结与展望。其中“实验对比”部分是拉开差距的关键。哪怕你只跑了一组yolov5s的实验也可以在文档里加入不同置信度阈值下的检测效果对比、不同天气场景下的检测效果分析、常见误检漏检案例分析。这些内容能充分展示你的工程素养也方便答辩时应对老师提问。数据资料方面建议把数据集样本、标注可视化结果、训练过程截图、检测效果视频整理成独立的附件目录这些“全部资料”在展示和后续复现时都很加分。写在最后的一点体会这个项目我最初做的时候光是在TT100K标注转换和数据清洗上就花了两天时间训练调试又花了两天真正跑通时那种豁然开朗的感觉到现在还记得。回头来看最大的心得有三条数据质量比模型结构更重要任何时候先可视化确认标注不出错再训练版本锁定很重要yolov5的代码、PyTorch版本、预训练权重三者匹配能省掉大量排错成本不要迷恋大模型yolov5s配合1280分辨率在交通标志识别任务上已经足够惊艳先把效果打通比追求SOTA指标有意义得多。如果你拿到类似的资料包建议不要只停留在跑通Demo而要按照本文的流程把数据转换、训练、评估、部署的每一个环节都理解透彻再试着改一改超参数、换一换模型规模看看效果差异。做到这一步你掌握的就不只是一个项目而是一整套可复用的目标检测工程方法论。本文还有配套的精品资源点击获取