ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

YOLOv5交通标志检测实战:从数据转换到模型部署全流程

YOLOv5交通标志检测实战:从数据转换到模型部署全流程 简介面向计算机视觉课程设计与期末实战的YOLOv5交通标志检测完整资源包适合需要从零跑通深度学习检测项目的高校学生与自学者。针对真实道路场景中的交通标志目标提供可复现的检测模型、全部数据与完整训练链路可作为课程设计、毕业课题或项目实战练习的参照。压缩包共266个文件约423MB主要包含pt模型权重文件、yaml数据与模型配置、Python训练推理脚本、jpg/png图片标注样例等同时附带Dockerfile、CSV训练结果与事件日志便于复现实验与扩展调参。已有362人学习下载项目源自作者期末97分作业代码经过严格调试解压后即可按说明直接运行从数据集组织、模型训练到检测效果输出均有对应文件覆盖非常适合快速产出课程汇报或作为入门YOLO实战的完整基线。1. YOLOv5交通标志物检测项目包里有什么拿到压缩包先做三件事这个场景我见过太多次拿到一份“YOLOv5交通标志物检测源码训练好的模型全部数据”的压缩包第一反应是解压、跑 train.py、等着看结果然后被一堆依赖报错和数据格式问题劝退。这个包的本质是三件套——能改的源码、能直接用的权重、能重训的数据集它的价值不在于跑通一次 demo而在于让你在最短时间内把交通标志物检测这件事从零复现到可用状态。适合课程设计、毕业设计也适合想在边缘设备上做技术试点验证的人。我拿到这类包永远先做三件事看目录结构、确认数据标注格式、确认模型推理入口。这三件事做完这个包能不能用、该怎么改基本就有数了。下面按一套标准流程把每一步讲透。2. 交通标志数据整理从 TT100K/VOC/COCO 到 YOLO 格式的转换2.1 交通标志数据集的三种常见形态交通标志检测的数据集最经典的是 TT100K腾讯与清华合作采集的真实路况标志数据特点是类别多、标志在画面中占比小、同一张图里经常出现多个目标。课程项目和开源项目里数据通常以三种格式之一分发格式标注载体坐标体系YOLOv5 是否直接支持VOC XML每张图一个 xmlobject 节点下是类别名和 bndbox像素坐标否需转换COCO JSON一个 annotations.json包含 images 和 annotations 数组bbox 为 [x, y, w, h]像素坐标否需转换YOLO txt每张图一个 txt每行一个目标格式为 class cx cy w h归一化0~1是YOLOv5 原生只认第三种格式。所以拿到手的第一步不是急着改网络而是先把数据统一成 YOLO txt。转换的核心逻辑只有四步把类别名映射成整数 id、把左上角右下角坐标算成中心点坐标、把像素值除以图片宽高做归一化、把结果写进同名 txt。TT100K 的类别命名是像 i5、p30 这种内部编码转的时候先映射成可读的名字再转 id别直接把编码当类别名用否则后面分析结果时你根本看不懂哪个类是哪个。2.2 VOC XML 转 YOLO txt一段够用的转换脚本下面这段脚本可以处理绝大多数 VOC 格式的交通标志数据我一般会先在项目里跑一遍转换再抽查三五个文件确认坐标没有飘移。import xml.etree.ElementTree as ET import os # 类别名 - 整数id 的映射顺序必须和后面 data.yaml 的 names 完全一致 CLASS_MAP {speed_40: 0, speed_60: 1, stop: 2, no_entry: 3, warning_60: 4} def convert_voc(xml_path, out_dir, classes_map): tree ET.parse(xml_path) root tree.getroot() img_w float(root.find(size/width).text) img_h float(root.find(size/height).text) lines [] for obj in root.findall(object): name obj.find(name).text if name not in classes_map: 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) 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{classes_map[name]} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) if lines: txt_name os.path.splitext(os.path.basename(xml_path))[0] .txt with open(os.path.join(out_dir, txt_name), w) as f: f.write(\n.join(lines)) xml_dir Annotations # 原始 xml 所在目录 out_dir labels/train # 输出目录 os.makedirs(out_dir, exist_okTrue) for xml_file in os.listdir(xml_dir): if xml_file.endswith(.xml): convert_voc(os.path.join(xml_dir, xml_file), out_dir, CLASS_MAP)脚本的逻辑说明解析 XML 后先取 size 节点下的 width 和 height这是原图尺寸归一化必须用它做分母而不是用某个 bbox 的宽度。每个 object 节点代表一个目标bndbox 给出的是左上角 x1/y1 和右下角 x2/y2中心点坐标就是两端相加除以二再做归一化。数值保留 6 位小数已经足够训练时 YOLOv5 会自动做边框回归坐标精度不会成为瓶颈。需要注意两个参数CLASS_MAP 的 key 一定是你 XML 里 name 字段的真实文本value 是训练时要用的整数 id从 0 开始连续编号out_dir 要和图片目录严格配对YOLOv5 训练时是把图片路径中的 images 替换成 labels 来找标签文件的配对错了会有一批图片被当成无标签样本。如果数据里有 COCO 格式思路完全一样只是读取方式换成 json.load注意 COCO 的 bbox 本身就是 [x, y, w, h]中心点要自己加一半宽高而且 COCO 的类别 id 通常从 1 开始转 YOLO 时记得减 1。2.3 数据划分与目录结构YOLOv5 的硬约定数据转换完接下来是目录结构。YOLOv5 对数据存放有一套约定违反这套约定训练时不会报错但会静默地跳过标签非常难受。约定是这样dataset/ images/train/xxx.jpg images/val/xxx.jpg images/test/xxx.jpg labels/train/xxx.txt labels/val/xxx.txt图片和标签的名字必须完全一致只是扩展名不同。YOLOv5 找标签的规则是把图片路径中的 images 替换成 labels把 .jpg/.png 替换成 .txt。如果你把标签放在 labels/all 而不是 labels/train训练时它找不到标签这张图就变成纯背景负样本模型会被带偏val 指标看起来正常但实际一塌糊涂。划分脚本我用的是一段随机划分加文件迁移核心点是要固定随机种子保证每次划分结果一致方便复现import random, os, shutil random.seed(42) imgs os.listdir(images/all) random.shuffle(imgs) n len(imgs) train_imgs imgs[:int(n * 0.8)] val_imgs imgs[int(n * 0.8):int(n * 0.9)] test_imgs imgs[int(n * 0.9):] for split, split_imgs in [(train, train_imgs), (val, val_imgs), (test, test_imgs)]: os.makedirs(fimages/{split}, exist_okTrue) os.makedirs(flabels/{split}, exist_okTrue) for img in split_imgs: base os.path.splitext(img)[0] shutil.move(os.path.join(images/all, img), os.path.join(fimages/{split}, img)) label_path os.path.join(labels/all, base .txt) if os.path.exists(label_path): shutil.move(label_path, os.path.join(flabels/{split}, base .txt))这段脚本的说明先读 images/all 下所有图片按 8:1:1 切成训练、验证、测试三份。shutil.move 会把图片和同名 txt 一起搬走。用 random.seed(42) 固定随机序列第二次跑不会因为打乱顺序不同而得出差一个量级的指标。test 目录建议只放图片不放标签后面推理验证时直接对整个目录跑 detect.py相当于一次盲测更接近真实使用场景。划分完后最好写几行命令核对一下 labels 数量与 images 数量是否一致少了就是有图片没有对应标签要回头查原数据。3. 环境配置与现成模型推理把 best.pt 跑起来3.1 conda 建环境与依赖安装YOLOv5 环境配置是新手翻车第一站。常见情况是python 版本太高导致某些依赖编译失败或者 torch 版本和本机 CUDA 驱动不匹配导致装了也调不起 GPU。我一般用 conda 单独建一个环境不碰系统自带 python。conda create -n yolov5 python3.8 -y conda activate yolov5 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install -r requirements.txt参数说明python 3.8 是兼容性最好的版本旧版 YOLOv5 代码在高版本 python 上容易出现 numpy 版本冲突torch 的 cu118 表示 CUDA 11.8 版本如果你本机驱动是 CUDA 12 以上可以换成 cu121但要先执行 nvidia-smi 确认驱动支持的 CUDA 版本否则装了也会提示 CUDA unavailable。纯 CPU 环境就把第二行的 --index-url 去掉pip 会自动装 CPU 版。requirements.txt 装不动的时候优先换国内镜像源例如在 pip 后加 -i https://pypi.tuna.tsinghua.edu.cn/simple不要硬等超时。3.2 用 detect.py 跑通第一张图环境就绪后先用训练好的 best.pt 做一次推理验证。这一步的价值在于确认权重文件本身没坏、图像预处理链路正常。命令如下python detect.py \ --weights runs/train/exp/weights/best.pt \ --source data/images/road_01.jpg \ --img 640 \ --conf-thres 0.4 \ --iou-thres 0.45 \ --save-txt --save-conf参数说明--source 接受图片路径、目录、视频文件或摄像头设备号目录或视频会逐帧处理--img 是推理分辨率交通标志属于小目标检测推理分辨率不要低于训练时设置的分辨率否则远处小标志会直接丢失--conf-thres 置信度阈值0.4 适合 demo 演示减少误报实际调优阶段建议降到 0.25多放出来一些候选框再判断--iou-thres 是 NMS 的 IoU 阈值默认 0.45 一般不用动--save-txt 会把每个框的类别和归一化坐标存成 txt后面做结果分析可以直接读文件不用重新标注。推理结果会写到 runs/detect/exp 目录打开那张可视化结果图后重点看两个地方第一标志类别名是否正确如果框的位置对但类别全是乱的不用怀疑是数据转换时 CLASS_MAP 和 names 顺序错位了第二近处的大标志框得很好但远处小标志全漏了说明当前模型对这个分辨率下的场景不适应后面需要走第 4 章的调优路线。3.3 低显存运行模型的三个手段很多人的机器显存只有 6G 甚至 4G运行这个项目会遇到显存不足。低显存运行模型不是玄学无非三板斧降 batch、降分辨率、用半精度。第一板斧是加 --half 参数把推理精度从 FP32 降到 FP16显存占用直接减半速度反而变快代价是可能损失 1 到 2 个点的精度对交通标志检测这个场景完全可接受。第二板斧是把 --img 从 1280 降到 640显存占用按面积比例降四倍。第三板斧是换小权重项目里如果带了多种模型文件优先用体积最小的那个做验证。CPU 环境就加 --device cpu跑一张图可能两三秒但流程能通。3.4 不只信 mAP按类别检查 AP 分布跑通推理后要用 val.py 对验证集做一次完整评估。只看总 mAP 会被骗交通标志数据经常是几十个类别某个高频类别把平均值拉得很高少数低频类别全是漏检。python val.py --weights runs/train/exp/weights/best.pt \ --data data/traffic_sign.yaml \ --task val输出结果里有一张 P_curve.png 和每种类别的 AP 数值。正确做法是把 AP 最低的几个类别记下来打开它们的可视化预测图观察是漏检多还是误检多。漏检多说明这类标志在数据里样本太少或目标太小误检多说明容易把它和相似标志弄混。这一步做完你才知道第 4 章调参应该往哪个方向使劲而不是盲目调 confidence 阈值。4. 用全部数据重新训练data.yaml 与关键参数设置4.1 写 data.yaml路径与 names 顺序决定成败把手头数据整理好之后下一步就是 yolov5 训练自己的数据集。训练入口是 train.py它首先读取 data.yaml 配置文件。这个文件极简单但十个报错里有八个出在它身上。# data/traffic_sign.yaml train: /home/user/dataset/images/train val: /home/user/dataset/images/val nc: 5 names: [speed_40, speed_60, stop, no_entry, warning_60]参数说明train 和 val 写绝对路径最省心相对路径要以项目根目录为基准。路径一律用正斜杠不要写 d:\data 这种带反斜杠的 Windows 写法YAML 解析时反斜杠是转义符路径会被截断。nc 是类别总数必须和 names 列表长度一致。最关键的约束是names 的顺序就是标签 txt 里第一列数字的顺序。你在转换脚本 CLASS_MAP 里把 stop 定义成 id2names 列表里下标为 2 的就必须是 stop。顺序错位时训练不会报错模型最终学到的类别和名字完全错位推理结果看起来像神经病一样乱猜。4.2 train.py 训练命令与五个必调参数目录结构、data.yaml 都确认无误后开始训练。命令是这样的python train.py \ --data data/traffic_sign.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 100 \ --hyp hyp.scratch-low.yaml参数说明--weights 用官方 COCO 预训练权重 yolov5s.pt 做迁移学习比从零训练收敛快得多这个文件从 YOLOv5 官方仓库下载后放在项目根目录即可不需要额外配置--img 训练输入分辨率交通标志目标小常见做法是直接拉到 640 起步显存够就上 1280--batch 由显存决定16G 显存用 16 到 328G 用 8 到 16再低就得配合梯度累积--epochs 100 在交通标志这个规模的数据集上基本够用训练日志里 mAP 曲线如果最后还在涨就加 50 轮--hyp 是 yolov5 超参数文件路径新手优先用 hyp.scratch-low.yaml它的学习率和增强强度比较保守不容易训练发散。有一个细节值得注意训练第一个 epoch 时控制台会输出每张图的标签统计和各类别目标数。如果这里显示某个类别目标数为 0或者大量图片显示 no labels立刻 CtrlC 停下来去查数据不要等训练完。训练一旦带着错误标签跑完后面每一步都是在错误基础上叠加返工成本非常高。4.3 交通标志小目标检测调优的四个出手点训练能跑通只是开始交通标志检测的难点集中在“小目标”上。画面里一个 40 像素高的限速牌在 640 分辨率下经过 5 次下采样到特征图上只剩 1 到 2 个像素网络几乎看不见它。调优按顺序做这四个手段第一是提高输入分辨率。把 --img 从 640 提到 1280小目标的召回率通常有肉眼可见的提升代价是显存和时间都涨到三到四倍。显存不够的折中方案是把原图切片成两块或四块重叠区域分别推理再做 NMS 合并这个 trick 在小目标检测里非常常见。第二是调整 mosaic 增强。YOLOv5 默认开启 mosaic它把四张图拼成一张训练对模型鲁棒性有帮助但最后十几个 epoch 建议关掉因为 mosaic 合成的图分布和真实单图推理场景有偏差不关会导致验证集指标高、实际跑视频时抖动。train.py 里设置 --close-mosaic 10 表示最后 10 个 epoch 关闭。第三是检查自动 anchor。YOLOv5 训练时默认自动 k-means 聚类生成 anchor看训练日志里 autoanchor 输出对比 anchor 尺度和你的目标框分布是否匹配。交通标志整体偏小如果聚类出来的 anchor 宽高都在 20 像素以上说明数据里大目标太多需要检查数据标注是否有把背景框进去的脏数据。第四是处理类别不平衡。交通标志数据里 stop 标志可能几百张warning 标志可能就几十张。统计各类别样本数之后对数量少的类别做重复采样或者简单粗暴地复制几份小类图片放进训练集。损失函数层面可以给少样本类别加权但工程上先做数据层面的平衡效果最直接。5. 交通标志检测避坑指南五个常见的翻车现场5.1 类别编号错位导致“名字全乱”现象训练完 val mAP 看起来正常但推理可视化时停车标志被识别成限速 60限速 40 被识别成警告框的位置都对类别全乱。原因VOC 转 YOLO txt 时 CLASS_MAP 的 id 顺序和 data.yaml 里的 names 顺序不一致。YOLOv5 训练时只认整数 id不检查名字模型学到的语义和你以为的语义对不上。解决训练前写一行命令抽查标签文件head -20 labels/train/xxx.txt把每行第一列数字按 names 下标翻译回名字逐条比对是否合理。更稳妥的做法是转换脚本直接读取 data.yaml 的 names 列表去生成 CLASS_MAP两处只维护一份定义。5.2 训练时大量标签找不到现象训练日志出现大量警告提示 no labels 或 ignoring corrupt image/label或者第一个 epoch 的 label 统计里目标数异常少。原因图片在 images/train标签却放在 labels/all或者 txt 文件名和 jpg 名不一致比如某次批量拷贝产生了 .jpg. 后缀。还有一个隐蔽原因是 Windows 下 txt 的换行符问题偶尔会导致 YOLOv5 读标签失败。解决先核对目录结构是否符合 images 与 labels 的同名配对。再写三行脚本统计没有标签的图片数量。最后如果确认标签没问题把标签 txt 用 sed -i s/\r// 去掉 Windows 换行符或者用 dos2unix 命令批量处理。这类问题十次里有八次是路径写错。5.3 小目标漏检但大目标正常现象近处的大标志框得很好远处小标志全漏掉降低 confidence 阈值也救不回来。原因640 输入分辨率下远处标志只有 15×15 像素经过 YOLOv5 的 32 倍下采样特征图上连一个像素都不到网络物理上看不见这个目标。解决首选把推理和训练的 --img 提升到 1280小目标召回率提升最明显。显存受限就做切图推理把原图切成两块或四块每块单独推理再合并结果。anchor 方面检查 autoanchor 聚类结果如果聚类出来的 anchor 都偏大说明小目标占比低数据层面要补充带小目标的样本。5.4 验证集 mAP 虚高换一段视频全垮现象val 集 mAP 0.93看起来非常好但放一段没见过的路段视频漏检一半以上。原因数据划分用了随机抽样同一辆车经过同一路口时拍摄的连续帧一部分进了 train 一部分进了 val。网络相当于提前“背过”验证场景mAP 没有任何参考价值。这是交通标志项目里最典型的黑匣子。解决数据划分必须按场景或按时间片分组。比如连续的视频帧按每 5 分钟一个片段切分整个片段进同一集合而不是按图片粒度随机 shuffle。如果是按路段采集的图片以文件名里的路段编号分组再划分。这样验证集的成绩才接近真实泛化水平。5.5 显存 OOM 导致进程直接被杀现象训练中途报 CUDA out of memory进程退出之前所有 epoch 白跑这也是项目最容易让人崩溃的地方。原因batch 设置太大或 --img 分辨率太高。有时是 num_workers 在 Windows 上异常占用内存导致 CPU 内存先爆掉面板上看到的是显存不够其实是内存不够。解决batch 减半或者加 --accumulate 参数做梯度累积效果等同于大 batch 但显存占用不变。训练加 --half 用混合精度显存直接降一半。num_workers 设置成 0 或 2不要默认开 8Windows 下经常卡死。还有一条后悔药先确认机器总显存再决定方案8G 卡老老实实用 yolov5s 640 batch 16不要一上来就试 yolov5x。6. 模型导出与部署验证从 PyTorch 走向 ONNX 与 TensorRT训练和调参做完项目只完成了一半。交通标志检测的落地场景是路侧盒子或车载设备跑在推理引擎而不是 PyTorch 环境里。YOLOv5 官方仓库的 export.py 可以直接完成导出命令如下python export.py \ --weights runs/train/exp/weights/best.pt \ --include onnx engine \ --imgsz 640 \ --half参数说明--include 指定导出格式onnx 是跨平台标准格式engine 是 TensorRT 的推理引擎需要在 NVIDIA 显卡上运行且驱动版本与 TensorRT 匹配--half 导出 FP16 权重体积减半推理速度明显提升但可能掉 1 到 2 个点精度导出后要做差异验证。格式运行环境典型用途注意点best.ptPyTorch训练调试、二次调参依赖 torch 环境不适合部署best.onnxCPU/GPU 均可跨平台集成、服务化用 onnxruntime 推理需检查输入输出节点变化best.engineNVIDIA TensorRT低延迟实时视频流换显卡型号或驱动就需要重新导出导出完成后我的习惯是先拿一段完全没参与训练的路况视频在 detect.py 里完整跑一遍结合 --save-txt 保存的检测结果做逐帧统计观察漏检的目标集中在哪类标志、哪个距离区间。如果漏检集中在远距离小目标回头检查的就是数据增强和输入分辨率而不是去调 NMS 参数。这个流程走通之后这个交通标志物检测项目才算真正从“能跑”变成了“能用”。希望帮到你。本文还有配套的精品资源点击获取
返回列表