VOC 与COCO 格式的相互转换

下面用到的 VOC 和 COCO 是我自己制作的小型数据集,有五张图,分为 car、person、motorcycle 和 dog 四个类,没有语义分割等内容。
代码中可能有许多不成熟的地方或者错误的地方,还请各位教正!

VOC转换成COCO

在转换之前,先来了解json格式中必要的元素:
在这里插入图片描述

  • 重要工具:xml、json
  • 参照 COCO 的格式,我先把图片名称、目标类别列出来,初始化 annotations,categories 等。
import xml.etree.ElementTree as ET
import json

sets = ['z_00001', 'z_00002', 'z_00003', 'z_00004', 'z_00005']
classes = ['car', 'person', 'motorcycle', 'dog']
images = []
annotations = []
categories = []
si = 1
oid = 1
  • 用 for 循环遍历每一张 image 的对应 xml 文件,用 xml 中的工具读入文件、获取有用信息。
  • 读取<size>部分,用.find().text获取<width>等的文本信息,将其变为int型后存入images中,同时每一张image有特定的 id
  • 遍历<object>、获取并储存 annotation,过程与上一步相似。每一个目标有对应的 image_id 和 category_id,并且有自己的 id
for file in os.listdir(ann_filepath):
    #   file_name = 'D:/Datasets/My_datas/VOC/Annotations/%s.xml' % set
    in_file = open(ann_filepath + '\\' + file, encoding='utf-8')
    tree = ET.parse(in_file)
    root = tree.getroot()
    file_name = root.find('filename').text

    # 输入img大小,输出image[]
    for img_size in root.iter('size'):
        width = int(float(img_size.find('width').text))
        height = int(float(img_size.find('height').text))
        depth = int(float(img_size.find('depth').text))
        # 收集一张img内所有obj
        image = {'file_name': file_name, 'width': width, 'height': height, 'depth': depth, 'id': si}
        images.append(image)

    # 得到关于obj位置,从而得到obj关键信息,输出annotations[]
    for obj in root.iter('object'):

        # difficult = 0
        # if obj.find('difficult') != None:
        #     difficult = obj.find('difficult').text

        # 得到obj类别
        cls = obj.find('name').text
        if cls not in COCO_NAMES:
            print('not in')
            continue

        cls_id = COCO_NAMES.index(cls)
        # 找到box位置,得到左下、右上角xy坐标
        xmlbox = obj.find('bndbox')
        bbox_ann = [int(float(xmlbox.find('xmin').text)), int(float(xmlbox.find('ymin').text)),
                int(float(xmlbox.find('xmax').text)), int(float(xmlbox.find('ymax').text))]

        bbox = [(bbox_ann[2] + bbox_ann[0]) / 2, (bbox_ann[3] + bbox_ann[1]) / 2, bbox_ann[2] - bbox_ann[0],
                bbox_ann[3] - bbox_ann[1]]
        print(bbox)
        area = bbox[2] * bbox[3]
        print(area)

        iscrowd=0
        annotation = {'image_id': si, 'category_id': cls_id + 1, 'id': oid,'area':area,
                      'iscrowd':iscrowd,'bbox': bbox}  # category_id要加一,因为cls_id=0是背景
        # print(annotation)
        annotations.append(annotation)
        oid += 1
    si += 1
print('共%s张图' % (si - 1))

for cls in COCO_NAMES:
    cls_id = COCO_NAMES.index(cls)
    categorie = {'id': cls_id + 1, 'name': cls}
    categories.append(categorie)

Annotation = {'images': images, 'annotations': annotations, 'categories': categories}
with open(save_path, 'w', encoding='utf-8') as f:
    f.write(json.dumps(Annotation, indent=4, ensure_ascii=False))
# print(Annotation)

  • 类别模块,cls_id要加一,因为cls_id=0是背景
for cls in classes:
    cls_id = classes.index(cls)
    categorie = {'id': cls_id + 1, 'name': cls}
    categories.append(categorie)
  • Annotation 收集所有模块,写入到文件中。
    由于个人感觉原 xml 文件中并不是所有东西都有用,所以有’偷工减料’(annotations的area和 iscrowd不能‘偷工减料’,不然验证的时候可能会报错)。
Annotation = {'images': images, 'annotations': annotations, 'categories': categories}
with open('。/My_datas/COCO/ann.json', 'w', encoding='utf-8') as f:
    f.write(json.dumps(Annotation, indent=4, ensure_ascii=False))
print(Annotation)

COCO转换成VOC

这里我用到了 pycocotools,这是一个读取 COCO 内部信息的工具:

  • COCO() 的作用是读取 json 内部信息
  • .imgs.values() 返回 images 模块信息
  • .cats.values() 返回 images 中的每一个目标的分类
    如下图 coco.py 的内部截图
    在这里插入图片描述
  • .imgToAnns.values() 返回 annotations 模块信息
    在这里插入图片描述
    输入如下:
from pycocotools.coco import COCO
import xml.dom
import xml

info = r'./My_datas/COCO/ann.json'
coco = COCO(info)

imgs = coco.imgs.values()
oi = coco.catToImgs  # .values含第category_id类的图片的image_id
cats = coco.cats.values()
anns = coco.imgToAnns.values()
anns = sum(anns, [])
print('imgs.values():\n%s' % imgs, '\n', 'catToImgs:\n%s' % oi, '\n',
      'cats.values():\n%s' % cats, '\n', 'annotations:\n%s' % anns)

输出如下图:
在这里插入图片描述
sum(anns, [ ]) 的作用是去掉 anns 中的 [ ] ,否则输出如下,在后面迭代时报错.
在这里插入图片描述
在进行COCO转换成VOC 格式之前,我想先将每张image中的目标进行分类并保存,即建立 ImageSets/Main 的 .txt 文件,因为在制作自己的小数据集时没有进行这样的分类操作,在这里可以顺便做一下。代码如下:

cat_name = []
for cat in cats:
    n = cat['name']
    cat_name.append(n)
    print(n)

for i in oi:
    ci = int(float(i)) - 1  # 为了避免类别对不上号,不直接用len(oi)
    list_file = './My_datas/VOC/ImageSets/Main/%s.txt' % (cat_name[ci])
    with open(list_file, 'w', encoding='utf-8') as f:
        si = oi[i]
        for img in imgs:
            if img['id'] in si:
                file_name = img['file_name'].strip('.jpg')
                f.write(file_name)
                f.write('\n')

打开./VOC/ImageSets/Main 可以看到每个类别的 txt :
在这里插入图片描述
接下来是COCO对于VOC 的转换,参考自:https://blog.csdn.net/weixin_43384257/article/details/103737588.

  • 首先,封装创建节点的过程、添加一个子节点的过程
  • <object>节点较为特殊,因此单独进行封装,方便下面操作
# 封装创建节点的过程
def createElementNode(doc, tag, attr):  # 创建一个元素节点
    element_node = doc.createElement(tag)
    text_node = doc.createTextNode(attr)  # 创建一个文本节点
    element_node.appendChild(text_node)  # 将文本节点作为元素节点的子节点
    return element_node

# 封装添加一个子节点
def createChildNode(doc, tag, attr, parent_node):
    child_node = createElementNode(doc, tag, attr)
    parent_node.appendChild(child_node)

def createObjectNode(doc, ann):
    object_node = doc.createElement('object')
    for cat in cats:
        if cat['id'] == ann['category_id']:
            name = cat['name']

    createChildNode(doc, 'name', name, object_node)
    createChildNode(doc, 'difficult', '0', object_node)

    bndbox_node = doc.createElement('bndbox')
    createChildNode(doc, 'xmin', str(int(ann['bbox'][0])), bndbox_node)
    createChildNode(doc, 'ymin', str(int(ann['bbox'][1])), bndbox_node)
    createChildNode(doc, 'xmax', str(int(ann['bbox'][2])), bndbox_node)
    createChildNode(doc, 'ymax', str(int(ann['bbox'][3])), bndbox_node)
    object_node.appendChild(bndbox_node)

    return object_node

注意:所有信息都要转换成 str 型

如下,处理 filename、img_size 等信息,当然,也有’偷工减料’。

for img in imgs:
    my_dom = xml.dom.getDOMImplementation()
    doc = my_dom.createDocument(None, 'annotation', None)
    root_node = doc.documentElement

    createChildNode(doc, 'filename', img['file_name'], root_node)

    size_node = doc.createElement('size')
    createChildNode(doc, 'width', str(img['width']), size_node)
    createChildNode(doc, 'height', str(img['height']), size_node)
    createChildNode(doc, 'depth', str(img['depth']), size_node)
    root_node.appendChild(size_node)

    createChildNode(doc, 'segmented', '0', root_node)
    for ann in anns:
        if ann['image_id'] == img['id']:
            object_node = createObjectNode(doc, ann)
            root_node.appendChild(object_node)
  • 最后,写到 xml 中,
    删除第一行信息<?xml version="1.0" encoding="utf-8"?>
    new_file = open('./My_datas/COCO/ann_voc/%s.xml' % (img['file_name'].strip('.jpg')), 'w')
    doc.writexml(new_file, addindent=' ' * 4, newl='\n', encoding='utf-8')
    new_file.close()

    # 删除第一行字
    file = open('./My_datas/COCO/ann_voc/%s.xml' % (img['file_name'].strip('.jpg')), 'r+')
    lines = file.readlines()
    file.seek(0)
    file.truncate()
    for line in lines[1:]:
        if line.split():
            file.write(line.replace('<?xml version="1.0" encoding="utf-8"?>', ''))
    file.close()

参考

coco详解

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值