MicroSoft COCO数据集

安装错误

no such file or directory: 'pycocotools/_mask.c'
解决办法: pip install cython

评价标准

COCO数据集介绍

COCO数据集具有5种标签类型, 分别为: 目标检测, 关键点检测, 物体分割, 多边形分割 以及 图像描述. 这些标注数据使用JSON格式存储. 所有文件除了标签项以外都共享同样的数据结构, 如下所示:

标签结构各有不同, 如下所示:

Object Detection

每一个目标实例的标签都具有一系列条目, 包括该目标的类别id以及分割掩膜(segmentation mask). 分割掩膜的格式取决于实例表示的是一个单一的目标(iscrowd=0, 使用polygons标注)还是一群目标(iscrowd=1, 使用RLE标注). 注意, 一个单一的物体(iscrowd=0)在被遮挡的情况下, 可能会需要多个多边形来表示. Crowd标签用来标注一大群目标(如一群人). 另外, 每一个物体都会提供一个闭合的bounding box( box的坐标系从图左上角开始,0-indexed). 最后, 标注结构的类别条目存储着从cat id 到类别的映射以及超类别的名字.

关键点检测

Stuff Segmentation

Stuff Segmentation的格式和object detection的格式几乎一模一样, 但是stuff segmentation无需iscrowd条目, 因为该条默认置为0. 为了方便访问, coco提供了json和png两种标注格式. 在 json 格式中, 每一个出现在图片中的类别都会单独用一个RLE标签条目编码(也就是说同类的会被放到同一个RLE编码里面). category_id 则代表当前的物体类别的id.

Panoptic Segmentation

数据集信息

标注格式

COCO-API 使用方法及源码解析

利用 json 文件实例化 COCO API 对象

  • 参数
    -annotation_file=None (str): location of annotation file
1
2
3
4
5
6
7
8
9
10
from pycocotools.coco import COCO
dataDir = '/home/zhaozheng/coco'
dataType = 'val2017'
annFile = '{}/annotations/instances_{}.json'.format(dataDir, dataType)
coco = COCO(annFile)
# 若实例化成功, 则会返回:
# loading annotations into memory...
# Done (t=0.81s)
# creating index...
# index created!

coco.createIndex()

COCO 的构造函数会调用该函数来建立数据索引, 用户通常不会直接调用该函数

参数: 无
返回: 无

调用后会输出:

1
2
3
4
loading annotations into memory...
Done (t=0.74s)
creating index...
index created!

coco.info()

参数: 无
返回: 无

调用后会打印数据集标签的相关信息

1
2
3
4
5
6
description: COCO 2017 Dataset
url: http://cocodataset.org
version: 1.0
year: 2017
contributor: COCO Consortium
date_created: 2017/09/01

coco.getAnnIds()

  • 参数:
    • imgIds=[](int array) : 返回指定 imgs id 的 annotations id 列表
    • catIds=[](int array) : 返回指定类别 id 的所有 annotations id 列表
    • areaRng=[](float array, 二元组, 指定面积区间大小) : 返回area项标签处于指定区间的 annotations id 列表
    • iscrowd=None(boolean):
  • 返回: 返回满足条件的 annotations 的 id, 如果三个列表参数均为空, 则直接返回所有的 id. 注意上面三个参数的筛选过程是逐步进行的, 前者筛选后的结果会作为后者筛选的输入, 若三项同时为空, 则会返回所有的类别编号

coco.getCatIds()

  • 参数(当传入单个字符串时, 会自动转换成只包含一个字符串元素的列表):
    • catNms=[] (str array) : 返回列表中指定的类别名称的类别编号
    • supNms=[] (str array) : 返回列表中指定的子类别名称的类别编号
    • catIds=[] (int array) : 返回指定类别编号对应的类别编号
  • 返回
    • ids (int array) : 类别编号, 注意上面三个参数的筛选过程是逐步进行的, 前者筛选后的结果会作为后者筛选的输入, 若三项同时为空, 则会返回所有的类别编号

coco.getImgIds()

  • 参数:
    • imgIds=[](int array) : 返回指定 img ids 的 img ids
    • catIds=[](int array) : 返回具有指定类别 ids 的 img ids
  • 返回: 符合条件的图片的 id, 若参数均为空, 则返回所有的图片 id

coco.loadAnns()

  • 参数:
    • ids=[](int array) : 加载指定 ann ids 的 ann
  • 返回: 一个元素均为字典类型的列表, 其中字典的键根据任务的不同可能包含:
    • segmentation:
    • area:
    • iscrowd: 布尔值, 表示是否是群体目标(不好检测及分割)
    • image_id: 代表当前框属于哪一张图片
    • bbox: 代表 bbox 的坐标, 分别为 [x,y,width,height]
    • category_id: 代表当前的类别编号
    • id: 当前框的 id, 也就是输入参数 ann ids 的值
      注意, 参数为空时会返回一个空列表

coco.loadCats()

加载类别信息

  • 参数: ids=[](int array): 类别编号 cat ids
  • 返回: 一个元素均为字典类型的列表, 字典的键包含:
    • supercategory: 分别代表大类别(如交通工具)
    • id: 类别编号 cat ids
    • name: 以及类别的名称(如飞机). 注意, 参数为空时会返回一个空列表

coco.loadImgs()

加载图片信息

  • 参数: ids=[](int array): 图片编号 img ids
  • 返回: 一个元素均为字典类型的列表, 字典的键包含:
    • license
    • file_name: 图片的本地文件名
    • coco_url: 可以在线访问的图片地址
    • height: 图片的高
    • width: 图片的宽
    • data_captured
    • flickr_url, id

coco.showAnns()

先标签的信息显示出来(前面的 load 函数只是加载)

参数没有默认值, 调用函数时必须指定

  • 参数: anns(array of object), 传入的需要是 loadAnns() 函数返回的 anns 对象列表
  • 返回: 无

该函数需要先用import matplotlib.pyplot as plt创建相应的画布并加载图片才能看到最终的可视化效果, 具体见如下代码所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import matplotlib.pyplot as plt

# get all images containing given categories, select one at random
catIds = coco.getCatIds(catNms=['person','dog','skateboard']);
imgIds = coco.getImgIds(catIds=catIds );
imgIds = coco.getImgIds(imgIds = [324158])
img = coco.loadImgs(imgIds[np.random.randint(0,len(imgIds))])[0]
I = io.imread(img['coco_url'])
plt.axis('off')
plt.imshow(I)
plt.show()
# load and display instance annotations
plt.imshow(I); plt.axis('off')
annIds = coco.getAnnIds(imgIds=img['id'], catIds=catIds, iscrowd=None)
anns = coco.loadAnns(annIds)
coco.showAnns(anns)

coco.loadRes()(待尝试)

加载算法结果, 同时创建API以便访问它们, Load algorithm results and create API for accessing them.

  • 参数: resFile(sre): 文件名称或者 result file, 无默认值, 必须指定
  • 返回: result pai object

coco.download()

从Mscoco.org.server上下载COCO数据集

coco.loadNumpyAnnotations()

从 numpy 数组中转换算法的结果, 其结果是一个 Nx7 大小的数组

  • 参数: data(numpy.ndarray), Nx7, {imageId, x1, y1, w, h, score, class}
  • 返回: annotations(python nested list)

coco.annToRLE

只能接受一个 ann 对象, 不能是多个 ann 组成的列表

  • 参数: ann, 将 polygons 或者 uncompressed RLE 类型的数据转换成 RLE
  • 返回: rle 类型的数据

coco.annToMask

只能接受一个 ann 对象, 不能是多个 ann 组成的列表

  • 参数: ann, 将 polygons, uncompressed RLE 或者 RLE 类型的数据转换成二值掩膜
  • 返回: 二值掩膜

encode()

将 binary masks 编码成 RLE 形式

  • 参数: bimask
  • 返回: RLE 类型的数据

decode()

将 RLE 形式解码成 binary masks

  • 参数: rleObjs, rle 类型的输入
  • 返回: 解码后的二值掩膜

area()

  • 参数: rleObjs, rle 类型的输入
  • 返回: 分割区域的面积

toBbox()

源码解析

最常用的是 pycocotools/coco.py 文件中的COCO类, 其内部实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# pycocotools/coco.py

# 首先, 是一大串的注释
# COCO API提供了一系列的辅助函数来帮助载入,解析以及可视化COCO数据集的annotations
# 该文件定义了如下API 函数:
# COCO - COCO api 类, 用于载入coco的annotation 文件, 同时负责准备对应数据结构来存储
# decodeMask - 通过rle编码规范, 来对二值mask M进行解码
# encodeMask - 使用rle编码规范来对二值mak M进行编码
# getAnnIds - 获得满足给定过滤条件的ann ids(标注)
# getCatIds - 获得满足给定过滤条件的cat ids(类别)
# getImgIds - 获得满足给定过滤条件的img ids(图片)
# loadAnns - 根据指定的ids加载anns
# loadCats - 根据指定的ids加载cats
# loadImgs - 根据指定的ids加载imgs
# annToMask - 将annotation里面的segmentation信息转换成二值mask
# showAnns - 可视化指定的annotations
# loadRes - 加载算法结果同时创建API以便访问它们
# download - 从Mscoco.org.server上下载COCO数据集


# 接下来, 具体看一下COCO类的实现
class COCO:
def __init__(self, annotation_file=None):
# 构造函数
# 参数annotation_file: 指定了annotation文件的位置

# dataset, anns, cats, imgs均为字典类型数据
self.dataset, self.anns, self.cats, self.imgs = dict(), dict(), dict(), dict()

# imgToAnns, catToImgs均为defaultdict数据类型(带有默认值的字典)
self.imgToAnns, self.catToImgs = defaultdict(list), defaultdict(list)

if not annotation_file == None:
#...

# 以只读方式加载json标注文件
dataset = json.load(open(annotation_file, 'r'))
assert type(dataset)==dict # 确保读出来的是字典类型
#...

# 正式将读取的字典数据赋给该类的成员变量
self.dataset = dataset

# 创建索引
self.createIndex()

def createIndex(self):
# 创建索引

# 三个都是字典数据类型
anns,cats,imgs={},{},{}
# 两个defaultdict数据类型
imgToAnns, catToImgs = defaultdict(list), defaultdict(list)
if 'annotations' in self.dataset:
for ann in self.dataset['annotations']:

xml 数据转成 COCO 数据

对于 COCO 数据格式, 我们需要利用 json 模块来构建, 不同的任务需要的标签各不相同, 对于目标检测任务来说, 我们需要如下标签信息:

  • info: 数据集相关信息, 主要是数据集的相关信息, 训练时不会用到
  • images: 图片相关信息, id, 宽, 高, 文件名(最重要的四个)等
  • annotations: 标签相关信息, 目标检测任务需要 bbox, 对应的类别编号, 以及所属图片编号
  • license: 训练时不会用到, 随便填

通用模板如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
import sys
import glob
try:
import xml.etree.cElementTree as ET
except ImportError:
import xml.etree.ElementTree as ET
import os
from PIL import Image
import json
import numpy as np

# 输入: 多个 xml 文件, 每个 xml 文件对应了一张图片的标签
# 输出: 一个 json 文件, 文件中的数据格式符合 COCO 的格式要求

# 首先, 将需要转换的所有 xml 文件路径名获取到
xml_dir = 'path/to/xmls'
xmls = glob.glob(os.path.join(xml_dir, '*.xml'))

# 然后, 对于 COCO 数据集的每一项, 分别定义不同的函数来获取, 这种方式易于实现, 且简单直观

def xml_get_img_name(xml_path):
tree = ET.ElementTree(xml_path) # 打开一个 xml 文件, 并解析成树结构
root = tree.getroot()
return root.find('filename').text # 返回找到的文件名(图片名)

def xml_get_bboxes(xml_path):
tree = ET.ElementTree(xml_path) # 打开一个 xml 文件, 并解析成树结构
root = tree.getroot()
objects = root.findall('object')
bboxes = [] # 因为一张图片中有多个 bbox, 因此, 我们要返回一个列表
for obj in objects:
obj_name = obj.find('name').text
bbox = obj.find('bndbox')
x1 = int(bbox.find('xmin').text)
y1 = int(bbox.find('ymin').text)
x2 = int(bbox.find('xmax').text)
y2 = int(bbox.find('ymax').text)
bboxes.append([x1, y1, x2, y2, int(obj_name)])
return bboxes


json_dict = {} # 创建最外层的 json 字典

# 建立 info 部分
json_dict['info'] = {} # 这里是字典, 一个 json 文件只有一个 info 信息
json_dict['info']['contributor'] = 'zhaozheng'
json_dict['info']['year'] = 'zhaozheng'
json_dict['info']['description'] = 'zhaozheng'
json_dict['info']['url'] = 'zhaozheng'
json_dict['info']['date_created'] = 'zhaozheng'
# 还可以自行完善其他信息, info 部分不影响训练

# 建立 licenses 部分
json_dict['licenses'] = [] # 注意, 这里是列表, 这是由 COCO 的数据格式决定的, 说明一个json文件可以有多个 license
tmp_dict = {} # 申请临时的字典, 构建单个 license
tmp_dict['id'] = 1
tmp_dict['name'] = 'xxxx License'
json_dict['licenses'].append(tmp_dict) # 这里只添加一个 license, 可以根据实际情况进行添加

# 建立 images 部分
json_dict['images'] = [] # 注意, 是列表, 列表的长度代表了数据集中图片的数量
for i in range(len(xmls)): # 注意, xml 文件与图片一一对应, 这里我们顺序访问, 并用 i 作为每一张 img 的 id, 后面会利用这个 i 给每一个 box 填充 img_id
tmp_dict = {} # 创建单个 img 的临时字典
tmp_dict['id'] = i
tmp_dict['file_name'] = xml_get_img_name(xml[i])
img_path = os.path.join(xml_dir, tmp_dict['file_name'])
img = cv2.imread(img_path)# 默认 xml 与 jpg 的一一对应文件在同一个目录下
tmp_dict['height'] = img.shape[0]
tmp_dict['width'] = img.shape[1]
tmp_dict['flickr_url'] = '' # coco 格式要求, 训练时不会用到, 填空即可
tmp_dict['coco_url'] = '' # 同理
tmp_dict['date_captured'] = '' # 同理
tmp_dict['license'] = 1 # 这里赋予的是 license 的id, 我们上面定义了 id 为 1 的license

json_dict['images'].append(tmp_dict) # images 中的一项已经填充完毕, 将其添加到列表中


# 建立 categories 部分, 对于目标检测任务来说, 除了 annotations 之外还有 categories 关键字
json_dict['categories'] = [] # 同理, 多个类别, 每个类别构成一个字典, 字典中包含该类别信息, 列表的长度代表字典的个数, 也就是类别的个数
cats = ['cat', 'dog', 'fish']
for i in range(len(cats)):
tmp_dict = {}
tmp_dict['id'] = i
tmp_dict['name'] = cats[i]
tmp_dict['supercategory'] = 'animal'
json_dict['categories'].append(tmp_dict)

# 建立 annotations 部分
json_dict['annotations'] = []
id = 0 # bbox 的 id
for i in range(len(xmls)): # 为了方便知道当前 bbox 对应的 img id, 我们从每个 xml 文件进行遍历
# 获取当前图片中的所有 bbox
bboxes = xml_get_bboxes(xmls[i])
for bbox in bboxes:
# bbox = [x1, y1, x2, y2, bbox]
tmp_dict = {}
tmp_dict['id'] = id
id += 1
tmp_dict['image_id'] = i
tmp_dict['category_id'] = bbox[4]
x = bbox[0] + bbox[2] // 2
y = bbox[1] + bbox[3] // 2
width = bbox[2] - bbox[0]
height = bbox[3] - bbox[1]
tmp_dict['bbox'] = [x, y, width, height]
tmp_dict['segmentation'] = []
tmp_dict['area'] = w * h
tmp_dict['iscrowd'] = 0
json_dict['annotations'].append(tmp_dict)