拓冰建站拓冰建站
首页 / 资讯中心 / 正文

YOLO交通标志识别:VOC/COCO/YOLO格式转换与训练实战

简介这是一份YOLO交通标志识别数据集专为目标检测学习者与开发者准备涵盖3000张真实场景下的高质量图片场景丰富使用LabelImg标注标注框质量高同时提供VOCxml、COCOjson与YOLOtxt三种格式标签可直接用于主流YOLO系列模型训练。压缩包共2000个文件约807.7MB文件以1985个XML标签为主另有少量txt清单、HTML教程与Python脚本。配套资源还包含Linux与Windows双平台的YOLO环境搭建教程、训练案例教程以及训练集/验证集/测试集划分脚本可帮助用户按需拆分数据、生成训练清单并快速跑通完整流程。已有799人浏览学习无论是课程设计、算法实验还是实际项目验证都很适合需要高质量交通标志数据来实践模型训练与调参的读者。1. YOLO交通标志识别数据集能落地在哪一步做交通标志识别的人大多有过这种经历拿到了一个“含3000张图片 voc、coco、yolo三种格式标签 划分脚本 训练教程”的压缩包解压之后却不知道该先用哪份标签、按什么比例切数据、训练目录怎么摆。眼前文件很多能直接跑通的路径反而变模糊了。这个标题的价值不在“3000张图”这个数字而在“三种格式 脚本 教程”这个组合它默认画好了从数据集到训练脚本之间的全部通路。这篇内容就顺着这条通路讲先把 voc、coco、yolo 三种格式放在同一个样本上看清差异再动手写划分脚本然后按 YOLOv5 / YOLOv8 的常用流程整理目录并启动训练最后把验证和增强的细节补上。适合的读者是已经会标注、会跑模型但还没把“数据集处理”变成流水线的工程师看完后你能回答自己的三个问题标签格式选哪份、划分脚本改哪几行、训练起来后先看哪个指标。2. 三种标注格式的结构差异voc、coco、yolo各自解决什么问题2.1 VOC用XML描述单张图信息最直白VOC 格式源自 PASCAL VOC 竞赛后来成为目标检测领域最常见的交换格式之一。它的特点是“一图一 XML”图片stop_001.jpg对应stop_001.xmlbox 坐标用xmin, ymin, xmax, ymax四个像素值存类别名直接写在name节点里。正因为每张图的信息完全独立人工检查最方便不同团队之间传递数据时也几乎没有理解成本。实际看一个典型结构annotation folderimages/folder filenamestop_001.jpg/filename size width1920/width height1080/height depth3/depth /size object namestop/name bndbox xmin412/xmin ymin312/ymin xmax682/xmax ymax582/ymax /bndbox /object /annotation注意size里的宽高必须要读因为后续转 YOLO 格式时要用它做归一化。很多“转换后框全偏”的问题都出在这一步有人用图片的实际读取尺寸有人用 XML 里的 size两边不一致框就错位了。我一般会加一步校验先读 XML 的 size再拿cv2.imread的 shape 对一遍不一致时直接报警。2.2 COCO用JSON全局管理适合多任务COCO 格式用一个 JSON 文件装下整个数据集的images、annotations、categories三张表。与 VOC 相比它更像数据库每张图有id每个框挂在某个image_id下类别通过category_id关联。如果你后续要做实例分割、姿态估计或关键点检测COCO 的扩展字段能直接容纳这些信息这是它至今仍是学术与工程评估标准格式的原因。COCO 的 annotion 字段是四元组[x, y, width, height]其中x, y是框左上角像素坐标不是中心点。这一点和 YOLO 恰恰相反也是格式转换脚本里最容易写错的坐标语义。很多教程里“转出来的框全部往右下角偏”就是代码把 COCO 的左上角坐标当成了中心点去换算。另外COCO 的类别列表可能与数据集标注顺序不一致转换时要按categories里的顺序建立name - id映射不要想当然地按字母序排。2.3 YOLO用txt存归一化坐标训练读起来最快YOLO 系列训练器需要的标签格式是一张图对应一个同名 txt 文件每一行表示一个目标类别 id、归一化中心 x、归一化中心 y、归一化宽 w、归一化高 h。这里的归一化是指除以图片宽高训练时用letterbox做缩放填充归一化坐标能保证框不受输入尺寸变化影响。0 0.284895 0.413889 0.140625 0.250000 1 0.626302 0.706944 0.089844 0.115741第一列0和1是类别索引不是类别名训练配置文件data.yaml里的names列表顺序必须和这里的数字严格一致。这个对应关系错位不会报错只会让模型把“限速40”学到“停车”上而且训练过程看起来一切正常回调的 mAP 也不会提示问题。格式转换脚本里我会要求输出一份classes.txt把names顺序写死数据交接时一起带过去。三种格式的核心字段对比如下维度VOC XMLCOCO JSONYOLO txt存储粒度每图一个文件全局一个 JSON每图一个 txt坐标定义左上角 右下角像素左上角像素 宽高归一化中心点 宽高类别表达name 文本category_id 数字数字索引适合阶段标注/质检多任务/评测训练2.4 一个VOC转YOLO的稳妥小脚本把三个要点记住后转换其实只是字段重排。直接读 XML取 size 和 bndbox按 YOLO 公式换算后写入 txtimport xml.etree.ElementTree as ET class_names [stop, limit-40, limit-60, crosswalk] # 顺序决定类别id def voc_to_yolo(xml_path, out_txt): 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) lines [] for obj in root.findall(object): name obj.find(name).text if name not in class_names: continue cls_id class_names.index(name) b obj.find(bndbox) xmin float(b.find(xmin).text) ymin float(b.find(ymin).text) xmax float(b.find(xmax).text) ymax float(b.find(ymax).text) x_center (xmin xmax) / 2.0 / img_w y_center (ymin ymax) / 2.0 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h # 越界保护训练时letterbox会做填充越界坐标会影响loss计算 x_center min(max(x_center, 0.0), 1.0) y_center min(max(y_center, 0.0), 1.0) w min(w, 1.0) h min(h, 1.0) lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) with open(out_txt, w, encodingutf-8) as f: f.write(\n.join(lines))这段代码的坑集中在两处。第一class_names的顺序必须与训练时data.yaml的names一致不能“先转出来看看训练时再改”。第二clip 操作是必要的有些标注框会把边缘目标画到图片外一点YOLO 训练器虽然能容忍很小的越界值但超过一定阈值会直接抛异常或者让BCE损失出现异常跳变。转完后我会抽查 10 张图用 OpenCV 把归一化坐标映射回像素并画框肉眼确认没有整体偏移再进入下一步。3. 划分脚本怎么做从随机切分到类别分层分配3.1 为什么不能只用“前700张”这种切法压缩包自带的划分脚本通常是按比例随机打乱后切出train / val / test但实际使用时直接调用默认脚本的人后一半时间会花在怪问题上。常见现象是训练集和验证集里同一张图都出现或者“真停车”标志全部落在训练集验证集里一个都没有表现成 val mAP 非常高、测试却崩盘。原因是连续文件列表通常按类别目录装好顺序切分等于把类别也切开了。更隐蔽的问题来自“同源图片”监控视频抽帧出来的数据集里相邻几帧的画面几乎相同。如果随机划分没有按视频序列去重训练集和验证集会同时出现同一块路牌的不同帧评估指标虚高部署到新路段后掉点厉害。做划分操作时我都会先问一句数据是否来自连续帧如果是先按视频 id 分组再做分层抽样而不是对单帧做随机打乱。3.2 适配三种格式的划分脚本怎么写下面是一个对三格式都适用的基础脚本核心思路是“只按图片文件名做映射”先把图片列表切到 train / val / test 三个集合再统一复制图片和对应标注到目标目录这样三种格式只需要写一次切分逻辑。import os import shutil import random from collections import defaultdict random.seed(2024) image_dir images label_dirs [labels_voc, labels_coco, labels_yolo] train_ratio, val_ratio 0.8, 0.15 images [f for f in os.listdir(image_dir) if f.lower().endswith((.jpg, .png))] random.shuffle(images) train_list images[: int(len(images) * train_ratio)] val_list images[ int(len(images) * train_ratio): int(len(images) * (train_ratio val_ratio)) ] test_list images[int(len(images) * (train_ratio val_ratio)):] splits { train: train_list, val: val_list, test: test_list, } for split_name, file_list in splits.items(): dst_images os.path.join(dataset, split_name, images) os.makedirs(dst_images, exist_okTrue) for fmt in label_dirs: dst_labels os.path.join(dataset, split_name, labels, fmt) os.makedirs(dst_labels, exist_okTrue) for img_file in file_list: shutil.copy( os.path.join(image_dir, img_file), os.path.join(dst_images, img_file), ) stem os.path.splitext(img_file)[0] for fmt in label_dirs: src_label os.path.join(fmt, stem .txt) if os.path.exists(src_label): shutil.copy( src_label, os.path.join(dst_labels, labels, fmt, stem .txt), )这里有几个参数要按实际调整。random.seed(2024)固定随机种子保证每次运行得到相同的切分结果模型对比才有意义。train_ratio0.8, val_ratio0.15剩余 0.05 给 test如果数据量只有 3000 张可以把 test 去掉只留 train 和 val因为最终提交的是权重而不是测试集测评报告。label_dirs里的实际目录名要与压缩包解压后的命名一致常见的是Annotations、labels、json先ls看一眼再填。3.3 按类别分层划分的进阶写法3000 张图看起来不少但交通标志的类别天然不均衡“限速40”可能出现 800 次“停车让行”只有 30 次。简单随机切分下“停车让行”可能整体落到测试集训练时模型根本没怎么见过这个类推理时就会漏检。更稳的做法是按类别做分层抽样先统计每个图片里存在的全部类别再按类别所占比例把图分到各集合。from collections import defaultdict # 先统计每张图包含哪些类别 img_classes defaultdict(set) for img_file in images: stem os.path.splitext(img_file)[0] label_path os.path.join(labels_yolo, stem .txt) if not os.path.exists(label_path): continue with open(label_path) as f: for line in f: cls_id int(line.strip().split()[0]) img_classes[img_file].add(cls_id) # 按类别分组再按比例分配到 train/val/test分配时优先保证稀有类别 def stratify_split(images, img_classes, ratios(0.8, 0.15, 0.05)): # 具体实现把只含稀有类别的图先随机放入val/test再做整体补充 pass这个实现的价值在于把“稀有类保护”变成了显式策略先处理类别数少于某个阈值的图片把它们优先放入训练集再处理普通样本。实际中我不会过度追求严格分层——3000 张图的规模下过度分层会让某些图片被反复搬运反而拖慢流程。我会在第一次提醒“按类别分层在处理不均衡数据时比纯随机更稳”当 val 集里某类 AP 明显偏低时再回来调整划分。4. 训练教程目录怎么放、参数怎么调、loss怎么看4.1 按YOLOv8的目录结构整理分好后的数据集数据切完后下一步就是把目录整理成 YOLO 训练器能直接认的样子。YOLOv5 和 YOLOv8 都接受同一套基本结构区别在data.yaml里路径的写法YOLOv8 默认路径相对于data.yaml所在目录YOLOv5 默认相对于当前工作目录混用时会报“image not found”。建议统一用绝对路径省掉这层麻烦。dataset/ ├── train/ │ ├── images/ │ │ ├── stop_001.jpg │ │ └── limit_002.jpg │ └── labels/ │ ├── stop_001.txt │ └── limit_002.txt ├── val/ │ ├── images/ │ └── labels/ └── data.yaml注意labels目录里不再分voc、coco、yolo子目录训练只读 YOLO 格式。VOC 和 COCO 标签在这里的用途是兼容下游工具比如你想用某个轻量标注平台重新审核或者跑 COCO 预训练模型做迁移学习保留它们就不必重新导出了。4.2 写data.yaml并启动训练# dataset/data.yaml path: /home/user/workspace/dataset train: train/images val: val/images test: test/images nc: 12 names: 0: stop 1: limit-40 2: limit-60 3: crosswalk # 12类按实际类别顺序写nc是类别数要与names的长度一致。names的顺序必须和第 2 章转换脚本里的class_names一致缺失或顺序错位会导致训练不报错但语义全错。写好后用一行命令启动训练pip install ultralytics yolo train datadataset/data.yaml modelyolov8s.pt \ epochs100 imgsz640 batch16解释一下这行命令的几个关键参数。modelyolov8s.pt是预训练权重s 表示 small 版本对 3000 张图这个规模比较合适如果用yolov8x数据量撑不起大模型的容量val loss 很快就不再下降。epochs100对交通标志这类目标小、背景相对固定的场景够用一般到第 60 轮以后精度增益已经很小。batch16取决于显存8G 显存建议降到 8同时可以把imgsz从 640 降到 512训练速度更快对路牌这类目标影响不大。如果要用 YOLOv5 训练命令略不同但思路一致git clone https://github.com/ultralytics/yolov5 pip install -r yolov5/requirements.txt python yolov5/train.py --data dataset/data.yaml \ --weights yolov5s.pt --epochs 100 --batch-size 16 --img 640yolov5 的train.py会把--data理解为相对当前执行目录的路径所以要么在dataset同级目录运行并写dataset/data.yaml要么用绝对路径。实际踩坑时yolov5 还可能因为PIL与torchvision版本搭配问题报错建议固定 Python 3.9 / torch 2.x 的组合比反复换版本省时间。4.3 训练过程中的loss、P、R、mAP怎么读训练开始后终端会滚动输出 box_loss、cls_loss、dfl_loss 等指标。这三个数字分别对应边框回归误差、分类误差和分布焦点损失。对交通标志场景我更关注cls_loss与val/box_loss因为标志的类别间区分度低——比如限速30和限速40的形状几乎一样只有数字差别分类误差会比其他目标检测任务偏高这是正常的。yolo train datadataset/data.yaml modelyolov8s.pt \ epochs100 imgsz640 batch16 projectruns nametraffic_sign_exp1加project和name的好处是每次实验输出都落在独立目录runs/traffic_sign_exp1最后评估时直接对比不同实验的results.csv不必担心上一次的权重被覆盖。训练结束后项目目录下会出现weights/best.pt和weights/last.ptbest.pt是验证集上 mAP 最高的版本后续推理直接用这个文件。用自己数据推理的完整代码from ultralytics import YOLO model YOLO(runs/traffic_sign_exp1/weights/best.pt) results model.predict( test_images/, conf0.25, iou0.45, saveTrue, save_txtTrue, ) for r in results: boxes r.boxes.xyxy.cpu().numpy() clses r.boxes.cls.cpu().numpy().astype(int) for box, cls_id in zip(boxes, clses): print(box:, box, class:, model.names[cls_id])conf0.25表示只输出置信度高于 0.25 的框交通标志漏检的代价比误检高部署时我会把 conf 降到 0.15让模型多给几个候选框再由后端的时序投票去噪。iou0.45是 NMS 的 IoU 阈值越高越容易保留重叠框如果场景中两个标志距离很近建议降低到 0.35减少相邻目标的互相抑制。5. 验证与增强用固定验证集反复评估控制漏检比抬高mAP更优先5.1 先定义“评测基线”再谈增强训练完成后第一件事不是急着调参数而是把当前best.pt在验证集上跑一次完整评估得到按类别拆分的 AP 列表。YOLO 训练器自带的results.png只给总体 mAP这个值会被占比大的“限速40”类拉高掩盖“停车让行”类 AP 只有 0.1 的事实。我会单独看results.csv把 AP 最低的三个类别列出来这些才是真正需要增强和补数据的对象。yolo val modelruns/traffic_sign_exp1/weights/best.pt \ datadataset/data.yamlval 结束后会输出每个类别的 AP以及confusion_matrix.png直接展示哪两类容易互相混淆。交通标志最常见的混淆是“限速30”与“限速40”这类混淆靠数据增强很难解决更有效的做法是给同类图片做裁剪放大让模型能看清数字细节。5.2 增强时注意标签方向语义数据增强不是越多越好尤其对交通标志这类“方向敏感”目标。水平翻转会把“向左转弯”的标志变成“向右转弯”而 YOLO 的标准增强里fliplr0.5是默认开启的直接用就会把语义学反。解决方法是自定义增强列表关闭翻转或者做类别映射yolo train datadataset/data.yaml modelyolov8s.pt \ epochs100 imgsz640 batch16 \ hsv_h0.015 hsv_s0.5 hsv_v0.3 \ fliplr0.0 degrees5对交通标志hsv扰动可以略微加大因为户外光线变化本来就大degrees5给一点点小角度旋转模拟相机安装偏差。不建议开 mosaic 过狠虽然 mosaic 能提升小目标检测能力但 3000 张图规模下过于复杂的合成场景会拉长训练收敛时间。验证增强是否有效正确做法是把best.pt在基准线对应的同一份 val 集上重新跑yolo val对比增强前后的 mAP。如果某个类别的 AP 没变问题就不是数据量而是特征本身太接近这时就该考虑拍摄新图片而不是继续调增强参数。最终我建议保留两个模型一个是没有增强的 baseline一个是增强后的生产候选。两边都用同一份 val 集评估同时记录每个类别的漏检数而不是只盯整体 mAP。交通标志识别这个场景一张“停车”漏检往往比十次“限速”误报更严重——所以每次迭代结束先看最低类别 AP 有没有涨再看总体。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门