1. 从GTSDB到YOLO为什么需要转换数据集格式第一次接触交通标志检测项目时我发现GTSDBGerman Traffic Sign Detection Benchmark是个不错的起点。这个数据集包含了900张德国交通标志的图片格式为ppm标签文件则是gt.txt。但问题来了YOLO系列模型需要的是jpg图片和特定格式的txt标签文件。这就好比你想用微波炉加热食物却发现包装上写着仅适用于烤箱——必须得先转换格式才能用。我刚开始也犯过直接硬上的错误结果训练时各种报错。后来才明白YOLO需要的标签格式和GTSDB原始格式主要有三个关键区别坐标系统GTSDB使用左上角和右下角坐标YOLO需要中心点坐标宽高归一化处理YOLO要求所有坐标值在0-1之间相对于图片尺寸文件结构每个图片需要单独的标签文件而不是集中在一个gt.txt里这就像把纸质文档扫描成电子版——内容没变但存储方式完全不同。下面我就带你一步步完成这个格式翻译工作。2. 实战第一步获取并解压原始数据集首先得拿到原始数据。GTSDB数据集可以在Public Archive: ff17dc924eba88d5d01a807357d6614c (erda.dk)找到下载FullIJCNN2013.zip这个文件。我建议新建一个专门的项目文件夹比如命名为GTSDB2YOLO把下载的压缩包放进去解压。解压后会看到900个.ppm格式的图片文件一个gt.txt标签文件一些readme文档建议先看看这里有个小坑要注意Windows系统默认可能无法预览ppm文件。别担心这不是文件损坏只是系统不支持这种格式的缩略图显示。用代码能正常读取就行。3. 图片格式转换从ppm到jpgYOLO对jpg/png的支持最好所以先处理图片格式。Python的PIL库就能搞定这个转换我常用的转换脚本如下from PIL import Image import os def convert_ppm_to_jpg(input_dir, output_dir): if not os.path.exists(output_dir): os.makedirs(output_dir) for filename in os.listdir(input_dir): if filename.lower().endswith(.ppm): img_path os.path.join(input_dir, filename) try: img Image.open(img_path) new_filename os.path.splitext(filename)[0] .jpg save_path os.path.join(output_dir, new_filename) img.save(save_path, JPEG, quality95) except Exception as e: print(fError processing {filename}: {str(e)}) # 使用示例 input_dir ./FullIJCNN2013 # 解压后的文件夹 output_dir ./images_jpg # 输出文件夹 convert_ppm_to_jpg(input_dir, output_dir)运行后会生成900张jpg图片。有个细节要注意quality参数控制压缩质量建议设置在90-95之间。太低会影响图片质量太高则文件体积会变大。4. 标签格式转换从gt.txt到YOLO格式这是最关键的步骤。GTSDB的gt.txt文件内容是这样的格式00000.ppm;775;397;812;425;14 00001.ppm;399;393;427;421;14每行表示图片名;x1;y1;x2;y2;类别编号而YOLO需要的格式是每个图片对应一个同名的.txt文件每行表示class_id x_center y_center width height所有坐标值都是归一化后的0-1之间转换脚本如下import os def convert_gtsdb_to_yolo(gt_path, images_dir, labels_dir): if not os.path.exists(labels_dir): os.makedirs(labels_dir) with open(gt_path, r) as f: lines f.readlines() for line in lines: parts line.strip().split(;) if len(parts) 6: continue img_name parts[0] x1, y1, x2, y2 map(int, parts[1:5]) class_id int(parts[5]) - 1 # GTSDB类别从1开始YOLO从0开始 # 获取图片尺寸用于归一化 img_path os.path.join(images_dir, img_name.replace(.ppm, .jpg)) if not os.path.exists(img_path): continue img Image.open(img_path) img_w, img_h img.size # 计算中心点和宽高 x_center ((x1 x2) / 2) / img_w y_center ((y1 y2) / 2) / img_h width (x2 - x1) / img_w height (y2 - y1) / img_h # 写入YOLO格式标签 label_name os.path.splitext(img_name)[0] .txt label_path os.path.join(labels_dir, label_name) with open(label_path, w) as f: f.write(f{class_id} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}\n) # 使用示例 gt_path ./FullIJCNN2013/gt.txt images_dir ./images_jpg labels_dir ./labels convert_gtsdb_to_yolo(gt_path, images_dir, labels_dir)注意几个关键点类别ID减了1因为GTSDB从1开始计数而YOLO从0开始坐标归一化是相对于图片宽高的比例只生成有标注的图片对应的标签文件共741个5. 数据集划分与YAML配置现在我们有741张带标注的图片原始900张中有159张没有标注。接下来需要划分训练集、验证集和测试集。我推荐使用8:1:1的比例这样既保证足够训练数据又有合理的验证测试集。import os import random from shutil import copyfile def split_dataset(images_dir, labels_dir, output_dir, ratios(0.8, 0.1, 0.1)): # 创建输出目录结构 os.makedirs(os.path.join(output_dir, images, train), exist_okTrue) os.makedirs(os.path.join(output_dir, images, val), exist_okTrue) os.makedirs(os.path.join(output_dir, images, test), exist_okTrue) os.makedirs(os.path.join(output_dir, labels, train), exist_okTrue) os.makedirs(os.path.join(output_dir, labels, val), exist_okTrue) os.makedirs(os.path.join(output_dir, labels, test), exist_okTrue) # 获取所有有效图片名有对应标签的 image_files [f for f in os.listdir(images_dir) if f.endswith(.jpg)] label_files [f.replace(.jpg, .txt) for f in image_files] valid_pairs [(img, lbl) for img, lbl in zip(image_files, label_files) if os.path.exists(os.path.join(labels_dir, lbl))] random.shuffle(valid_pairs) total len(valid_pairs) train_end int(total * ratios[0]) val_end train_end int(total * ratios[1]) # 复制文件到对应目录 for i, (img_file, lbl_file) in enumerate(valid_pairs): if i train_end: subset train elif i val_end: subset val else: subset test # 复制图片 src_img os.path.join(images_dir, img_file) dst_img os.path.join(output_dir, images, subset, img_file) copyfile(src_img, dst_img) # 复制标签 src_lbl os.path.join(labels_dir, lbl_file) dst_lbl os.path.join(output_dir, labels, subset, lbl_file) copyfile(src_lbl, dst_lbl) # 使用示例 split_dataset(./images_jpg, ./labels, ./dataset)最后需要创建YOLO的配置文件比如gtsdb.yaml# 数据集路径 path: ./dataset train: images/train val: images/val test: images/test # 类别信息 names: 0: speed_limit_20 1: speed_limit_30 2: speed_limit_50 # ... 其他类别按GTSDB的实际顺序填写 42: end_of_no_passing6. 验证与训练技巧完成上述步骤后建议先用YOLO的命令行工具验证下数据集yolo detect train datagtsdb.yaml modelyolov8n.pt epochs10 --imgsz 640我在实际训练中发现几个优化点图片尺寸设置为640x640效果就不错再大提升有限数据增强很重要特别是旋转和色彩变换类别不平衡问题明显比如限速标志特别多可以尝试加权损失函数如果遇到Missing labels警告可能是有些图片没有对应的标签文件。这时需要检查数据集划分步骤是否正确处理了这些情况。