1. BDD100K数据集与YOLO格式简介
当你第一次接触BDD100K数据集时,可能会被它的JSON标签格式搞得一头雾水。这个由伯克利大学发布的自动驾驶数据集包含10万个视频片段,覆盖了各种天气、光照和道路场景,是训练目标检测模型的绝佳资源。但问题来了——YOLOv5/YOLOv8需要的是TXT格式的标签,每个文件对应一张图片,记录着归一化的目标位置和类别。
我刚开始用这个数据集时,花了一整天研究如何转换格式。后来发现,其实核心就是三步:解析JSON里的box2d坐标、按需筛选类别、将绝对坐标转为相对坐标。举个例子,JSON中一个"car"的坐标可能是{"x1": 320, "y1": 240, "x2": 480, "y2": 360},而YOLO需要的则是"0 0.5 0.5 0.25 0.25"这样的格式(假设类别car对应数字0)。
2. 环境准备与数据目录结构
在开始转换前,建议按这个结构组织你的文件夹:
bdd100k/ ├── images/ │ ├── train/ │ └── val/ └── labels/ ├── det_annotations/ # 原始JSON标签 │ ├── train/ │ └── val/ └── yolo_labels/ # 转换后的TXT标签 ├── train/ └── val/需要安装的Python包其实很简单:
pip install json numpy tqdm我建议用tqdm加个进度条,特别是处理几万张图片时,看着进度条慢慢走完会有种莫名的治愈感。曾经有一次我没用进度条,程序跑了半小时我都不知道是否卡死了,最后忍不住强制终止,结果发现已经处理完了...
3. JSON标签解析实战
BDD100K的JSON结构比COCO复杂些,关键是要找到"frames"下的"objects"数组。每个对象包含:
- category:类别名称(如"car")
- box2d:包含x1,y1,x2,y2的边界框
- attributes:可能有交通灯颜色等额外属性
这里有个坑:不同版本的BDD100K标签结构略有差异。有次我拿到的版本里,"frames"变成了"labels",害我debug了好久。建议先用这个代码检查结构:
import json with open("sample.json") as f: data = json.load(f) print(json.dumps(data, indent=2)[:500]) # 打印前500字符4. 类别筛选与映射策略
BDD100K有13个基础类别,但你可能只需要其中几个。我整理了这个对照表:
| BDD原始类别 | 保留标记 | YOLO类别ID |
|---|---|---|
| car | ✓ | 0 |
| pedestrian | ✗ | - |
| traffic light | ✓ | 1 |
| ... | ... | ... |
实现时建议用字典管理类别映射:
CLASS_MAP = { "car": 0, "traffic light": 1, "traffic sign": 2 }处理交通灯时要特别注意,颜色信息藏在attributes里:
if obj["category"] == "traffic light": color = obj["attributes"]["trafficLightColor"] class_name = f"tl_{color}" # 如tl_red5. 坐标归一化关键细节
这是最容易出错的部分!YOLO要求的是中心点坐标和宽高,且必须是相对于图像尺寸的比例值。计算公式如下:
def normalize_bbox(x1, y1, x2, y2, img_w=1280, img_h=720): x_center = (x1 + x2) / 2 / img_w y_center = (y1 + y2) / 2 / img_h width = (x2 - x1) / img_w height = (y2 - y1) / img_h return x_center, y_center, width, height注意BDD100K默认分辨率是1280x720,但有些图片可能不同。安全起见,可以检查JSON中的"resolution"字段。我曾经因为忽略这个,导致小目标检测完全失效——所有框都偏移了20%!
6. 完整代码实现与优化
结合上述要点,这是优化后的转换代码:
import json import os from tqdm import tqdm class BDD2YOLO: def __init__(self): self.class_map = { "car": 0, "traffic light": 1, "traffic sign": 2, "person": 3 } def convert(self, json_path, output_dir): os.makedirs(output_dir, exist_ok=True) with open(json_path) as f: data = json.load(f) filename = data["name"] lines = [] for frame in data["frames"]: for obj in frame["objects"]: category = obj["category"] if category not in self.class_map: continue # 处理交通灯颜色 if category == "traffic light": color = obj["attributes"].get("trafficLightColor", "none") category = f"tl_{color}" # 坐标归一化 box = obj["box2d"] xc, yc, w, h = self.normalize_bbox( box["x1"], box["y1"], box["x2"], box["y2"] ) lines.append( f"{self.class_map[category]} {xc:.6f} {yc:.6f} {w:.6f} {h:.6f}\n" ) # 保存结果 if lines: with open(f"{output_dir}/{filename}.txt", "w") as f: f.writelines(lines) @staticmethod def normalize_bbox(x1, y1, x2, y2, img_w=1280, img_h=720): xc = (x1 + x2) / 2 / img_w yc = (y1 + y2) / 2 / img_h w = (x2 - x1) / img_w h = (y2 - y1) / img_h return xc, yc, w, h # 批量处理 converter = BDD2YOLO() json_dir = "bdd100k/labels/det_annotations/train" output_dir = "bdd100k/labels/yolo_labels/train" for filename in tqdm(os.listdir(json_dir)): converter.convert( f"{json_dir}/{filename}", output_dir )这个版本比原始代码增加了错误处理、进度显示和更清晰的代码结构。用类封装也让后续维护更方便。
7. 验证与调试技巧
转换完成后,强烈建议做这些检查:
- 空文件检查:有些图片可能没有目标,确保对应的TXT文件为空或不存在
- 坐标范围验证:所有坐标值应在[0,1]范围内
- 可视化验证:用OpenCV画框检查是否对齐
这里有个可视化脚本片段:
import cv2 def visualize(img_path, label_path, class_names): img = cv2.imread(img_path) h, w = img.shape[:2] with open(label_path) as f: for line in f: cls_id, xc, yc, bw, bh = map(float, line.split()) 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[int(cls_id)], (x1,y1-5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0,0,255), 1) cv2.imshow("Preview", img) cv2.waitKey(0)8. 性能优化与批量处理
当处理10万级数据时,我有几个提速心得:
- 多进程处理:Python的multiprocessing模块能大幅提升速度
from multiprocessing import Pool def process_file(filename): converter.convert(f"{json_dir}/{filename}", output_dir) with Pool(8) as p: # 8个进程 list(tqdm(p.imap(process_file, os.listdir(json_dir)), total=len(os.listdir(json_dir))))- 缓存机制:如果中断后重新开始,可以跳过已处理的文件
- SSD存储:机械硬盘处理小文件速度会慢很多
最后提醒:转换完成后,记得在YOLO的配置文件中正确设置类别数和类别名称。比如在data.yaml中:
names: ["car", "traffic light", "traffic sign", "person"]