BDD100K标签处理避坑指南:从JSON到YOLO格式转换,再到类别合并与重排
BDD100K数据集高效处理实战从JSON到YOLO的完整转换与类别优化方案在计算机视觉领域数据准备往往占据项目70%以上的时间成本。BDD100K作为目前规模最大、场景最丰富的自动驾驶数据集之一其包含的10万张标注图像是训练高性能目标检测模型的宝贵资源。但当我们需要将其适配到具体业务场景时原始数据格式与类别体系往往无法直接使用——这就是为什么每个CV工程师都需要掌握BDD100K的深度处理技巧。1. 环境准备与数据概览处理BDD100K数据集前需要搭建合适的开发环境。推荐使用Python 3.8配合以下核心库pip install numpy pandas opencv-python tqdm pycocotools数据集目录结构通常如下bdd100k/ ├── images/ │ ├── 100k/ │ │ ├── train/ │ │ ├── val/ │ │ └── test/ # 注意测试集无标注 └── labels/ └── bdd100k_labels.json # 所有标注的聚合文件原始类别分布呈现长尾特征高频类别car26万、person3.3万低频类别train不足100、motor约2000提示建议在处理前先用pandas分析原始JSON的类别分布这将直接影响后续的合并策略2. JSON到YOLO格式的精准转换BDD100K的标注采用单JSON聚合格式与YOLO要求的每图单独TXT存在显著差异。以下转换脚本解决了三个关键问题坐标归一化将绝对坐标转换为YOLO格式的相对坐标文件映射建立图片文件名到标注的准确对应类别过滤在转换阶段即可排除不需要的类别import json from pathlib import Path def convert_bdd_to_yolo(json_path, output_dir, class_map): with open(json_path) as f: data json.load(f) for img in data: txt_path Path(output_dir) / f{img[name].split(.)[0]}.txt with open(txt_path, w) as f_txt: for frame in img[frames]: for obj in frame[objects]: if obj[category] not in class_map: continue # 坐标转换核心逻辑 x_center (obj[box2d][x1] obj[box2d][x2]) / 2 / 1280 y_center (obj[box2d][y1] obj[box2d][y2]) / 2 / 720 width (obj[box2d][x2] - obj[box2d][x1]) / 1280 height (obj[box2d][y2] - obj[box2d][y1]) / 720 f_txt.write(f{class_map[obj[category]]} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}\n)常见报错及解决方案坐标越界添加max(0, min(1, value))约束空标签文件保留空文件避免训练时索引错乱编码问题统一使用utf-8编码3. 智能类别合并策略实际业务中常需要将BDD100K的10个原始类别合并为更紧凑的体系。以自动驾驶场景为例典型合并方案原始类别合并后类别业务逻辑person, riderpedestrian所有行人类型统一car, bus, truckvehicle机动车辆归为一类traffic lightsignal交通信号单独处理traffic signsign道路标志单独保留实现类合并需要特别注意ID连续性合并后类别ID必须从0开始连续样本平衡避免某些合并类别样本过少属性兼容合并类别的视觉特征应尽量接近# 类别合并映射表 CLASS_MERGE_MAP { person: 0, rider: 0, # 合并到pedestrian car: 1, bus: 1, # 合并到vehicle truck: 1, traffic light: 2, traffic sign: 3 } # 应用合并 def merge_classes(input_txt, output_txt): with open(input_txt) as f_in, open(output_txt, w) as f_out: for line in f_in: parts line.strip().split() if not parts: continue old_cls parts[0] new_cls CLASS_MERGE_MAP.get(old_cls, -1) if new_cls 0: f_out.write(f{new_cls} { .join(parts[1:])}\n)4. 数据质量验证体系完成格式转换和类别合并后必须进行系统验证分布检查统计各类别样本数量from collections import Counter def check_distribution(label_dir): counter Counter() for txt_file in Path(label_dir).glob(*.txt): with open(txt_file) as f: for line in f: cls_id line.split()[0] counter[cls_id] 1 print(counter.most_common())可视化验证随机抽样检查标注准确性import cv2 import random def visualize_sample(image_dir, label_dir): img_files list(Path(image_dir).glob(*.jpg)) sample random.choice(img_files) img cv2.imread(str(sample)) txt_path Path(label_dir) / f{sample.stem}.txt with open(txt_path) as f: for line in f: cls_id, xc, yc, w, h map(float, line.split()) # 转换为绝对坐标绘制 x1 int((xc - w/2) * img.shape[1]) y1 int((yc - h/2) * img.shape[0]) x2 int((xc w/2) * img.shape[1]) y2 int((yc h/2) * img.shape[0]) cv2.rectangle(img, (x1,y1), (x2,y2), (0,255,0), 2) cv2.imshow(Sample, img) cv2.waitKey(0)完整性检查确保每个图片都有对应的标签文件def check_missing_files(image_dir, label_dir): img_stems {p.stem for p in Path(image_dir).glob(*.jpg)} label_stems {p.stem for p in Path(label_dir).glob(*.txt)} missing_labels img_stems - label_stems if missing_labels: print(f警告{len(missing_labels)}张图片缺少标注)5. 高级处理技巧对于专业用户这些进阶技巧能进一步提升数据质量样本均衡策略过采样稀有类别基于图像复杂度进行加权采样# 基于类别权重的采样 def get_sample_weights(labels): class_counts np.bincount(labels) weights 1. / class_counts return weights[labels]困难样本挖掘# 检测小目标占比高的样本 def find_hard_samples(label_dir, size_thresh0.03): hard_samples [] for txt_file in Path(label_dir).glob(*.txt): small_objs 0 total_objs 0 with open(txt_file) as f: for line in f: _, _, _, w, h map(float, line.split()) if w * h size_thresh**2: small_objs 1 total_objs 1 if total_objs 0 and small_objs / total_objs 0.5: hard_samples.append(txt_file.stem) return hard_samples数据增强配套确保几何变换与标注同步更新特别注意遮挡关系的合理性# 标注与图像同步旋转 def rotate_image_and_boxes(image, boxes, angle): h, w image.shape[:2] M cv2.getRotationMatrix2D((w/2,h/2), angle, 1) img_rot cv2.warpAffine(image, M, (w,h)) # 转换标注坐标 boxes_rot [] for box in boxes: # 旋转框计算逻辑... boxes_rot.append(rotated_box) return img_rot, boxes_rot处理完的数据集建议按YOLO标准结构组织dataset/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ └── labels/ ├── train/ ├── val/ └── test/配套的YAML配置文件示例train: ../dataset/images/train val: ../dataset/images/val nc: 4 # 合并后的类别数 names: [pedestrian, vehicle, signal, sign]