coco数据集转voc数据集
前言
在模型训练的学习中voc和coco是最常见的两种格式,并且经常需要互相转换,本篇提供coco数据集转voc数据集的方法。
coco 格式分析
COCO的 全称是Common Objects in COntext,是微软团队提供的一个可以用来进行图像识别的数据集。MS COCO数 据集中的图像分为训练、验证和测试集。
假设有以下两个图像文件:
- image1.jpg
- image2.jpg
coco格式数据集:annotations.json
{
"images": [
{
"id": 1,
"file_name": "image1.jpg",
"width": 640,
"height": 480
},
{
"id": 2,
"file_name": "image2.jpg",
"width": 800,
"height": 600
}
],
"annotations": [
{
"id": 1,
"image_id": 1,
"category_id": 1,
"bbox": [50, 50, 100, 100],
"area": 10000,
"segmentation": [
[
50, 50, 50, 150, 150, 50
]
],
"iscrowd": 0
},
{
"id": 2,
"image_id": 2,
"category_id": 2,
"bbox": [150, 200, 200, 150],
"area": 30000,
"segmentation": [
[
150, 200, 150, 350, 350, 200
]
],
"iscrowd": 0
}
],
"categories": [
{
"id": 1,
"name": "cat",
"supercategory": "animal"
},
{
"id": 2,
"name": "dog",
"supercategory": "animal"
}
]
}
coco 数据集字段解析
coco 数据集是一个json文件,一共包括5个部分。
{
"info": info, # 数据集的基本信息
"licenses": [license], # 许可证
"images": [image], # 图片信息,名字和宽高
"annotations": [annotation], # 标注信息
"categories": [category] # 标签信息
}
info{ # 数据集信息描述
"year": int, # 数据集年份
"version": str, # 数据集版本
"description": str, # 数据集描述
"contributor": str, # 数据集提供者
"url": str, # 数据集下载链接
"date_created": datetime, # 数据集创建日期
}
license{
"id": int,
"name": str,
"url": str,
}
image{ # images是一个list,存放所有图片(dict)信息。image是一个dict,存放单张图片信息
"id": int, # 图片的ID编号(每张图片ID唯一)
"width": int, # 图片宽
"height": int, # 图片高
"file_name": str, # 图片名字
"license": int, # 协议
"flickr_url": str, # flickr链接地址
"coco_url": str, # 网络连接地址
"date_captured": datetime, # 数据集获取日期
}
annotation{ # annotations是一个list,存放所有标注(dict)信息。annotation是一个dict,存放单个目标标注信息。
"id": int, # 目标对象ID(每个对象ID唯一),每张图片可能有多个目标
"image_id": int, # 对应图片ID
"category_id": int, # 对应类别ID,与categories中的ID对应
"segmentation": RLE or [polygon], # 实例分割,对象的边界点坐标[x1,y1,x2,y2,....,xn,yn]
"area": float, # 对象区域面积
"bbox": [xmin,ymin,width,height], # 目标检测,对象定位边框[x,y,w,h]
"iscrowd": 0 or 1, # 表示是否是人群
}
categories{ # 类别描述
"id": int, # 类别对应的ID(0默认为背景)
"name": str, # 子类别名字
"supercategory": str, # 主类别名字
}
需要注意的是coco数据集标注的坐标。xmin ymin width height和voc有很大差异,分别代表:
- xmin 左上角x轴坐标
- ymin 左上角y轴坐标
- width 图片像素宽
- heidht 图片像素高
voc 格式分析
voc 全称 The PASCAL Visual Object Classes,它由Visual Object Classes(可视对象类)和挑战(Challenge)等竞赛项目开发, 开始于2005年,结束于2012年最后一届 。
VOC数据集包含许多不同类型的图像,每个图像都标注了一些可视对象,如人,汽车,狗等。这些标注包括每个对象的位置,大小和类别等信息。
常见的voc数据集是voc2007 和voc 2012,当然在模型训练过程肯定都会自己标注数据集,导出为voc格式。
voc 数据集的格式:
<annotation>
<folder>17</folder> # 图片所处文件夹
<filename>77258.bmp</filename> # 图片名
<path>~/frcnn-image/61/ADAS/image/frcnn-image/17/77258.bmp</path>
<source> #图片来源相关信息
<database>Unknown</database>
</source>
<size> #图片尺寸
<width>640</width>
<height>480</height>
<depth>3</depth>
</size>
<segmented>0</segmented> #是否有分割label
<object> 包含的物体
<name>car</name> #物体类别
<pose>Unspecified</pose> #物体的姿态
<truncated>0</truncated> #物体是否被部分遮挡(>15%)
<difficult>0</difficult> #是否为难以辨识的物体, 主要指要结体背景才能判断出类别的物体。虽有标注, 但一般忽略这类物体
<bndbox> #物体的bound box
<xmin>2</xmin> #左
<ymin>156</ymin> #上
<xmax>111</xmax> #右
<ymax>259</ymax> #下
</bndbox>
</object>
</annotation>
重要的信息包括:filename
, size
, object
等。除此之外,还有一个主要注意的点就是标注的坐标,xmin,ymin,xmax,ymax是标注的四个角,分别代表:
- xmin: 左上角x轴坐标
- ymin:左上角y周坐标
- xmax: 右下角x轴坐标
- ymax:右下角y轴坐标
转换脚本
coco和voc之间有一些差异,而这些差异就是在转换过程中需要适配的。总的来说包括:
- coco数据集是1:n,一个json文件保存所有标注信息,对应n张图片;voc数据集是n:n,每一个图片对应一个xml的标注文件
- coco数据集将标注框信息、图片元数据分开保存,用id关联。而voc数据集图片元数据和标注信息放在一起
知道了两者之间的差异,coco转voc的基本思路就呼之欲出。整体思路大概是:从coco数据集中解析出图片信息,以图片为基本单位找到对应的标注信息。遍历所有图片,每一个图片生成一个xml文件,保存图片的元数据和标注框。
import json
import os
import shutil
from collections import defaultdict
from xml.etree.ElementTree import Element, SubElement, tostring
class CocoToVoc:
def __init__(self, voc_gt_dir: str, coco_json_path: str) -> None:
self.voc_gt_dir = voc_gt_dir
self.coco_json_path = coco_json_path
self.categories_dict = {}
self.annotations_dict = defaultdict(list)
def create_voc_annotations(self) -> None:
if os.path.exists(self.voc_gt_dir):
shutil.rmtree(self.voc_gt_dir)
os.makedirs(self.voc_gt_dir)
with open(self.coco_json_path, 'r') as f:
coco_data = json.load(f)
self.categories_dict = {label["id"]: label["name"] for label in coco_data['categories']}
for ann in coco_data['annotations']:
self.annotations_dict[ann["image_id"]].append(ann)
for image_info in coco_data['images']:
image_id = image_info['id']
annotations = self.annotations_dict[image_id]
xml_content = self.create_xml_annotation(image_info, annotations)
xml_filename = os.path.splitext(image_info['file_name'])[0] + '.xml'
xml_filepath = os.path.join(self.voc_gt_dir, xml_filename)
with open(xml_filepath, 'w') as f:
f.write(xml_content)
@staticmethod
def indent(elem: Element, level: int = 0) -> None:
"""xml保存美化"""
i = "\n" + level * "\t"
if len(elem):
if not elem.text or not elem.text.strip():
elem.text = i + "\t"
if not elem.tail or not elem.tail.strip():
elem.tail = i
for elem in elem:
CocoToVoc.indent(elem, level + 1)
if not elem.tail or not elem.tail.strip():
elem.tail = i
else:
if level and (not elem.tail or not elem.tail.strip()):
elem.tail = i
def create_xml_annotation(self, image_info: Element, annotations: list) -> Element:
root = Element('annotation')
# 图片基础信息
folder = SubElement(root, 'folder')
folder.text = 'VOC2007'
filename = SubElement(root, 'filename')
filename.text = image_info['file_name']
path = SubElement(root, 'path')
path.text = os.path.join(image_info['file_name'])
# 图片宽高信息
size = SubElement(root, 'size')
width = SubElement(size, 'width')
width.text = str(image_info['width'])
height = SubElement(size, 'height')
height.text = str(image_info['height'])
depth = SubElement(size, 'depth')
depth.text = '3' # Assuming RGB images
segmented = SubElement(root, 'segmented')
segmented.text = '0'
# 标注信息
for ann in annotations:
obj = SubElement(root, 'object')
name = SubElement(obj, 'name')
name.text = self.categories_dict.get(ann['category_id'])
bndbox = SubElement(obj, 'bndbox')
xmin = SubElement(bndbox, 'xmin')
xmin.text = str(ann['bbox'][0])
ymin = SubElement(bndbox, 'ymin')
ymin.text = str(ann['bbox'][1])
xmax = SubElement(bndbox, 'xmax')
xmax.text = str(ann['bbox'][0] + ann['bbox'][2])
ymax = SubElement(bndbox, 'ymax')
ymax.text = str(ann['bbox'][1] + ann['bbox'][3])
self.indent(root)
return tostring(root, encoding='unicode', method='xml')
if __name__ == '__main__':
voc_gt_dir, coco_json_path = "./gt", "./coco-annotations.json"
coco_to_voc = CocoToVoc(voc_gt_dir, coco_json_path)
coco_to_voc.create_voc_annotations()
示例coco数据集
可以使用该数据集作为测试
coco-annotations.json
{
"images": [
{"id": 1, "file_name": "2012_004328.jpg", "height": 500, "width": 328},
{"id": 2, "file_name": "2012_004315.jpg", "height": 375, "width": 500},
{"id": 3, "file_name": "2012_004329.jpg", "height": 500, "width": 333},
{"id": 4, "file_name": "2012_004317.jpg", "height": 500, "width": 375},
{"id": 5, "file_name": "2012_004312.jpg", "height": 329, "width": 500},
{"id": 6, "file_name": "2012_004310.jpg", "height": 500, "width": 345},
{"id": 7, "file_name": "2012_004309.jpg", "height": 375, "width": 500},
{"id": 8, "file_name": "2012_004326.jpg", "height": 375, "width": 500},
{"id": 9, "file_name": "2012_004330.jpg", "height": 500, "width": 375},
{"id": 10, "file_name": "2012_004319.jpg", "height": 312, "width": 500},
{"id": 11, "file_name": "2012_004331.jpg", "height": 375, "width": 500}],
"categories": [ {"id": 1, "name": "person"}],
"annotations": [
{"id": 1, "image_id": 1, "category_id": 1, "bbox": [59, 220, 107, 195], "iscrowd": 0},
{"id": 2, "image_id": 1, "category_id": 1, "bbox": [219, 226, 49, 106], "iscrowd": 0},
{"id": 3, "image_id": 2, "category_id": 1, "bbox": [392, 239, 81, 136], "iscrowd": 0},
{"id": 4, "image_id": 3, "category_id": 1, "bbox": [57, 88, 227, 309], "iscrowd": 0},
{"id": 5, "image_id": 4, "category_id": 1, "bbox": [64, 131, 106, 179], "iscrowd": 0},
{"id": 6, "image_id": 5, "category_id": 1, "bbox": [152, 22, 188, 258], "iscrowd": 0},
{"id": 7, "image_id": 6, "category_id": 1, "bbox": [71, 7, 274, 493], "iscrowd": 0},
{"id": 8, "image_id": 7, "category_id": 1, "bbox": [110, 212, 188, 163], "iscrowd": 0},
{"id": 9, "image_id": 8, "category_id": 1, "bbox": [245, 31, 250, 344], "iscrowd": 0},
{"id": 10, "image_id": 9, "category_id": 1, "bbox": [230, 133, 140, 308], "iscrowd": 0},
{"id": 11, "image_id": 10, "category_id": 1, "bbox": [274, 82, 27, 55], "iscrowd": 0},
{"id": 12, "image_id": 11, "category_id": 1, "bbox": [102, 25, 106, 205], "iscrowd": 0}]}