尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

交通标志红绿灯检测:XML转TXT格式与YOLO训练避坑指南

交通标志红绿灯检测:XML转TXT格式与YOLO训练避坑指南 简介面向目标检测实验与交通场景识别的学习者这份原创数据集提供交通标志与交通信号灯样本涵盖限速牌、警告牌及红绿灯等典型目标可直接用于YOLO系列模型的训练与验证。资源共1641个文件压缩包约217.98MB主体为877张高清PNG图片、761个XML标注文件同时附2个TXT类别及坐标说明、1个Python脚本XML保留LabelImg标注原始结果TXT则转换为YOLO所需的四角坐标与类别编号格式衔接顺畅。图片覆盖多种道路环境清晰度较高标注边界框贴合目标既适合初学者练习数据预处理、格式转换和模型调参也便于进阶者开展迁移学习或对比不同检测算法。自带转换脚本可省去重复标注时间整体目录结构清晰便于按需取用。目前已有475人学习下载可作为交通标志检测课程设计、毕业设计或入门实验的实用数据支撑。1. 交通标志与红绿灯检测数据集先看清里面装的是什么做交通标志和红绿灯检测实验最容易翻车的往往不是模型结构而是数据集本身。市面上这类 YOLO 目标检测数据集一部分把标注存成 txt 的 YOLO 格式另一批历史项目还保留着 xml 的 VOC 标注标题里的“交通标志 交通信号灯 红绿灯 检测数据集 xml txt格式”指的就是这种双格式资源——既留了 xml 里的完整标注信息又能转成 YOLO 直接训练。它的价值是帮你省掉最贵的标注时间但同时也埋了两个坑xml 转 txt 不是换个后缀就能用红绿灯与交通标志的类别编号必须和训练配置严格对齐。我按拿到数据之后的顺序把格式解析、转换脚本、训练参数和排错经验从头理顺适合正在做目标检测实验的学生以及要跑智慧交通项目的工程朋友参考。2. 先读懂标签体系xml 与 txt 双格式标注的字段差异拿到这类数据集别急着训练花半小时搞清楚 xml 和 txt 分别装了什么后面能省一整天的排错时间。两种格式表达的是同一批目标框只是坐标系和存放方式不同。xml 是 VOC 格式的完整记录txt 是 YOLO 直接读取的压缩版。只有把两者之间的换算关系摸清楚后面写转换脚本才不会被莫名其妙的坐标带偏。2.1 VOC 的 xml 标注八个字段缺一个就丢一组目标一个标准 VOC 格式的 xml 文件里我平时只关心下表中这几个字段。folder、filename、source 这类元信息对训练没有直接影响但 filename 常被用来和图片配对不要忽略。字段内容训练中的作用folder / filename图片所在目录与文件名与图片配对的依据size / width图片宽度像素归一化计算的分母之一size / height图片高度像素归一化计算的分母之二size / depth通道数通常为 3一般用不到object / name目标类别名决定 txt 里的 class_idobject / truncated目标是否在图像边缘被截断0 或 1通常保留object / difficult目标是否属于难例为 1 时建议转换时剔除object / bndboxxmin ymin xmax ymax像素绝对坐标转换唯一数据源真正决定坐标的是 size 和 object 下的内容。size 里的 width 和 height 是转换时的分母一旦这里的数据和实际图片宽高不一致转出来的坐标全部偏移。object 里一个目标一个块name 是类别名bndbox 是四个像素坐标。一张图里有多个目标就存在多个 object 块解析时要用 findall(object) 而不是 find(object)否则只拿到第一个框——这是转换脚本里最常见的隐形 bug。2.2 YOLO 的 txt 标注归一化坐标为什么全是小数YOLO 的 txt 每行一个目标五个数字用空格分开class_id x_center y_center width height。class_id 是整数四个坐标都是 0 到 1 的小数。遇到这种全小数格式新手容易以为坐标写错了其实这就是 YOLO 的标准写法。txt 字段计算方式说明class_idCLASS_NAMES.index(name)类别名转成从 0 开始的编号x_center((xmin xmax) / 2) / img_w目标中心点横向位置归一化y_center((ymin ymax) / 2) / img_h目标中心点纵向位置归一化width(xmax - xmin) / img_w目标宽度占比height(ymax - ymin) / img_h目标高度占比归一化的价值在于不依赖原始分辨率图片缩放后直接送进网络模型不需要关心输入尺寸。但这也意味着坐标是否准确完全取决于 xml 里 size 字段。举个例子某张 1280x720 的图里有个红绿灯bndbox 为 (168, 320, 190, 342)x_center 算出来是 179 / 1280 0.1398y_center 是 331 / 720 0.4597。转换后 txt 里对应这一行就是“1 0.139844 0.459722 0.017188 0.030556”后面你拿这个结果去画框必须能叠回原图。2.3 类别编号是双向对齐红绿灯和交通标志最容易错位的地方类别体系没统一是这类数据集翻车的第一来源。同一个数据集里xml 可能叫 green、traffic_light_green也可能直接叫 light_green交通标志那边更乱有 speed_limit_30、speed_limit_40 这种按具体限速拆开的也有直接写 sign 的。拿到手第一步先把 xml 里所有 name 去重打印一遍看实际有多少类再决定拆还是合。我一般会建议按颜色区分红绿灯按类型合并交通标志编号类别名说明0green_light绿灯含左转、直行箭头灯1red_light红灯2yellow_light黄灯样本少时注意过采样3speed_limit限速标志合并各级限速4warning警告标志合并各类警告5no_entry禁令标志禁止驶入类转换脚本里的 class_names 顺序、data.yaml 里的 names 顺序、模型推理时输出的索引三处必须完全一致。任何一边换了顺序训练时 loss 照常下降但 mAP 永远是零。后面排错章节会反复强调这一点因为这是实验里最容易看走眼的地方。3. 把 xml 转成 YOLO 的 txt一份能直接跑的脚本与目录组织格式看懂以后接下来就是动手把 xml 批量转成 txt。这类数据集下载下来经常是 images 和 xmls 混在一起放常见做法是先按 train/val 两套目录摆整齐再写脚本转换。我一般不在原目录里改而是新建 images、xmls、labels 三种子目录图片和标注一一对应后面训练器找文件也省心。3.1 先按 images、xmls、labels 三房分离组织目录无论你从哪个渠道拿到这套数据我都建议先整理成下面这个结构再动脚本dataset/ ├── images/ # 原始图片 │ ├── train/ │ └── val/ ├── xmls/ # 与 images 同名的 VOC 标注 │ ├── train/ │ └── val/ ├── labels/ # 转换后的 YOLO txt 输出目录 │ ├── train/ │ └── val/ └── data.yaml # 训练时用的数据集配置这个结构的核心逻辑是“三房分离”图片归图片原始 xml 归 xml转换产物归 labels。labels 目录保持与 images 相同的子目录名和文件名训练器才能自动找到同名 txt。很多人在这一步图省事把 labels 全部平铺在一个目录里结果 train 和 val 的标注互相覆盖训练时图片匹配到错误的标签文件指标一塌糊涂。3.2 xml 转 txt 脚本四个关键点决定转换是否干净转换脚本不复杂但有几个细节必须处理好。下面这份是我在多个数据集上调过的版本可以直接拿去改路径用# -*- coding: utf-8 -*- import os import xml.etree.ElementTree as ET # 类别顺序决定 txt 第一列的数字必须与后续 data.yaml 完全一致 CLASS_NAMES [ green_light, red_light, yellow_light, speed_limit, warning, no_entry, ] def xml2txt(xml_path, out_path): tree ET.parse(xml_path) root tree.getroot() # 归一化分母必须用 xml 里的 size不能用 cv2 读图后的尺寸 size root.find(size) img_w int(size.find(width).text) img_h int(size.find(height).text) with open(out_path, w, encodingutf-8) as f: for obj in root.findall(object): # findall 不是 find name obj.find(name).text.strip() if name not in CLASS_NAMES: continue # 不在类别表里的直接跳过 class_id CLASS_NAMES.index(name) difficult obj.find(difficult) if difficult is not None and difficult.text 1: continue # 难例剔除避免污染训练 bndbox obj.find(bndbox) xmin float(bndbox.find(xmin).text) ymin float(bndbox.find(ymin).text) xmax float(bndbox.find(xmax).text) ymax float(bndbox.find(ymax).text) x_center (xmin xmax) / 2.0 / img_w y_center (ymin ymax) / 2.0 / img_h box_w (xmax - xmin) / img_w box_h (ymax - ymin) / img_h if not (0 x_center 1 and 0 y_center 1): print(warning: out of range, xml_path, class_id) f.write(f{class_id} {x_center:.6f} {y_center:.6f} f{box_w:.6f} {box_h:.6f}\n) if __name__ __main__: xml_dir dataset/xmls/train out_dir dataset/labels/train os.makedirs(out_dir, exist_okTrue) for fname in os.listdir(xml_dir): if fname.endswith(.xml): xml2txt( os.path.join(xml_dir, fname), os.path.join(out_dir, fname.replace(.xml, .txt)), )逻辑说明脚本按目录遍历所有 xml对每个 object 块提取类别和 bndbox加总取中点后除以图片宽高得到归一化坐标。输出文件的命名规则是把 xml 后缀换成 txt保证和图片同名。参数说明CLASS_NAMES 的顺序一旦定下来就不要改它直接决定 txt 第一列的数字也决定之后 data.yaml 里的 names 顺序。xml 的 size 字段必须和真实图片一致如果图片被 resize 过而 xml 没更新转出来的框会整体偏移。difficult 为 1 的难例建议剔除否则模型会把难例当成噪声硬学。程序里加了范围告警如果坐标超出 0 到 1会打印警告这时要回到 xml 检查 bndbox 是否标错。3.3 转换后的可视化校验把框画回图上十行代码见真章转换完不要直接开训练先抽几十张图把框画回去看一眼。这一步能同时验证两件事归一化坐标换算是否正确类别索引是否对得上。我常用下面这个脚本# verify.py import cv2 def draw_boxes(image_path, label_path, class_names): img cv2.imread(image_path) h, w img.shape[:2] with open(label_path) as f: for line in f: parts line.strip().split() if len(parts) ! 5: continue class_id int(parts[0]) xc, yc, bw, bh map(float, parts[1:]) # 把归一化坐标换算回像素注意是中心点加减宽高的一半 x1 int((xc - bw / 2) * w) y1 int((yc - bh / 2) * h) x2 int((xc bw / 2) * w) y2 int((yc bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, class_names[class_id], (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1) cv2.imwrite(image_path.replace(.jpg, _check.jpg), img)在校验图上如果发现框全部跑到左上角或者整体偏移多半是 xml 的 size 和实际图片尺寸不一致如果某个框明显框错位置但其他框正常则是那一个目标的 bndbox 标注问题如果类别名和框对不上就是 CLASS_NAMES 的顺序错位回到脚本里改顺序而不是改训练配置。这个检查习惯能帮你过滤掉大部分低级错误。4. 用这套数据训练 YOLOdata.yaml 与训练参数的设置顺序格式转对了训练本身反而简单。在 ultralytics 框架下数据集、模型、训练参数三样对齐就能跑起来。这里有一个容易被忽略的事实这类数据集的问题从来不在损失函数也不在模型结构而在于数据和配置是否一致。4.1 data.yamlpath、nc、names 三处与转换脚本对齐data.yaml 是训练时读入的第一个文件写错一处就会导致类别错位。我习惯把路径写成相对路径避免换机器以后找不到目录。# dataset/data.yaml train: dataset/images/train # 训练集图片目录 val: dataset/images/val # 验证集图片目录 nc: 6 names: 0: green_light 1: red_light 2: yellow_light 3: speed_limit 4: warning 5: no_entrytrain 和 val 指向图片目录训练器会自动去找同名的 labels 目录所以 images 与 labels 的目录名、文件名必须完全一致。nc 必须等于转换脚本里 CLASS_NAMES 的长度names 的顺序必须和 CLASS_NAMES 一字不差。训练器实际输出的类别索引就是 txt 里的第一列数字任何换位都会导致整体错位。建议转换脚本跑完后随便打开一个 txt 文件看第一列数字对应的类别名再回来核对 yaml。4.2 训练命令与三类参数imgsz、batch、mosaic 这样调训练命令本身很直接但参数不能全用默认值。红绿灯和交通标志属于中小目标默认配置是为 COCO 80 类准备的直接套用会吃亏。yolo detect train \ datadataset/data.yaml \ modelyolov8s.pt \ imgsz640 \ batch16 \ epochs100 \ workers4 \ device0imgsz 默认 640对远处的红绿灯来说偏小。一个小目标在 1080p 原图里只有 24x24 像素缩到 640 以后变成十几个像素特征基本消失。显存允许的情况下我会把 imgsz 提到 960batch 相应降到 8。显卡只有 6G 显存的话坚持 imgsz640、batch8 更稳妥。epochs 对这类单场景数据集不必太多100 轮足够训练时盯住 val loss连续 20 轮不降就可以停。mosaic 增强要单独说。yolo 默认 mosaic1.0对常规目标没问题但红绿灯这类小目标在 mosaic 拼接时会被切到边缘甚至直接丢掉。我一般前 2/3 训练轮数保持 mosaic0.5后 1/3 设成 0让模型在最后阶段学习完整目标。至于损失函数红绿灯数据集常见问题不是 loss 不收敛而是类别错位导致 loss 正常下降、mAP 全零别在损失函数上花太多时间排查。类别不平衡更直接的解法是把数量少的类别对应图片复制几份进训练目录同时复制同名标签比调任何损失权重都见效快。4.3 划分 train/val按场景切别随机切随机划分在目标检测数据集上是最大的隐患。同一路口的连续视频帧背景几乎一样随机切会让大量相似帧同时出现在训练集和验证集val mAP 虚高到 0.9 以上一到真实场景立即露馅。正确做法是按采集场景或时间划分保证验证集与训练集来自不同时段或不同路口。我一般这样操作假如数据是按视频片段组织的先按视频编号分组每个视频的前 80% 帧进 train后 20% 帧进 val然后同步移动图片和 xml。# 按场景目录 scene_01 拆分 mkdir -p dataset/images/train/scene_01 mkdir -p dataset/xmls/train/scene_01 mv dataset/raw/scene_01/*.jpg dataset/images/train/scene_01/ mv dataset/raw/scene_01/*.xml dataset/xmls/train/scene_01/注意 mv 命令里图片和 xml 必须同步操作否则图片进了 train 而对应 xml 还留在 raw 目录转换脚本会漏掉标注。验证集不需要很大占总量的 5% 到 10% 就够但必须异源这样的验证结果才有参考价值。5. 数据预处理与训练避坑五个让模型翻车的常见原因前面流程走完剩下的就是排错。这一章我把实际踩过的坑按“现象 → 原因 → 解决”列出来每条都是真实项目里见过的不是理论推断。5.1 xml 解析报错跑到一半就中断现象转换脚本执行到某个 xml 文件时抛出 ParseError程序直接退出前面的转换结果也没写完整。原因xml 文件不是 UTF-8 编码常见的是 GBK 格式保存还有可能是下载过程中文件被截断标签闭合标签缺失。数据集批量打包时这种坏文件几乎一定存在。解决脚本里对单个文件做 try/except 容错解析失败时打印文件路径跳过继续处理其他文件。对编码问题先把 xml 用 UTF-8 重新保存再转对真正损坏的文件删掉 xml 的同时记住删掉对应图片否则训练时图片配对找不到标签。try: tree ET.parse(xml_path) except ET.ParseError: print(bad xml:, xml_path) continue5.2 训练正常但 mAP 全零类别编号错位现象训练能正常跑完loss 也在下降但验证结果的每个类别 mAP 都是 0或者一类的 mAP 高到异常而其他类全零。原因txt 第一列的类别编号和 yaml names 的顺序没对上。转换脚本里 CLASS_NAMES 把 green_light 放在 0yaml 里却把 red_light 放在 0模型学到的特征和标签全错位了。这种错误用肉眼很难发现因为 loss 曲线看起来完全正常。解决取训练集里一个 txt 文件打印第一列数字对照 yaml 的 names 逐个核对。另一个更直观的办法是用校验脚本画框如果框的位置全对但类别名乱跳就是这里错位了。记住这个顺序转换脚本 CLASS_NAMES、data.yaml names、模型 output index三处必须同序。5.3 验证集 mAP 虚高到 0.9同源帧污染现象训练结束后 val mAP 高达 0.9看起来效果非常好但把模型拿到现场视频上跑漏检严重。原因train/val 用随机方式划分同一个视频片段里的相邻帧同时出现在训练集和验证集。模型相当于背下了这段路的背景验证时看到“熟悉”的帧就得高分遇到没见过的路口立刻失效。这是我第一次做目标检测实验时踩过的最深的坑也是做这类数据集最容易看走眼的地方。解决放弃随机划分按采集时间或场景目录划分。验证集可以小但必须和训练集来自不同场景。划分后检查一遍两个集合里有没有文件名前缀相同的图片有就说明同源帧还没清干净。5.4 红绿灯小目标漏检mosaic 把目标切没了现象整体 mAP 尚可但远处的小红绿灯几乎全漏近处的却能检测到。原因小目标在图像里只占几十像素imgsz640 输入下进一步缩小再叠加 mosaic 拼接目标可能在拼接边界被截断一半。模型在训练时大量看到残缺目标自然学不会完整的红绿灯特征。解决把 imgsz 提到 960 并相应降 batchmosaic 增强在训练后 1/3 轮数关掉把包含小目标的图片在训练目录里多复制几份对应标签同步复制增加这类样本的曝光度。还有一个土办法把包含远距离红绿灯的图在预处理时只做轻微缩放不要用大的 scale 增强。5.5 txt 里出现 NaN 或超界坐标size 字段与图片不一致现象转换完成后扫一遍 txt发现某些行出现 nan或者 width、height 算出来大于 1。原因xml 的 size 字段和实际图片宽高不一致常见于图片被批量压缩后 xml 没同步更新。bndbox 本身也可能标错比如 xmax 比图片宽度还大。解决转换脚本里加范围断言x_center、y_center、box_w、box_h 任一超出 0 到 1 就打印文件名。批量扫描已有的 txt把含 nan 或超界数值的行挑出来定位到具体 xml手工修正 size 或 bndbox。修正时以实际图片的宽高为准不要看 xml 里写的。for txt in label_files: for line in open(txt): parts line.strip().split() if nan in line or float(parts[3]) 1: print(txt, line)6. 验证模型的两条捷径按类 mAP 和 ONNX 导出实测训练完别只盯着整体 mAP 看。整体 0.9 不代表红绿灯好用按类别拆开才能暴露真实短板。我每次都会把验证结果按类打印重点关注 yellow_light 和 no_entry 这类样本少的类别如果它们的 mAP 明显低于 green_light最常见的原因是样本不足而不是模型结构问题。这里给一个我自己常用的验收标准作参考。验证维度指标建议参考值检测精度mAP500.85 以上小目标召回AR 小目标红绿灯单独看低于 0.7 要补样本类别平衡per-class mAP各类之间差距小于 0.1推理速度单张推理耗时1080p 下 30ms 以内第二条捷径是导出 ONNX 做实测。训练完的 best.pt 在验证集上再好看也不如实拍视频有说服力导出成 ONNX 后用 onnxruntime 跑单张推理能同时验证模型在非训练环境下的数值精度和速度。yolo export modelruns/detect/train/weights/best.pt formatonnx imgsz640import onnxruntime as ort import cv2 sess ort.InferenceSession(best.onnx) img cv2.imread(test.jpg) # 预处理resize 到 imgsz、归一化、通道转 CHW按原训练时的预处理顺序 # 推理后取 output 的 boxes 和 scores按置信度阈值过滤 # 把归一化坐标换算回原图尺寸后画框导出后如果发现输出结果和 PyTorch 推理不一致先看预处理是否完全复刻训练流程尤其是归一化除 255 和颜色通道顺序。如果部署环境对速度还有更高要求常见路线是在 ONNX 基础上再做引擎优化压榨剩余性能这一步的收益比换更重的模型结构来得直接。我第一次拿这类数据集做实验时就吃过随机划分的亏当时看着 val mAP 高得离谱拿到现场视频被打脸。后来老实按视频前 80% 切数值才诚实起来。这类数据集的价值在标注质量不在新模型结构把数据管好、把验证做严格效果远比折腾模型快。希望帮到你。本文还有配套的精品资源点击获取
返回列表