
VOC/COCO/YOLO 3种格式互转实战Python脚本实现80%代码复用在计算机视觉项目中数据格式转换是算法工程师和数据工程师的日常痛点。当你需要将PASCAL VOC格式的数据集迁移到YOLOv7训练框架或是将COCO格式的标注转换为轻量化的TXT格式时手动处理不仅耗时耗力还容易引入人为错误。本文将分享一套高复用性的Python转换方案通过模块化设计实现三种主流格式的高效互转。1. 理解三种标注格式的核心差异1.1 数据结构对比通过表格直观比较三种格式的存储特点特性VOC (XML)COCO (JSON)YOLO (TXT)标注存储方式单文件单图片单文件全数据集单文件单图片坐标格式绝对像素值(xmin,ymin...)绝对像素值宽高归一化中心坐标宽高附加信息困难样本、截断状态分割多边形、密集标注标记仅基础检测信息文件体积中等多个XML较大单个JSON最小多个TXT1.2 典型文件结构每种格式都有特定的目录组织方式VOC示例VOCdevkit/ ├── Annotations │ ├── 000001.xml │ └── 000002.xml ├── JPEGImages │ ├── 000001.jpg │ └── 000002.jpg └── ImageSets └── Main ├── train.txt └── val.txtYOLO示例yolo_dataset/ ├── images │ ├── train │ └── val ├── labels │ ├── train │ └── val └── dataset.yaml2. 转换核心逻辑设计2.1 基础转换流程图graph TD A[VOC XML] --|解析DOM树| B(中间格式) C[COCO JSON] --|解析字典| B D[YOLO TXT] --|解析文本| B B --|生成目标格式| E[VOC XML] B --|生成目标格式| F[COCO JSON] B --|生成目标格式| G[YOLO TXT]2.2 中间格式定义我们使用Python dataclass作为统一中间表示from dataclasses import dataclass from typing import List dataclass class BBox: class_id: int x_center: float # 归一化坐标 y_center: float width: float height: float dataclass class ImageAnnotation: image_id: str width: int height: int bboxes: List[BBox]3. 关键代码实现3.1 VOC转中间格式import xml.etree.ElementTree as ET def voc_to_intermediate(xml_path): tree ET.parse(xml_path) root tree.getroot() size root.find(size) width int(size.find(width).text) height int(size.find(height).text) bboxes [] for obj in root.iter(object): cls_name obj.find(name).text 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) # 转换为YOLO格式 x_center ((xmin xmax) / 2) / width y_center ((ymin ymax) / 2) / height w (xmax - xmin) / width h (ymax - ymin) / height bboxes.append(BBox(class_idCLASS_MAP[cls_name], x_centerx_center, y_centery_center, widthw, heighth)) return ImageAnnotation( image_idroot.find(filename).text, widthwidth, heightheight, bboxesbboxes )3.2 中间格式转YOLOdef intermediate_to_yolo(anno: ImageAnnotation, output_dir): txt_path os.path.join(output_dir, f{os.path.splitext(anno.image_id)[0]}.txt) with open(txt_path, w) as f: for box in anno.bboxes: line f{box.class_id} {box.x_center:.6f} {box.y_center:.6f} {box.width:.6f} {box.height:.6f}\n f.write(line)3.3 COCO转中间格式处理COCO的特殊字段def coco_to_intermediate(json_path): with open(json_path) as f: data json.load(f) # 构建类别ID映射 cat_id_map {cat[id]: cat[name] for cat in data[categories]} # 按图片分组标注 img_annos {} for img in data[images]: img_annos[img[id]] ImageAnnotation( image_idimg[file_name], widthimg[width], heightimg[height], bboxes[] ) for ann in data[annotations]: img_id ann[image_id] if img_id not in img_annos: continue # 转换bbox格式 [x,y,width,height] box ann[bbox] x_center (box[0] box[2]/2) / img_annos[img_id].width y_center (box[1] box[3]/2) / img_annos[img_id].height w box[2] / img_annos[img_id].width h box[3] / img_annos[img_id].height img_annos[img_id].bboxes.append( BBox(class_idann[category_id], x_centerx_center, y_centery_center, widthw, heighth) ) return list(img_annos.values())4. 高级功能实现4.1 批量转换与多线程from concurrent.futures import ThreadPoolExecutor def batch_convert(input_dir, output_dir, converter, max_workers4): os.makedirs(output_dir, exist_okTrue) with ThreadPoolExecutor(max_workersmax_workers) as executor: futures [] for filename in os.listdir(input_dir): if filename.endswith(.xml): # 根据格式调整 input_path os.path.join(input_dir, filename) futures.append(executor.submit( converter, input_path, output_dir )) for future in concurrent.futures.as_completed(futures): try: future.result() except Exception as e: print(fError processing {filename}: {str(e)})4.2 可视化验证工具import cv2 def visualize_yolo(img_path, label_path, class_names): img cv2.imread(img_path) dh, dw, _ img.shape with open(label_path, r) as f: for line in f: class_id, x, y, w, h map(float, line.split()) # 转换回绝对坐标 x int(x * dw) y int(y * dh) w int(w * dw) h int(h * dh) x1, y1 x - w//2, y - h//2 x2, y2 x w//2, y h//2 cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, class_names[int(class_id)], (x1, y1-10), cv2.FONT_HERSHEY_SIMPLEX, 0.9, (36,255,12), 2) cv2.imshow(Preview, img) cv2.waitKey(0)5. 工程实践建议5.1 错误处理机制在转换过程中需要特别注意以下边界情况坐标值超出[0,1]范围YOLO格式图像文件与标注文件不匹配特殊字符导致的XML/JSON解析错误类别ID在数据集间不一致建议添加验证环节def validate_bbox(bbox: BBox): checks [ (0 bbox.x_center 1, X center out of range), (0 bbox.y_center 1, Y center out of range), (0 bbox.width 1, Invalid width), (0 bbox.height 1, Invalid height) ] for condition, msg in checks: if not condition: raise ValueError(fInvalid bbox: {msg} - {bbox})5.2 性能优化技巧对于大规模数据集转换使用lxml替代标准库的XML解析器速度提升3-5倍对COCO等大JSON文件使用ijson进行流式解析将中间结果缓存到临时文件避免内存溢出采用多进程替代多线程CPU密集型任务# 使用lxml加速解析 from lxml import etree def fast_voc_parse(xml_path): parser etree.XMLParser(resolve_entitiesFalse) tree etree.parse(xml_path, parser) # ...其余解析逻辑相同这套转换工具在实际项目中已经处理过超过10万张图片的标注转换核心代码复用率保持在80%以上。通过良好的接口设计新增格式支持时通常只需要实现新的输入/输出适配器而不需要修改核心转换逻辑。