coco数据集转voc数据集

image

前言

在模型训练的学习中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格式。
image

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之间有一些差异,而这些差异就是在转换过程中需要适配的。总的来说包括:

  1. coco数据集是1:n,一个json文件保存所有标注信息,对应n张图片;voc数据集是n:n,每一个图片对应一个xml的标注文件
  2. 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}]}
posted @ 2024-05-22 09:51  金色旭光  阅读(318)  评论(0编辑  收藏  举报