本站消息

  出租广告位,需要合作请联系站长

  今日名言-想象你自己对困难作出的反应,不是逃避或绕开它们,而是面对它们,同它们打交道,以一种进取的和明智的方式同它们奋斗 。——马克斯威尔·马尔兹

  今日名言-用谅解、宽恕的目光和心理看人、待人。人就会觉得葱笼的世界里,春意盎然,到处充满温暖。——蔡文甫


+关注
已关注

分类  

暂无分类

标签  

暂无标签

日期归档  

暂无数据

如何将标注格式从 PASCAL VOC XML 转换为 COCO JSON

发布于2021-07-25 07:41     阅读(1835)     评论(0)     点赞(8)     收藏(4)


计算机视觉问题需要带标注的数据集。随着object detection的发展,出现了描述对象标注不同文件格式。这造成了令人沮丧的情况,团队将时间花在从一种标注格式转换为另一种标注格式上,而不是专注于更高价值的任务——比如改进深度学习模型架构。数据科学家花费时间在注释格式之间进行转换就像作者花费时间将 Word 文档转换为 PDF。最常用的标注格式来自于有挑战的和日积月累的数据集,随着机器学习研究人员利用这些数据集构建更好的模型,他们的注释格式成为非官方的标准协议。在这篇文章中,我们将为您提供在两种最常见格式之间进行转换所需的代码:VOC XML 和 COCO JSON。

PASCAL VOC XML

PASCAL(Pattern Analysis, Statistical modelling and ComputAtional Learning 模式分析、统计建模和计算学习)是一个由欧盟资助的卓越网络。从 2005 年到 2012 年,PASCAL 举办了视觉对象挑战赛 (VOC,Visual Object Challenge)。 PASCAL 每年都会发布对象检测数据集并报告基准。 (此处提供了 PASCAL VOC 数据集。)

PASCAL VOC 注释以 XML 格式发布,其中每个图像都有一个随附的 XML 文件,描述框架中包含的边界框。例如,在用于血细胞检测的 BCCD 数据集中,单个 XML标注示例如下所示:

  1. <annotation>
  2. <folder>JPEGImages</folder>
  3. <filename>BloodImage_00000.jpg</filename>
  4. <path>/home/pi/detection_dataset/JPEGImages/BloodImage_00000.jpg</path>
  5. <source>
  6. <database>Unknown</database>
  7. </source>
  8. <size>
  9. <width>640</width>
  10. <height>480</height>
  11. <depth>3</depth>
  12. </size>
  13. <segmented>0</segmented>
  14. <object>
  15. <name>WBC</name>
  16. <pose>Unspecified</pose>
  17. <truncated>0</truncated>
  18. <difficult>0</difficult>
  19. <bndbox>
  20. <xmin>260</xmin>
  21. <ymin>177</ymin>
  22. <xmax>491</xmax>
  23. <ymax>376</ymax>
  24. </bndbox>
  25. </object>
  26. ...
  27. <object>
  28. ...
  29. </object>
  30. </annotation>

请注意一些关键细节:(1) 被注释的图像文件被称为相对路径 (2) 图像元数据被包含为宽度(width)、高度(hight)和深度(depth) (3) 边界框像素位置由最左上角点xmin、ymin和最右下角点xmax、ymax确定。

COCO JSON

COCO(Common Objects in Context)数据集起源于微软 2014 年发表的一篇论文。该数据集“包含 4 岁儿童可以轻松识别的 91 种物体类型的照片”。在 328,000 张图像中总共有 250 万个标注过的实例。鉴于开源数据的绝对数量和质量,COCO 已成为测试和证明新模型最先进性能的标准数据集。(数据集可在此处获得。)

COCO 标注以 JSON 格式发布。与每个图像都有自己的标注文件的 PASCAL VOC 不同,COCO JSON 只用描述一组图像集合的单个 JSON 文件。此外,COCO 数据集支持多种类型的计算机视觉问题:关键点检测、对象检测、分割和创建字幕。因此,根据不同的任务有不同的标注格式。这篇文章的重点是物体检测(object detection)。用于对象检测的 COCO JSON 示例注释如下所示:

  1. {
  2. "info": {
  3. "year": "2020",
  4. "version": "1",
  5. "description": "Exported from roboflow.ai",
  6. "contributor": "",
  7. "url": "https://app.roboflow.ai/datasets/bccd-single-image-example/1",
  8. "date_created": "2020-01-30T23:05:21+00:00"
  9. },
  10. "licenses": [
  11. {
  12. "id": 1,
  13. "url": "",
  14. "name": "Unknown"
  15. }
  16. ],
  17. "categories": [
  18. {
  19. "id": 0,
  20. "name": "cells",
  21. "supercategory": "none"
  22. },
  23. {
  24. "id": 1,
  25. "name": "RBC",
  26. "supercategory": "cells"
  27. },
  28. {
  29. "id": 2,
  30. "name": "WBC",
  31. "supercategory": "cells"
  32. }
  33. ],
  34. "images": [
  35. {
  36. "id": 0,
  37. "license": 1,
  38. "file_name": "0bc08a33ac64b0bd958dd5e4fa8dbc43.jpg",
  39. "height": 480,
  40. "width": 640,
  41. "date_captured": "2020-02-02T23:05:21+00:00"
  42. }
  43. ],
  44. "annotations": [
  45. {
  46. "id": 0,
  47. "image_id": 0,
  48. "category_id": 2,
  49. "bbox": [
  50. 260,
  51. 177,
  52. 231,
  53. 199
  54. ],
  55. "area": 45969,
  56. "segmentation": [],
  57. "iscrowd": 0
  58. },
  59. {
  60. "id": 1,
  61. "image_id": 0,
  62. "category_id": 1,
  63. "bbox": [
  64. 78,
  65. 336,
  66. 106,
  67. 99
  68. ],
  69. "area": 10494,
  70. "segmentation": [],
  71. "iscrowd": 0
  72. },
  73. {
  74. "id": 2,
  75. "image_id": 0,
  76. "category_id": 1,
  77. "bbox": [
  78. 63,
  79. 237,
  80. 106,
  81. 99
  82. ],
  83. "area": 10494,
  84. "segmentation": [],
  85. "iscrowd": 0
  86. },
  87. ...
  88. ]
  89. }

请注意这里的一些关键事项:(1) 有关于数据集本身及其许可证的信息 (2) 包含的所有标签都定义为类别 (3) 边界框定义为左上角的 x、y 坐标以及边界框的宽度和高度。

将 VOC XML 转换为 COCO JSON

LabelImg、VoTT 和 CVAT 等流行的标注工具提供 Pascal VOC XML格式的标注。 ImageNet 等一些模型需要 Pascal VOC。其他的,比如 Mask-RCNN,需要 COCO JSON 标注图像。
要将一种格式转换为另一种格式,您可以编写(或借用)自定义脚本或使用 Roboflow 之类的工具(Roboflow 1000张免费)。

使用 Python 脚本

GitHub 用户(和 Kaggle Master)yukkyo 创建了一个存放在github的脚本,Roboflow 团队已经克隆了这个脚本并稍微修改了存储库,以便在此处使用,你可以从这里获得。

整个代码如下:

  1. import os
  2. import argparse
  3. import json
  4. import xml.etree.ElementTree as ET
  5. from typing import Dict, List
  6. from tqdm import tqdm
  7. import re
  8. def get_label2id(labels_path: str) -> Dict[str, int]:
  9. """id is 1 start"""
  10. with open(labels_path, 'r') as f:
  11. labels_str = f.read().split()
  12. labels_ids = list(range(1, len(labels_str)+1))
  13. return dict(zip(labels_str, labels_ids))
  14. def get_annpaths(ann_dir_path: str = None,
  15. ann_ids_path: str = None,
  16. ext: str = '',
  17. annpaths_list_path: str = None) -> List[str]:
  18. # If use annotation paths list
  19. if annpaths_list_path is not None:
  20. with open(annpaths_list_path, 'r') as f:
  21. ann_paths = f.read().split()
  22. return ann_paths
  23. # If use annotaion ids list
  24. ext_with_dot = '.' + ext if ext != '' else ''
  25. with open(ann_ids_path, 'r') as f:
  26. ann_ids = f.read().split()
  27. ann_paths = [os.path.join(ann_dir_path, aid+ext_with_dot) for aid in ann_ids]
  28. return ann_paths
  29. def get_image_info(annotation_root, extract_num_from_imgid=True):
  30. path = annotation_root.findtext('path')
  31. if path is None:
  32. filename = annotation_root.findtext('filename')
  33. else:
  34. filename = os.path.basename(path)
  35. img_name = os.path.basename(filename)
  36. img_id = os.path.splitext(img_name)[0]
  37. if extract_num_from_imgid and isinstance(img_id, str):
  38. img_id = int(re.findall(r'\d+', img_id)[0])
  39. size = annotation_root.find('size')
  40. width = int(size.findtext('width'))
  41. height = int(size.findtext('height'))
  42. image_info = {
  43. 'file_name': filename,
  44. 'height': height,
  45. 'width': width,
  46. 'id': img_id
  47. }
  48. return image_info
  49. def get_coco_annotation_from_obj(obj, label2id):
  50. label = obj.findtext('name')
  51. assert label in label2id, f"Error: {label} is not in label2id !"
  52. category_id = label2id[label]
  53. bndbox = obj.find('bndbox')
  54. xmin = int(bndbox.findtext('xmin')) - 1
  55. ymin = int(bndbox.findtext('ymin')) - 1
  56. xmax = int(bndbox.findtext('xmax'))
  57. ymax = int(bndbox.findtext('ymax'))
  58. assert xmax > xmin and ymax > ymin, f"Box size error !: (xmin, ymin, xmax, ymax): {xmin, ymin, xmax, ymax}"
  59. o_width = xmax - xmin
  60. o_height = ymax - ymin
  61. ann = {
  62. 'area': o_width * o_height,
  63. 'iscrowd': 0,
  64. 'bbox': [xmin, ymin, o_width, o_height],
  65. 'category_id': category_id,
  66. 'ignore': 0,
  67. 'segmentation': [] # This script is not for segmentation
  68. }
  69. return ann
  70. def convert_xmls_to_cocojson(annotation_paths: List[str],
  71. label2id: Dict[str, int],
  72. output_jsonpath: str,
  73. extract_num_from_imgid: bool = True):
  74. output_json_dict = {
  75. "images": [],
  76. "type": "instances",
  77. "annotations": [],
  78. "categories": []
  79. }
  80. bnd_id = 1 # START_BOUNDING_BOX_ID, TODO input as args ?
  81. print('Start converting !')
  82. for a_path in tqdm(annotation_paths):
  83. # Read annotation xml
  84. ann_tree = ET.parse(a_path)
  85. ann_root = ann_tree.getroot()
  86. img_info = get_image_info(annotation_root=ann_root,
  87. extract_num_from_imgid=extract_num_from_imgid)
  88. img_id = img_info['id']
  89. output_json_dict['images'].append(img_info)
  90. for obj in ann_root.findall('object'):
  91. ann = get_coco_annotation_from_obj(obj=obj, label2id=label2id)
  92. ann.update({'image_id': img_id, 'id': bnd_id})
  93. output_json_dict['annotations'].append(ann)
  94. bnd_id = bnd_id + 1
  95. for label, label_id in label2id.items():
  96. category_info = {'supercategory': 'none', 'id': label_id, 'name': label}
  97. output_json_dict['categories'].append(category_info)
  98. with open(output_jsonpath, 'w') as f:
  99. output_json = json.dumps(output_json_dict)
  100. f.write(output_json)
  101. def main():
  102. parser = argparse.ArgumentParser(
  103. description='This script support converting voc format xmls to coco format json')
  104. parser.add_argument('--ann_dir', type=str, default=None,
  105. help='path to annotation files directory. It is not need when use --ann_paths_list')
  106. parser.add_argument('--ann_ids', type=str, default=None,
  107. help='path to annotation files ids list. It is not need when use --ann_paths_list')
  108. parser.add_argument('--ann_paths_list', type=str, default=None,
  109. help='path of annotation paths list. It is not need when use --ann_dir and --ann_ids')
  110. parser.add_argument('--labels', type=str, default=None,
  111. help='path to label list.')
  112. parser.add_argument('--output', type=str, default='output.json', help='path to output json file')
  113. parser.add_argument('--ext', type=str, default='', help='additional extension of annotation file')
  114. args = parser.parse_args()
  115. label2id = get_label2id(labels_path=args.labels)
  116. ann_paths = get_annpaths(
  117. ann_dir_path=args.ann_dir,
  118. ann_ids_path=args.ann_ids,
  119. ext=args.ext,
  120. annpaths_list_path=args.ann_paths_list
  121. )
  122. convert_xmls_to_cocojson(
  123. annotation_paths=ann_paths,
  124. label2id=label2id,
  125. output_jsonpath=args.output,
  126. extract_num_from_imgid=True
  127. )
  128. if __name__ == '__main__':
  129. main()

脚本使用方法

1. 创建labels.txt

如果需要用到labels.txt,那么这个文件的作用是将标签转换为 id 的字典,请使用 labels.txt。

举例labels.txt 内容:

  1. Label1
  2. Label2
  3. ...

运行脚本:

用法1(使用ID列表)

  1. $ python voc2coco.py \
  2. --ann_dir /path/to/annotation/dir \
  3. --ann_ids /path/to/annotations/ids/list.txt \
  4. --labels /path/to/labels.txt \
  5. --output /path/to/output.json \
  6. <option> --ext xml

用法2(使用标注文件路径列表):

标注路径列表文件paths.txt举例:

  1. /path/to/annotation/file.xml
  2. /path/to/annotation/file2.xml
  3. ...
  1. $ python voc2coco.py \
  2. --ann_paths_list /path/to/annotation/paths.txt \
  3. --labels /path/to/labels.txt \
  4. --output /path/to/output.json \
  5. <option> --ext xml

使用举例:

在这个例子中 你可以把VOC数据集 Shenggan/BCCD_Dataset: BCCD Dataset is a small-scale dataset for blood cells detection.

这个数据集对应的文件结构是:

  1. ├── BCCD
  2. │ ├── Annotations
  3. │ │ └── BloodImage_00XYZ.xml (364 items)
  4. │ ├── ImageSets # Contain four Main/*.txt which split the dataset
  5. │ └── JPEGImages
  6. │ └── BloodImage_00XYZ.jpg (364 items)
  7. ├── dataset
  8. │ └── mxnet # Some preprocess scripts for mxnet
  9. ├── scripts
  10. │ ├── split.py # A script to generate four .txt in ImageSets
  11. │ └── visualize.py # A script to generate labeled img like example.jpg
  12. ├── example.jpg # A example labeled img generated by visualize.py
  13. ├── LICENSE
  14. └── README.md

转换成COCO json格式通过这个命令:

  1. $ python voc2coco.py
  2. --ann_dir sample/Annotations \
  3. --ann_ids sample/dataset_ids/test.txt \
  4. --labels sample/labels.txt \
  5. --output sample/bccd_test_cocoformat.json \
  6. --ext xml
  7. # Check output
  8. $ ls sample/ | grep bccd_test_cocoformat.json
  9. bccd_test_cocoformat.json
  10. # Check output
  11. cut -f -4 -d , sample/bccd_test_cocoformat.json
  12. {"images": [{"file_name": "BloodImage_00007.jpg", "height": 480, "width": 640, "id": "BloodImage_00007"}

使用 Roboflow

Roboflow 能够仅仅通过页面点击就能将Pascal VOC XML 转换到 COCO JSON (or 反之亦然) .事实上,你能使用Robotflow转换几乎所有格式的标注(CreateML JSON, YOLO Darknet TXT, LabelMe, SuperAnnotate, Scale AI, Labelbox, Supervisely, and dozens more) 到其它 任意标注格式。1000张图片以内是免费的。

参考:

https://blog.roboflow.com/how-to-convert-annotations-from-voc-xml-to-coco-json/

https://github.com/yukkyo/voc2coco

https://github.com/Shenggan/BCCD_Dataset



所属网站分类: 技术文章 > 博客

作者:机甲战士

链接:http://www.pythonpdf.com/blog/article/523/2c731ae15ff0718e6ed2/

来源:编程知识网

任何形式的转载都请注明出处,如有侵权 一经发现 必将追究其法律责任

8 0
收藏该文
已收藏

评论内容:(最多支持255个字符)