4、标注格式详解:Pascal VOC格式、YOLO txt格式、COCO格式对比与转换

做目标检测这几年,我接触最多的就是这三种标注格式。说实话,刚入行那会儿我也被这些格式搞得晕头转向——明明都是框,怎么存法完全不一样?今天咱们就把它们掰开揉碎了讲清楚。

4.1 Pascal VOC格式:XML老大哥

Pascal VOC格式,说白了就是用XML文件存标注信息。每个图片对应一个同名的XML文件。我个人习惯叫它「XML格式」,因为它的核心就是那个.xml文件。

核心特点:每个图片一个XML,框坐标是左上角和右下角(xmin, ymin, xmax, ymax)。

来看个实际例子。假设你有一张图片叫dog_001.jpg,那它的标注文件就是dog_001.xml。内容长这样:

<annotation>
    <folder>VOC2007</folder>
    <filename>dog_001.jpg</filename>
    <size>
        <width>640</width>
        <height>480</height>
        <depth>3</depth>
    </size>
    <object>
        <name>dog</name>
        <bndbox>
            <xmin>100</xmin>
            <ymin>50</ymin>
            <xmax>300</xmax>
            <ymax>200</ymax>
        </bndbox>
    </object>
    <object>
        <name>cat</name>
        <bndbox>
            <xmin>350</xmin>
            <ymin>100</ymin>
            <xmax>500</xmax>
            <ymax>300</ymax>
        </bndbox>
    </object>
</annotation>

嗯,这里要注意:坐标是绝对值,单位是像素。图片宽640高480,那xmin=100就是距离左边100个像素。

我的经验:VOC格式最大的好处是「人类可读」。你打开XML文件,一眼就能看出框在哪、是什么类别。调试的时候特别方便。我曾经在项目里用VOC格式做数据校验,因为它的结构清晰,写脚本解析起来几乎不出错。

4.2 YOLO txt格式:轻量级选手

YOLO格式就简单粗暴多了。每个图片对应一个.txt文件,文件名和图片名一样(只是扩展名不同)。每行代表一个目标,格式是:

class_id x_center y_center width height

注意!这里的坐标都是归一化后的相对值。什么意思?就是所有值都在0到1之间。x_center = 框中心x坐标 / 图片宽度。y_center、width、height同理。

还是上面那个例子,YOLO格式长这样:

0 0.3125 0.2604 0.3125 0.3125
1 0.6641 0.4167 0.2344 0.4167

等等,这数字怎么来的?我算给你看:

  • 第一个框:x_center = (100+300)/2 / 640 = 200/640 = 0.3125
  • y_center = (50+200)/2 / 480 = 125/480 = 0.2604
  • width = (300-100) / 640 = 200/640 = 0.3125
  • height = (200-50) / 480 = 150/480 = 0.3125

避坑指南:我曾经在转换格式时犯过一个低级错误——忘了归一化。直接把像素坐标写进YOLO文件,结果训练出来的模型框全飞到天上去。排查了整整一天才发现问题。所以记住:YOLO格式必须归一化!

YOLO格式还有个特点:没有类别名称,只有类别ID。你需要额外维护一个classes.txt文件,里面按顺序写类别名。比如:

dog
cat
person
car

那ID 0就是dog,ID 1就是cat,以此类推。

4.3 COCO格式:JSON大集合

COCO格式把所有标注信息塞进一个JSON文件里。结构稍微复杂点,但功能强大。它主要包含五个字段:

字段 说明
images 所有图片信息(id, file_name, width, height)
annotations 所有标注框(id, image_id, category_id, bbox, area, iscrowd)
categories 类别列表(id, name, supercategory)

COCO的bbox格式是[x, y, width, height],注意!这里的x和y是框左上角坐标,不是中心点。而且坐标是绝对值(像素值)。

举个例子:

{
    "images": [
        {"id": 1, "file_name": "dog_001.jpg", "width": 640, "height": 480}
    ],
    "annotations": [
        {
            "id": 1,
            "image_id": 1,
            "category_id": 1,
            "bbox": [100, 50, 200, 150],
            "area": 30000,
            "iscrowd": 0
        },
        {
            "id": 2,
            "image_id": 1,
            "category_id": 2,
            "bbox": [350, 100, 150, 200],
            "area": 30000,
            "iscrowd": 0
        }
    ],
    "categories": [
        {"id": 1, "name": "dog", "supercategory": "animal"},
        {"id": 2, "name": "cat", "supercategory": "animal"}
    ]
}

我的建议:如果你做的是大规模数据集(比如上万张图),COCO格式最合适。一个JSON文件搞定所有,加载速度快。但如果你只是小项目调试,VOC格式更直观。YOLO格式则是最轻量的,训练时读取效率最高。

4.4 三种格式对比总结

对比项 Pascal VOC YOLO txt COCO JSON
文件形式 每图一个XML 每图一个txt 所有图一个JSON
坐标格式 左上+右下 (xmin,ymin,xmax,ymax) 中心+宽高 (x_center,y_center,w,h) 左上+宽高 (x,y,w,h)
坐标值 绝对值(像素) 归一化(0~1) 绝对值(像素)
类别表示 类别名称(字符串) 类别ID(整数) 类别ID(整数)
可读性 ★★★★★ ★★☆☆☆ ★★★☆☆
读取效率 ★★☆☆☆ ★★★★★ ★★★★☆

4.5 格式转换实战

你想想看,实际项目中经常需要在这三种格式之间来回切换。比如你从网上下载的数据集是VOC格式,但你要用YOLO训练,那就得转。我写了个通用的转换脚本,核心逻辑就几步:

VOC转YOLO:

def voc_to_yolo(xml_file, classes):
    tree = ET.parse(xml_file)
    root = tree.getroot()
    width = int(root.find('size/width').text)
    height = int(root.find('size/height').text)
    
    yolo_lines = []
    for obj in root.findall('object'):
        class_name = obj.find('name').text
        class_id = classes.index(class_name)
        
        xmin = int(obj.find('bndbox/xmin').text)
        ymin = int(obj.find('bndbox/ymin').text)
        xmax = int(obj.find('bndbox/xmax').text)
        ymax = int(obj.find('bndbox/ymax').text)
        
        # 转成YOLO格式(归一化)
        x_center = (xmin + xmax) / 2.0 / width
        y_center = (ymin + ymax) / 2.0 / height
        w = (xmax - xmin) / width
        h = (ymax - ymin) / height
        
        yolo_lines.append(f"{class_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}")
    
    return yolo_lines

YOLO转COCO:

def yolo_to_coco(txt_file, img_width, img_height, classes):
    coco_annotations = []
    with open(txt_file, 'r') as f:
        for line in f.readlines():
            parts = line.strip().split()
            class_id = int(parts[0])
            x_center = float(parts[1]) * img_width
            y_center = float(parts[2]) * img_height
            w = float(parts[3]) * img_width
            h = float(parts[4]) * img_height
            
            # 转成COCO格式(左上角+宽高)
            x = x_center - w / 2
            y = y_center - h / 2
            
            coco_annotations.append({
                'bbox': [x, y, w, h],
                'category_id': class_id + 1,  # COCO类别ID从1开始
                'area': w * h
            })
    
    return coco_annotations

注意:COCO的类别ID通常从1开始,而YOLO的类别ID从0开始。转换时别忘了+1。我有个同事就因为这个bug,模型训练了两天发现类别全对不上,那叫一个崩溃。

嗯,到这里三种格式就讲清楚了。说白了,格式只是数据的「包装方式」,核心信息都一样——框的位置和类别。你只要理解了坐标的「绝对值vs归一化」、「左上角vs中心点」这两个关键区别,转换起来就游刃有余了。

我个人建议:小项目用VOC方便调试,大项目用COCO方便管理,训练时用YOLO格式效率最高。至于怎么转,上面代码直接拿去用就行。