【数据集转换】VOC数据集转COCO数据集·代码实现+操作步骤

在自己的数据集上实验时,往往需要将VOC数据集转化为coco数据集,因为这种需求所以才记录这篇文章,代码出处未知,感谢开源。

在远程服务器上测试目标检测算法需要用到测试集,最常用的是coco2014/2017和voc07/12数据集。


coco数据集的地址为http://cocodataset.org/#download
voc和coco的镜像为https://pjreddie.com/projects/pascal-voc-dataset-mirror/

一、数据集格式对比

1.1 VOC数据集

VOC_ROOT     #根目录
    ├── JPEGImages         # 存放源图,(当然图片并不一定要是**.jpg格式的,只是规定文件夹名字叫JPEGImages**);
    │     ├── aaaa.jpg     
    │     ├── bbbb.jpg  
    │     └── cccc.jpg
    ├── Annotations        # 存放xml文件,VOC的标注是xml格式,与JPEGImages中的图片一一对应
    │     ├── aaaa.xml 
    │     ├── bbbb.xml 
    │     └── cccc.xml 
    └── ImageSets          
        └── Main
          ├── train.txt    # txt文件中每一行包含一个图片的名称
          └── val.txt

1.2 COCO数据集

COCO_ROOT     #根目录
    ├── annotations        # 存放json格式的标注
    │     ├── instances_train2017.json   
    │     └── instances_val2017.json
    └── train2017         # 存放图片文件
    │     ├── 000000000001.jpg 
    │     ├── 000000000002.jpg 
    │     └── 000000000003.jpg 
    └── val2017        
          ├── 000000000004.jpg 
          └── 000000000005.jpg 

1.2.3 json标注格式

VOC一个文件一个xml标注不同,COCO所有的目标框标注都是放在一个json文件中的。
这个json文件解析出来是一个字典,格式如下:

{
  "info": info, 
  "images": [image], 
  "annotations": [annotation], 
  "categories": [categories],
  "licenses": [license],
}

二、转换步骤

2.1 程序总体目录

 2.2  标签文件转换代码实现(xml文件转json格式)VOC_To_CoCo_01.py

这里需要运行三次,因为train.txt val.txt test.txt是三个文件,具体看注释

# VOC_To_CoCo_01.py

import os
import argparse
import json
import xml.etree.ElementTree as ET
from typing import Dict, List
import re


def get_label2id(labels_path: str) -> Dict[str, int]:
    """id is 1 start"""
    with open(labels_path, 'r') as f:
        labels_str = f.read().split()
    labels_ids = list(range(1, len(labels_str) + 1))
    return dict(zip(labels_str, labels_ids))


def get_annpaths(ann_dir_path: str = None,
                 ann_ids_path: str = None,
                 ext: str = '',
                 annpaths_list_path: str = None) -> List[str]:
    # If use annotation paths list
    if annpaths_list_path is not None:
        with open(annpaths_list_path, 'r') as f:
            ann_paths = f.read().split()
        return ann_paths

    # If use annotaion ids list
    ext_with_dot = '.' + ext if ext != '' else ''
    with open(ann_ids_path, 'r') as f:
        ann_ids = f.read().split()
    ann_paths = [os.path.join(ann_dir_path, aid + ext_with_dot) for aid in ann_ids]
    return ann_paths


def get_image_info(annotation_root, extract_num_from_imgid=True):
    path = annotation_root.findtext('path')
    if path is None:
        filename = annotation_root.findtext('filename')
    else:
        filename = os.path.basename(path)
    img_name = os.path.basename(filename)
    img_id = os.path.splitext(img_name)[0]
    if extract_num_from_imgid and isinstance(img_id, str):
        img_id = int(re.findall(r'\d+', img_id)[0])

    size = annotation_root.find('size')
    width = int(size.findtext('width'))
    height = int(size.findtext('height'))

    image_info = {
        'file_name': filename,
        'height': height,
        'width': width,
        'id': img_id
    }
    return image_info


def get_coco_annotation_from_obj(obj, label2id):
    label = obj.findtext('name')
    assert label in label2id, f"Error: {label} is not in label2id !"
    category_id = label2id[label]
    bndbox = obj.find('bndbox')
    xmin = int(bndbox.findtext('xmin')) - 1
    ymin = int(bndbox.findtext('ymin')) - 1
    xmax = int(bndbox.findtext('xmax'))
    ymax = int(bndbox.findtext('ymax'))
    assert xmax > xmin and ymax > ymin, f"Box size error !: (xmin, ymin, xmax, ymax): {xmin, ymin, xmax, ymax}"
    o_width = xmax - xmin
    o_height = ymax - ymin
    ann = {
        'area': o_width * o_height,
        'iscrowd': 0,
        'bbox': [xmin, ymin, o_width, o_height],
        'category_id': category_id,
        'ignore': 0,
        'segmentation': []  # This script is not for segmentation
    }
    return ann


def convert_xmls_to_cocojson(annotation_paths: List[str],
                             label2id: Dict[str, int],
                             output_jsonpath: str,
                             extract_num_from_imgid: bool = True):
    output_json_dict = {
        "images": [],
        "type": "instances",
        "annotations": [],
        "categories": []
    }
    bnd_id = 1  # START_BOUNDING_BOX_ID, TODO input as args ?

    for a_path in annotation_paths:
        # Read annotation xml
        ann_tree = ET.parse(a_path)
        ann_root = ann_tree.getroot()

        img_info = get_image_info(annotation_root=ann_root,
                                  extract_num_from_imgid=extract_num_from_imgid)
        img_id = img_info['id']
        output_json_dict['images'].append(img_info)

        for obj in ann_root.findall('object'):
            ann = get_coco_annotation_from_obj(obj=obj, label2id=label2id)
            ann.update({'image_id': img_id, 'id': bnd_id})
            output_json_dict['annotations'].append(ann)
            bnd_id = bnd_id + 1

    for label, label_id in label2id.items():
        category_info = {'supercategory': 'none', 'id': label_id, 'name': label}
        output_json_dict['categories'].append(category_info)

    with open(output_jsonpath, 'w') as f:
        output_json = json.dumps(output_json_dict)
        f.write(output_json)
    print('Convert successfully !')


def main():
    parser = argparse.ArgumentParser(
        description='This script support converting voc format xmls to coco format json')
    parser.add_argument('--ann_dir', type=str, default='./VOCdevkit/Annotations')
    parser.add_argument('--ann_ids', type=str, default='./VOCdevkit/ImageSets/Main/val.txt') # 这里修改 train val test 一共修改三次
    #parser.add_argument('--ann_ids', type=str, default='./VOCdevkit/ImageSets/Main/train.txt')
    #parser.add_argument('--ann_ids', type=str, default='./VOCdevkit/ImageSets/Main/test.txt')
    parser.add_argument('--ann_paths_list', type=str, default=None)
    parser.add_argument('--labels', type=str, default='./VOCdevkit/labels.txt')
    parser.add_argument('--output', type=str, default='./output/annotations/val.json') # 这里修改 train val test 一共修改三次
    #parser.add_argument('--output', type=str, default='./output/annotations/train.json')
    #parser.add_argument('--output', type=str, default='./output/annotations/test.json')
    parser.add_argument('--ext', type=str, default='xml')
    args = parser.parse_args()
    label2id = get_label2id(labels_path=args.labels)
    ann_paths = get_annpaths(
        ann_dir_path=args.ann_dir,
        ann_ids_path=args.ann_ids,
        ext=args.ext,
        annpaths_list_path=args.ann_paths_list
    )
    convert_xmls_to_cocojson(
        annotation_paths=ann_paths,
        label2id=label2id,
        output_jsonpath=args.output,
        extract_num_from_imgid=True
    )


if __name__ == '__main__':
    if not os.path.exists('./output/annotations'):
        os.makedirs('./output/annotations')
    main()

2.3 数据集图像文件copy代码实现(复制图片数据集到coco中)VOC_To_CoCo_02.py

# VOC_To_CoCo_02.py

import os
import shutil

images_file_path = './VOCdevkit/JPEGImages/'
split_data_file_path = './VOCdevkit/ImageSets/Main/'
new_images_file_path = './output/'

if not os.path.exists(new_images_file_path + 'train'):
    os.makedirs(new_images_file_path + 'train')
if not os.path.exists(new_images_file_path + 'val'):
    os.makedirs(new_images_file_path + 'val')
if not os.path.exists(new_images_file_path + 'test'):
    os.makedirs(new_images_file_path + 'test')

dst_train_Image = new_images_file_path + 'train/'
dst_val_Image = new_images_file_path + 'val/'
dst_test_Image = new_images_file_path + 'test/'

total_txt = os.listdir(split_data_file_path)
for i in total_txt:
    name = i[:-4]
    if name == 'train':
        txt_file = open(split_data_file_path + i, 'r')
        for line in txt_file:
            line = line.strip('\n')
            line = line.strip('\r')
            srcImage = images_file_path + line + '.jpg'
            dstImage = dst_train_Image + line + '.jpg'
            shutil.copyfile(srcImage, dstImage)
        txt_file.close()
    elif name == 'val':
        txt_file = open(split_data_file_path + i, 'r')
        for line in txt_file:
            line = line.strip('\n')
            line = line.strip('\r')
            srcImage = images_file_path + line + '.jpg'
            dstImage = dst_val_Image + line + '.jpg'
            shutil.copyfile(srcImage, dstImage)
        txt_file.close()
    elif name == 'test':
        txt_file = open(split_data_file_path + i, 'r')
        for line in txt_file:
            line = line.strip('\n')
            line = line.strip('\r')
            srcImage = images_file_path + line + '.jpg'
            dstImage = dst_test_Image + line + '.jpg'
            shutil.copyfile(srcImage, dstImage)
        txt_file.close()
    else:
        print("Error, Please check the file name of folder")

三、效果展示

猜你喜欢

转载自blog.csdn.net/qq_39237205/article/details/126120347