![4ed20829825e6f57a47f12f1635fcce2.png](https://i-blog.csdnimg.cn/blog_migrate/cceaed10ce0fb9b742eecb7e6f440294.jpeg)
计算机视觉问题需要带注释的数据集。具体来说,对象检测问题要求框架内的项目以标注的注释为边界。
随着对象检测的发展,描述对象注释的不同文件格式已经出现。这造成了令人沮丧的情况,团队需要投入大量时间从一种注释格式转换为另一种注释格式,而不是专注于更高价值的任务,例如改进深度学习模型架构。
数据科学家花费时间在注释格式之间进行转换就像作者花费时间将Word文档转换为PDF一样。
最常见的注释格式来自挑战和聚集的数据集。随着机器学习研究人员利用这些数据集构建更好的模型,其注释的格式成为非官方的标准协议。
PASCAL VOC XML
PASCAL(模式分析,统计建模和计算学习)是由欧盟资助的卓越网络。从2005年到2012年,PASCAL举办了视觉对象挑战赛(VOC)。PASCAL每年发布对象检测数据集并报告基准。
PASCAL VOC注释以XML格式发布,其中每个图像都有一个随附的XML文件,该文件描述了框架中包含的边界框。例如,在用于血液细胞检测的BCCD数据集中,一个XML注释示例如下所示:
<annotation>
<folder>JPEGImages</folder>
<filename>BloodImage_00000.jpg</filename>
<path>/home/pi/detection_dataset/JPEGImages/BloodImage_00000.jpg</path>
<source>
<database>Unknown</database>
</source>
<size>
<width>640</width>
<height>480</height>
<depth>3</depth>
</size>
<segmented>0</segmented>
<object>
<name>WBC</name>
<pose>Unspecified</pose>
<truncated>0</truncated>
<difficult>0</difficult>
<bndbox>
<xmin>260</xmin>
<ymin>177</ymin>
<xmax>491</xmax>
<ymax>376</ymax>
</bndbox>
</object>
...
<object>
...
</object>
</annotation>
注意几个关键的事情:(1),其被注释被提及作为相对路径的图像文件(2)的图像的元数据被包括作为width,height和depth(3)的边界框的像素位置由顶部表示左角和底部右下角为xmin,ymin,xmax,ymax。
COCO JSON
上下文中的通用对象(COCO)数据集源自Microsoft 在2014年发表的一篇论文。数据集“包含91种对象的照片,这些照片很容易被4岁的孩子识别”。在328,000张图像中,总共有250万个带标签的实例。考虑到开源数据的数量和质量,COCO已成为用于测试和证明新模型中最新性能的标准数据集。
COCO注释以JSON格式发布。与PASCAL VOC中每个图像都有其自己的注释文件不同,COCO JSON要求使用一个JSON文件来描述一组图像集合。此外,COCO数据集支持多种类型的计算机视觉问题:关键点检测,对象检测,分段和创建标题。因此,手头任务有不同的格式。这篇文章着重于对象检测。用于对象检测的COCO JSON示例注释如下所示:
{
"info": {
"year": "2020",
"version": "1",
"description": "Exported from roboflow.ai",
"contributor": "",
"url": "https://app.roboflow.ai/datasets/bccd-single-image-example/1",
"date_created": "2020-01-30T23:05:21+00:00"
},
"licenses": [
{
"id": 1,
"url": "",
"name": "Unknown"
}
],
"categories": [
{
"id": 0,
"name": "cells",
"supercategory": "none"
},
{
"id": 1,
"name": "RBC",
"supercategory": "cells"
},
{
"id": 2,
"name": "WBC",
"supercategory": "cells"
}
],
"images": [
{
"id": 0,
"license": 1,
"file_name": "0bc08a33ac64b0bd958dd5e4fa8dbc43.jpg",
"height": 480,
"width": 640,
"date_captured": "2020-02-02T23:05:21+00:00"
}
],
"annotations": [
{
"id": 0,
"image_id": 0,
"category_id": 2,
"bbox": [
260,
177,
231,
199
],
"area": 45969,
"segmentation": [],
"iscrowd": 0
},
{
"id": 1,
"image_id": 0,
"category_id": 1,
"bbox": [
78,
336,
106,
99
],
"area": 10494,
"segmentation": [],
"iscrowd": 0
},
{
"id": 2,
"image_id": 0,
"category_id": 1,
"bbox": [
63,
237,
106,
99
],
"area": 10494,
"segmentation": [],
"iscrowd": 0
},
...
]
}
请注意此处的一些关键事项:(1)有关数据集本身及其许可证的信息(2)包括的所有标签均被定义为categories(3)边界框被定义为x,y左上角的坐标随后是边界框width和height。
将VOC XML转换为COCO JSON
流行的注释工具(如LabelImg)在Pascal VOC XML中提供注释。诸如ImageNet之类的某些模型要求Pascal VOC。其他(例如Mask-RCNN)要求使用COCO JSON注释的图像。
使用Python脚本
import os
import argparse
import json
import xml.etree.ElementTree as ET
from typing import Dict, List
from tqdm import tqdm
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 ?
print('Start converting !')
for a_path in tqdm(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)
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=None,
help='path to annotation files directory. It is not need when use --ann_paths_list')
parser.add_argument('--ann_ids', type=str, default=None,
help='path to annotation files ids list. It is not need when use --ann_paths_list')
parser.add_argument('--ann_paths_list', type=str, default=None,
help='path of annotation paths list. It is not need when use --ann_dir and --ann_ids')
parser.add_argument('--labels', type=str, default=None,
help='path to label list.')
parser.add_argument('--output', type=str, default='output.json', help='path to output json file')
parser.add_argument('--ext', type=str, default='', help='additional extension of annotation file')
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__':
main()
要在自己的示例上重用此脚本,文件结构必须与示例存储库的文件结构匹配!
最近确实被数据集的标注格式转换的问题搞得很头大,等我完全处理好了以后会针对keypoints(人体关键点)标注的问题做详细介绍。
最近在用远程服务器做实验,环境都是配置好了的,几乎不用担心环境的问题,直接就可以跑实验,有同样需求的朋友可以参考:智星云官网: http://www.ai-galaxy.cn/,淘宝店:https://shop36573300.taobao.com/公众号: 智星AI,
越来越觉得,本科没毕业之前觉得毕业好难,本科毕业工作之后又觉得工作好难,一边工作一边考研,又觉得考研更难,现在眼看还有一年研究生就要毕业了,我才觉得现在才更难。人生也许就是不断的升级打怪的过程,不要放弃,硬着头皮总是能挺过去的。九层之台,起于累土。PEACE。
参考文献:
http://cocodataset.org/#home
https://gluon-cv.mxnet.io/build/examples_datasets/pascal_voc.html
https://blog.roboflow.ai/how-to-convert-annotations-from-voc-xml-to-coco-json/
http://www.ai-galaxy.cn/
https://shop36573300.taobao.com/