简介中国交通标志TT100K检测数据集是面向交通标志识别与检测任务的标准化数据集适合研究自动驾驶、智能交通系统的算法工程师和高校学生使用。资源包含7962张真实交通场景jpg图片每张均配有VOC格式xml标注与YOLO格式txt标注覆盖45类常见中国交通标志可直接用于训练YOLO、SSD、Faster R-CNN等目标检测模型免去自行转换标注格式的环节。压缩包共2000个文件以1999个xml标注文件为主另有1个使用说明txt整体约401.64MB目录结构便于按序读取与批量处理。已有1342人学习或浏览该资源。对需要大规模数据训练和验证交通标志识别算法、提升模型泛化能力的开发者而言这份数据集提供了完整的标注体系与足够的样本量是开展相关实验与项目落地的实用基础。1. TT100K检测数据集这套双格式标注资源到底能省多少事中国交通标志TT100K检测数据集VOCYOLO格式7962张45类别这个.7z压缩包拆开之后是直接能进训练流程的三件套7962张jpg配7962个VOC xml和7962个YOLO txt45个类别覆盖限速、禁令、警告、指示等国内公路最常见的标志。做检测项目的人都知道标注成本经常比训练成本还高一套现成且双格式齐全的数据集能省掉至少两周的标注和格式转换时间。这套东西适合三类人做交通标志识别的算法工程师拿来做预训练数据和评测集跑YOLO复现的学生txt直接喂训练脚本想快速验证检测方案的个人开发者。这个包是在TT100K基础上筛选整理后的子集图片更干净、类别更规整跳过了最费劲的原始数据过滤步骤。下面按我实际拆包和跑训练的顺序讲双格式怎么读、怎么清洗、坑在哪。2. 双格式标注解剖VOC和YOLO分别怎么读2.1 文件配对逻辑与目录结构解压完第一眼看到的就是一大排firc_tt100k_开头的文件同编号的jpg、xml、txt三条一一对应。文件命名规律很简单firc_tt100k_编号.jpg / .xml / .txt其中xml是Pascal VOC标准的标注文件txt是YOLO训练脚本能直接读的标注文件。这个包里txt不包含分割路径信息只写目标框和类别编号至于数据集怎么拆训练集验证集由你自己定。拿到包之后建议先做一次配对完整性检查别急着开训练。常见做法是下面这段脚本以jpg为主索引校验同名校验文件是否存在from pathlib import Path root Path(tt100k) imgs sorted(root.glob(*.jpg)) print(jpg总数:, len(imgs)) missing 0 for img in imgs: xml img.with_suffix(.xml) txt img.with_suffix(.txt) if not xml.exists() or not txt.exists(): missing 1 print(缺配对:, img.name) print(缺失文件数:, missing)逻辑说明很简单jpg是源头xml和txt都是从它派生出来的标注文件所以拿jpg做循环主体。with_suffix方法会把.jpg替换成.xml和.txt再判断文件是否存在。如果missing为0说明7962张三件套齐了。这里有个容易被忽略的点有些压缩包解压后会混进__MACOSX目录或Thumbs.db这类系统文件glob(*.jpg)不会匹配到它们所以计数不受影响。但如果解压出多个层级目录就要先确认jpg是不是都在同一个目录别让脚本只扫到其中一部分。2.2 VOC格式到YOLO格式的坐标换算随手打开一个xml结构就是标准的Pascal VOC格式annotation filenamefirc_tt100k_6759.jpg/filename size width2048/width height2048/height /size object namespeed_limit_40/name bndbox xmin1024/xmin ymin800/ymin xmax1152/xmax ymax928/ymax /bndbox /object /annotation这里最关键的信息有三处size里是原图宽高object的name是类别名bndbox是像素级的左上角和右下角坐标。注意VOC的坐标是整数像素而YOLO需要归一化到[0,1]区间内的浮点数转换公式是cx (xmin xmax) / 2 / widthcy (ymin ymax) / 2 / heightw (xmax - xmin) / widthh (ymax - ymin) / heightcx、cy是目标中心点坐标w和h是框宽高。用代码实现import xml.etree.ElementTree as ET def voc_to_yolo(xml_path, class_names): tree ET.parse(xml_path) root tree.getroot() W int(root.find(size/width).text) H int(root.find(size/height).text) lines [] for obj in root.iter(object): name obj.find(name).text if name not in class_names: continue cls_id class_names[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) cx (xmin xmax) / 2.0 / W cy (ymin ymax) / 2.0 / H w (xmax - xmin) / W h (ymax - ymin) / H lines.append(f{cls_id} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) return linesclass_names是手动维护的类别名到编号字典比如{speed_limit_40: 0, speed_limit_60: 1}。如果xml里出现不在字典里的类别名continue直接跳过这也是做类别过滤的常用手段。取坐标时用float而不是int是为了防止标注工具把坐标写成带小数的值导致int后丢精度。输出保留6位小数这是YOLO系列训练脚本的通用习惯。这个包里的txt已经是现成的一般不需要自己转换但把换算逻辑放在这里是为了方便你校验官方txt是否正确。我建议随机抽几张图按公式手动算一遍和txt里的数值对比误差大于1e-3就要警惕。2.3 45类体系与格式选型45个类别具体包含哪些以xml里的name字段为准。别依赖直觉动手统计一遍才是正经做法from collections import Counter from pathlib import Path import xml.etree.ElementTree as ET counts Counter() for xml in Path(tt100k).glob(*.xml): root ET.parse(xml).getroot() for obj in root.iter(object): counts[obj.find(name).text] 1 for name, n in counts.most_common(): print(f{name}: {n})运行后会看到类似speed_limit_40: 812的输出类别和样本量一目了然。这一步为什么重要因为45类是从原始TT100K里筛出来的有些类别之间是近似关系统计结果能直接告诉你能不能训练得动以及哪些类适合合并。关于格式选型我的看法很直接VOC适合调试和可视化标注信息结构化解析方便YOLO适合直接训练一行一条标注读取开销小。这包两种都给最大的价值是省去写VOC转YOLO工具的工作量也避免自己转换时写错坐标白烧训练时间。提示txt第一列是类别编号编号对应的类别名在txt里看不到必须从xml反查。训练前把类别名按固定顺序排好生成一份names清单锁死后面所有环节都用这一份不要在训练中途换映射。另外注意这些xml里的name字段可能包含一些相似语义的类别比如不同数字的限速牌。做实际项目时你是不是需要区分限速40和限速60取决于业务需求如果只要判断这是限速牌就把它们合并成一个类别再训练能显著提升小样本类别的召回率。3. 从7z压缩包到可训练数据集解压、校验与划分3.1 解压7z与三件套计数先讲解压因为这一步卡住不少人。Linux或macOS下用p7zip# 安装p7zipDebian/Ubuntu系 sudo apt install p7zip-full # 解压到tt100k目录 7z x 中国交通标志TT100K检测数据集VOCYOLO格式7962张45类别.7z -ott100kx参数是解压extract-o后面紧跟输出目录注意-o和目录之间不能有空格。Windows下不想装命令行的用7-Zip或Bandizip右键提取到当前文件夹就行效果等价。如果压缩包是用7z高版本压缩的旧版可能解不动升级p7zip到最新版再试。解压完别急着数先看一遍目录结构确认jpg和xml、txt是否在同一层。然后计数cd tt100k echo jpg: $(find . -name *.jpg | wc -l) echo xml: $(find . -name *.xml | wc -l) echo txt: $(find . -name *.txt | wc -l)find带-name会把子目录里的文件也找出来wc -l数行数。三个输出都应该是7962任何一个对不上就说明包不完整或解压过程有问题。我习惯把这三个数先记在笔记里后面清洗增删后能对比防止手误把文件弄丢。另外一个细节在Linux终端粘贴含中文的压缩包名时如果终端编码不对会报找不到文件用Tab补全最稳。3.2 Python清洗损坏图片与非法标注这一步容易被跳过但我不建议省。TT100K原始数据集质量不错但下载传输和解压过程可能损坏文件网盘渠道偶尔有零字节文件。扫一遍不费多少时间from PIL import Image import xml.etree.ElementTree as ET from pathlib import Path bad_imgs, bad_xmls [], [] for img in Path(tt100k).glob(*.jpg): try: Image.open(img).verify() except Exception: bad_imgs.append(img.name) for xml in Path(tt100k).glob(*.xml): try: root ET.parse(xml).getroot() for obj in root.iter(object): b obj.find(bndbox) xmin float(b.find(xmin).text) xmax float(b.find(xmax).text) ymin float(b.find(ymin).text) ymax float(b.find(ymax).text) if not (0 xmin xmax and 0 ymin ymax): bad_xmls.append(xml.name) except Exception: bad_xmls.append(xml.name) print(损坏图片数:, len(bad_imgs)) print(非法框XML数:, len(bad_xmls))verify()只校验文件头和尾部完整性不会整图解码速度非常快7962张图几秒跑完。坐标合法性判断条件是左上角小于右下角且在图像范围内xmax小于等于xmin或ymax小于等于ymin都是退化框视为非法。这两个bad列表里的文件建议先移到backup目录而不是直接删万一后续发现误判还能找回来。接着统计小目标占比这会直接影响你训练时的输入分辨率和增强策略min_area_ratio 0.001 small total 0 for img_path in Path(tt100k).glob(*.jpg): with Image.open(img_path) as im: W, H im.size root ET.parse(img_path.with_suffix(.xml)).getroot() for obj in root.iter(object): b obj.find(bndbox) bw float(b.find(xmax).text) - float(b.find(xmin).text) bh float(b.find(ymax).text) - float(b.find(ymin).text) total 1 if bw * bh W * H * min_area_ratio: small 1 print(f小目标(面积占比{min_area_ratio:.1%})数量: {small}/{total})按框面积占整图面积的比例来判断阈值设0.1%是我从实际训练里沉淀下来的经验。再小的话在640x640输入下往往只有不到10x10像素YOLO基本学不动。跑完如果small占比超过40%建议要么在3.3里过滤这部分要么后续训练直接把imgsz拉到1280。TT100K这类街景数据里远距离小标志占比普遍偏高看到结果不奇怪。3.3 数据集划分与YOLO目录组织YOLO训练不认平铺目录标准结构是images/train、images/val、labels/train、labels/val。下面这个脚本完成划分和搬运seed固定保证复现import random from pathlib import Path random.seed(42) files sorted(Path(tt100k).glob(*.jpg)) random.shuffle(files) val_cnt int(len(files) * 0.2) val_files files[:val_cnt] train_files files[val_cnt:] base Path(yolo_dataset) for split, items in [(train, train_files), (val, val_files)]: img_dir base / images / split lbl_dir base / labels / split img_dir.mkdir(parentsTrue, exist_okTrue) lbl_dir.mkdir(parentsTrue, exist_okTrue) for img in items: img.rename(img_dir / img.name) txt img.with_suffix(.txt) txt.rename(lbl_dir / txt.name) xml_path img.with_suffix(.xml) xml_path.rename(base / xml_backup / split / xml_path.name)先随机打乱再按20%切出验证集剩下80%训练。seed固定成42下次重跑结果完全一致方便对比实验。xml没有扔而是备份到xml_backup这样调试可视化时能随时回去查原始结构。注意这里用的rename是移动文件如果希望保留平铺目录做其他用途把rename换成shutil.copy2即可。注意TT100K是街景采集数据同一个场景的多帧画面可能存在目标重叠。简单随机切分时同一场景的不同帧可能同时落到train和val里造成信息泄漏训练指标虚高。如果要做严格评测按场景分组切分或者用GPS时间戳分组最省事的做法是先把所有图片按文件名前缀分组同一组内的图片只进一个集合。划分完检查两个关键点train和val目录里txt数量和images数量是否一致以及每个txt文件行数是否大于0。空txt代表这张图没有目标在YOLO训练里会被忽略但不会报错不过如果空文件集中在验证集mAP计算会失去参考意义训练集里空文件过多则会导致部分epoch学不到正样本。4. 避坑TT100K使用中的五个典型问题4.1 现象一图片尺寸不统一resize后框错位训练时imgsz设了640loss正常下降但可视化预测结果时发现框明显贴边或者偏移尤其是大图缩到小图时偏移更严重。原因这个包里的图片并不全是一个分辨率有的接近正方形有的是宽幅。模型在letterbox后把图resize成640x640标注坐标做了等比缩放但如果你的预处理强制拉伸而不是letterbox坐标就会错位。解决ultralytics默认letterbox是安全的不需要改。但如果用的是自己写的pipeline务必等比缩放加灰边填充不要直接resize。另一个做法是训练前把所有图统一处理成640x640并同步缩放xml坐标从源头消灭长宽比差异代价是硬盘上多一份副本。4.2 现象二小目标占比过高mAP50-95上不去训练完mAP50有0.85但mAP50-95只有0.45两个指标之间差距过大。原因TT100K街景图里远处交通标志非常多框面积不到全图的0.1%。在YOLO这类下采样倍数高的检测器里小目标经过8次下采样只剩几个像素特征信息基本丢失。解决把imgsz从640调到1280显存不够就降batch效果立竿见影但训练时间翻倍。或者用SAHI做切片推理训练时保持640输入推理时把图切成多块分别预测再合并。如果只是做研究验证过滤小目标可以接受但真实场景里恰恰需要这类远距离标志不建议一滤了之。4.3 现象三解压后文件名或xml内容乱码Linux下7z x解压出来的文件名偶尔是乱码Windows下同样的包用7-Zip右键解压却完全正常。原因压缩包在Windows上创建时文件名用GBK编码写入元信息Linux终端默认UTF-8解码就乱了。解决Windows下解压最省心。Linux下遇到乱码先别删包用7z l查看压缩包内的文件清单确认是不是编码问题然后配合convmv批量转码文件名。xml内容乱码一般出在文件头声明和实际编码不一致用iconv转成UTF-8重写一遍即可。我踩过这个坑之后凡是从Windows端制作的压缩包一律优先在Windows或macOS下解压省得和编码较劲。4.4 现象四45类样本量严重不均衡统计下来发现排名前三的类别占总实例数的一半尾部类别只有几十个实例训练后尾部类基本学不出来。原因自然采集的道路数据就是长尾分布。限速牌和指示牌出镜率远高于一些少见禁令牌这是客观场景决定的不是数据集质量问题。解决先决定要不要做类合并。如果业务只需要区分限速、禁令、警告、指示四类直接把45类映射成4类尾部小类合并后样本量瞬间上去了。不做合并的话对尾部类别做copy-paste增强或过采样能够缓解但训练时间会变长。最怕的是不统计直接开训尾部类别在loss里被淹没推理时永远预测不到。4.5 现象五txt类别编号和预期的names对应不上训练时loss曲线正常但跑预测发现某个类别的框全部画成另一个类别名类别还是错的。原因txt第一列的编号是在整理数据集时按某个顺序生成的比如按类别名排序。如果你在data.yaml里写的names顺序和它不一致编号就错位了。YOLO训练不会校验类别名和编号的一致性它只看编号。解决从xml按序提取类别名生成编号到类别的映射表并锁死。训练、验证、导出模型全部用同一张表中途不要换。我之前写过一个小脚本把txt第一列的id和xml里对应实例的name打印出来对齐核对十五分钟就能排查完整个数据集。这一步做完后面出错的概率直接降一半。5. 快验一把用YOLOv8跑通TT100K的全流程5.1 组织目录与data.yaml到这一步目录结构已是标准YOLO格式装好ultralytics就能开跑。先把数据配置文件写好# tt100k.yaml path: /your/path/yolo_dataset train: images/train val: images/val names: 0: speed_limit_40 1: speed_limit_60 2: no_entry 3: warning_curve # 一共45行按你的映射表顺序填完path建议写绝对路径相对路径偶尔会因为切换工作目录而找不到数据。names的顺序必须和4.5锁死的那张映射表完全一致别图省事用Excel排序后直接粘贴编号对不上就是白训练一晚上。5.2 训练命令与可视化验证yolo detect train datatt100k.yaml modelyolov8n.pt \ epochs50 imgsz640 batch16 device0yolov8n是轻量模型普通显卡跑得动50轮半小时能完适合先验证数据链路。训练完直接跑推理看效果yolo predict modelruns/detect/train/weights/best.pt \ sourceyolo_dataset/images/val saveTrue打开预测图重点看三件事框边缘有没有明显偏离标志边界远距离小目标有没有漏掉类别对不对。如果小目标漏检严重按4.2的方案调imgsz或上SAHI。验证没问题后想部署的话顺手导出onnxyolo export modelruns/detect/train/weights/best.pt formatonnx imgsz640导出和训练共用同一套names编号错位在导出阶段同样会埋雷先跑验证再导出是我固定的顺序。我自己第一次拿这套数据训练时跳过了类别映射核对跑了三十轮才发现编号错位白烧了一晚上的电。从那以后每次拿到新数据集我都强制先跑一遍统计脚本把三件套数量、类别分布、坐标合法性全打印出来再开训练。希望帮到你。本文还有配套的精品资源点击获取