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格式效率最高。至于怎么转,上面代码直接拿去用就行。