介绍
官网:https://cocodataset.org/
下载地址:https://cocodataset.org/#download
COCO的全称是Common Objects in COntext,起源于微软于2014年出资标注党的Microsoft COCO数据,与ImageNet竞赛一样,是计算机视觉领域最受关注和权威的比赛之一。
COCO数据是一个大型、丰富的目标检测、分割和字幕数据集。其中一些样本如下所示。
COCO数据集主要包含有标注和无标注的数据:
- 2014:训练集+验证集+测试集
- 2015:测试集
- 2017:训练集+验证集+测试集+未标注
如上图和下表所示,两个数据的图像数量分别如下,其中测试集都未提供标注文件:
COCO2014 | COCO2017 | ||||
---|---|---|---|---|---|
train | val | test | train | val | test |
82783 | 40504 | 40775 | 118287 | 5000 | 40670 |
COCO数据集格式
COCO数据集包含5种类型的标注,分别是:目标检测、实例分割、关键点检测、全景分割和图像标注,都对应一个json文件。每个json文件主要包含如下五个部分:
{"info": info,"licenses": [license],"images": [image],"annotations": [annotation],"categories": [category]
}
info部分
info部分包含数据集的一些基本信息。
"info":
{"description": "COCO 2017 Dataset","url": "http://cocodataset.org","version": "1.0","year": 2017,"contributor": "COCO Consortium","date_created": "2017/09/01"
}
licenses部分
licenses部分包含数据集需要遵循的一些许可。
{"url":"http:\/\/creativecommons.org\/licenses\/by-nc-sa\/2.0\/","id":1,"name":"Attribution-NonCommercial-ShareAlike License"
}
images部分
images中描述了数据集的图像信息,主要包括文件名、图像宽高信息。
{"coco_url": "", "date_captured": "", "file_name": "000001.jpg", # 只需要包含文件名即可, 在MMDetection训练中需要指定图像的路径"flickr_url": "", "id": 1, # image_id"license": 0, "width": 416, # 图像宽高"height": 416
}
categories描述了数据集中的类别信息,annotations中包含的是数据集的物体信息。不同的任务对应的json文件中annotation和categories的形式不同,分别如下:
目标检测和实例分割
目标检测和实例分割任务的标注文件为instances_train2017.json、instances_val2017.json这两个文件。
annotations部分
如下所示,每个对象实例都包含一系列字段,包括对象的类别ID、所属图像ID,annotation ID,分段掩码,目标框信息。
{"id": int,"image_id": int,"category_id": int,"segmentation": RLE or [polygon],"area": float,"bbox": [x,y,width,height],"iscrowd": 0 or 1
}
其中:
- bbox为每个对象提供一个包围框,[x,y]表示框的左上角。
- segmentation格式取决于这个实例是单个对象(即iscrowd=0,将使用polygons格式,以多边形顶点表示。注意,单个对象可能需要多个多边形表示,例如遮挡时。),还是一组对象(即iscrowd=1,将使用RLE格式,mask编码)。
- area表示标注区域的面积。如果是矩形框,那就是高乘以宽;如果是polygon或者RLE,则是mask区域围成的面积。
对于实例分割任务,annotations部分既要包含bbox元素,也要包含segmentation要素。而在segmentation部分由于实例的形式不同,表示的方法也不同。
polygon格式
polygon格式如下,比较简单,这些数按照相邻的顺序两两组成一个点的xy坐标,包含n个数必定为偶数,表示n/2个点坐标。
{"segmentation": [[68.59,227.1,...]],"area": 1441.7063, # 目标区域面积"iscrowd": 0,"image_id": 210520,"bbox": [68.59,200.56,50.24,47.77], # [x, y, width, height]边界框坐标, 其中x,y为图像左上角点"category_id": 50, # 类别ID"id": 2231047 # 对象ID
}
RLE格式
如果iscrowd=1,那么segmentation就是RLE格式(segmentation字段会含有counts和size数组),如下所示。COCO数据的RLE都uncompressed RLE格式(与之相对的是compact RLE)。RLE所占字节的大小和边界上的像素数量是正相关的。
segmentation部分中的counts和size数组共同组成了这幅图片中的分割mask。其中size表示这张图像的宽高,然后对于一副图像的一个segmentation而言,每一个像素点要么在分割的目标区域中,要么是背景。显然,如果该像素在目标区域中为1,在背景中为1。那么对于一个张240×320的图像来说,共有76800个像素点,这样表示的大小为76800个bit,但是这样写很浪费空间,RLE则直接用0或1的个数表示。
RLE,Run-Length Encoding,变动长度编码算法,是一种对于二值图像的编码方式,以不同码字来表示连续的黑、白像素数。RLE格式是一种更加高效的图像语义分割数据表示格式,其数据以一串RLE编码的方式进行存储,而不是以像素点的形式存储,有效减少了数据的体积。RLE格式分割标注文件即是使用RLE格式对物体分割区域进行标注的文件。
很多分割数据集为了节省空间,标注文件采用了RLE格式,比如COCO等。RLE格式带来的好处就是基于RLE去计算目标区域的面积以及两个目标之间的union和intersection交并集时效果很高。
{"segmentation": {"counts": [272,2,4,4,4,4,...],"size": [240,320]},"area": 18419,"iscrowd": 1,"image_id": 448263,"bbox": [1,0,276,122],"category_id": 1,"id": 900100448263
}
基于Python实现RLE格式分割标注文件的格式转换 - 海_纳百川 - 博客园
polygon转RLE
假设输入的多边形数据,可以通过如下方式将其转换为RLE格式,以便后续转换为mask
if isinstance(mask_ann, list):# polygon -- a single object might consist of multiple parts# we merge all parts into one mask rle coderles = maskUtils.frPyObjects(mask_ann, img_h, img_w)rle = maskUtils.merge(rles)
RLE转mask二值图像
对于RLE格式的数据,直接调用decode方法来生成mask,其中0表示背景,1表示前景。
def _poly2mask(mask_ann, img_h, img_w):"""Private function to convert masks represented with polygon tobitmaps.Args:mask_ann (list | dict): Polygon mask annotation input.img_h (int): The height of output mask.img_w (int): The width of output mask.Returns:numpy.ndarray: The decode bitmap mask of shape (img_h, img_w)."""if isinstance(mask_ann, list):# polygon -- a single object might consist of multiple parts# we merge all parts into one mask rle coderles = maskUtils.frPyObjects(mask_ann, img_h, img_w)rle = maskUtils.merge(rles)elif isinstance(mask_ann['counts'], list):# uncompressed RLErle = maskUtils.frPyObjects(mask_ann, img_h, img_w)else:# rlerle = mask_annmask = maskUtils.decode(rle)return maskdef save_mask(mask, filename):mask = mask * 255 im = Image.fromarray(mask)im.save(filename)if __name__ == "__main__":dataDir='datasets/COCO/'dataType='val2017'annFile='{}/annotations/instances_{}.json'.format(dataDir,dataType)# 初始化标注数据的 COCO api coco=COCO(annFile)imgIds = coco.getImgIds(imgIds = [549220])# 获取到image结构体信息image = coco.loadImgs(imgIds[np.random.randint(0,len(imgIds))])[0]I = Image.open('%s/%s/%s'%(dataDir,dataType,image['file_name'])) I.save(image['file_name'])# 获取到annotation结构体信息annIds = coco.getAnnIds(imgIds=image['id'], iscrowd=0)annotation = coco.loadAnns(annIds[0])[0]# 将多边形或者RLE数据转换为maskmask = _poly2mask(annotation["segmentation"], img_h=image["height"], img_w=image['width']) print(mask.shape, mask.dtype)# 保存masksave_mask(mask, "plot.png")
mask二值图像转RLE
基于上面的mask,转换为对应的RLE,步骤如下:
- 找到轮廓
- 将每个轮廓转换为RLE格式
def contours_to_rle(contours, image_width, image_height):# 创建空白掩码图像mask_image = np.zeros((image_height, image_width), dtype=np.uint8)# 将每个轮廓绘制在掩码图像上cv2.drawContours(mask_image, contours, -1, 255, -1)# 将二进制掩码图像转换为 RLE 格式rle_encoding = maskUtils.encode(np.asfortranarray(mask_image))rle_encoding['counts'] = rle_encoding['counts'].decode('utf-8')# 返回 RLE 格式的分割信息return rle_encoding
# mask为上述转换RLE得到的
contours, hierarchy = cv2.findContours(mask * 255, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = [c for c in contours if cv2.contourArea(c)>100]for contour in contours:# 将mask转换为RLEsrles = contours_to_rle(contour, mask.shape[1], mask.shape[0])# 以下代码是为了验证生成的RLEs是否有问题mask = _poly2mask(annotation["segmentation"], img_h=image["height"], img_w=image['width'])# 通过对比生成的mask可以知道不存在问题save_mask(mask, "plot.png")
categories部分
categories是一个包含多个category实例的数组,而category结构体描述如下:
{"id": int,"name": str,"supercategory": str,
}
从json标注文件中摘取两个category实例如下:
{"supercategory": "person","id": 1,"name": "person"
},
{"supercategory": "vehicle","id": 2,"name": "bicycle"
},
COCO2017数据集中,共有80个category。
object Keypoint类型的格式
person_keypoints_train2017.json和person_keypoints_val2017.json这两个文件是COCO2017数据中表示关键点的标注文件。在不同的JSON文件中info、licenses和images是共享的,不共享的是annotation和categories这两个字段。
annotations字段
这个annotation结构体中包含了目标检测和实例分割任务annotation结构体的所有字段,再加上2个额外的字段。结构如下:
annotation{"keypoints": [x1,y1,v1,...],"num_keypoints": int,"id": int,"image_id": int,"category_id": int,"segmentation": RLE or [polygon],"area": float,"bbox": [x,y,width,height],"iscrowd": 0 or 1,
}
其中keypoints是一个长度为3×k的数组,其中k是category中keypoints的总数量,在COCO中,k=17。每个keypoint是一个长度为3的数组,第一个和第二个元素表示关键点的x和y坐标值,第三个元素是一个标注位v。当v=0时表示这个关键点没有标注,v=1时表示这个关键点标注了但是不可见(遮挡),v为2时表示这个关键点标注了且可见。
num_keypoints表示这个目标上被标注的关键点数量,其中比较小的目标上可能无法标注全部的关键点。示例如下:
{"segmentation": [[125.12,539.69,140.94,522.43...]],"num_keypoints": 10,"area": 47803.27955,"iscrowd": 0,"keypoints": [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,142,309,1,177,320,2,191,398...],"image_id": 425226,"bbox": [73.35,206.02,300.58,372.5],"category_id": 1,"id": 183126
},
categories字段
COCO2017数据中categories字段中的元素数量为1,只有person一个。相比于检测和分割,keypoints的categories中新增了两个额外的元素。定义如下:
{"id": int,"name": str,"supercategory": str,"keypoints": [str],"skeleton": [edge]
}
其中keypoints元素是一个长度为k的数字,包含了每个关键点的名字;skeleton定义了哥哥关键点之间的的连接性(比如人的左手腕和左肘是连接的,但是左手腕和右手腕不是)。示例如下:
{"supercategory": "person","id": 1,"name": "person","keypoints": ["nose","left_eye","right_eye","left_ear","right_ear","left_shoulder","right_shoulder","left_elbow","right_elbow","left_wrist","right_wrist","left_hip","right_hip","left_knee","right_knee","left_ankle","right_ankle"],"skeleton": [[16,14],[14,12],[17,15],[15,13],[12,13],[6,12],[7,13],[6,7],[6,8],[7,9],[8,10],[9,11],[2,3],[1,2],[1,3],[2,4],[3,5],[4,6],[5,7]]
}
COCO数据集的标注格式
COCOAPI使用
coco安装
coco的安装方式非常简单,运行如下命令即可。
git clone https://github.com/pdollar/coco.gitcd coco/PythonAPI
# 如果使用的是 python2, 运行下面的命令:
make -j8
# 如果使用的是 python3, 需要更改 Makefile:
vi Makefile
# 将 Makefile 中的 python 改为 python3, 然后:
make -j8
coco使用
加载json文件,得到coco对象
- 导入相关的库
from pycocotools.coco import COCO
import numpy as np
import skimage.io as io
import matplotlib.pyplot as plt
import pylab
pylab.rcParams['figure.figsize'] = (8.0, 10.0)
- 构建coco对象
COCO是一个Microsoft COCO数据集的辅助类,用于读取和可视化标记文件。输入参数为JSON文件的路径
dataDir='/path/to/your/coco_data'
dataType='val2017'
annFile='{}/annotations/instances_{}.json'.format(dataDir,dataType)
# 初始化标注数据的 COCO api
coco=COCO(annFile)
coco对象创建完成之后会输出如下信息:
loading annotations into memory...
Done (t=0.81s)
creating index...
index created!
COCO类包含如下的属性:
- dataset:JSON文件加载之后读取的内容都包含在dataset属性中;
- img_ann_map:表示从img ID到ann的映射关系,通过img ID得到图像中包含哪些ann;
- cat_img_map:表示cat ID到img ID的映射关系,通过cat ID得到img ID,表示该图像中包含这些类别的数据;
- anns,cats,imgs:通过ID能得到对应的annotation、category、image结构体内容;
提供如下的常见方法:
getCatIds(self, catNms=[], supNms=[], catIds=[])
基于给定的类别名,超类名或者类别ID来获取到类别ID列表。
getImgIds(self, imgIds=[], catIds=[])
基于给定的img ID或者cat ID来获取img ID列表。
def loadAnns(self, ids=[]):
基于给定的ann ID来获取对应的annotation结构体列表。
def loadCats(self, ids=[]):
基于给定的cat ID来获取category结构体列表。
def loadImgs(self, ids=[]):
基于给定的img ID来获取image结构体列表。
def showAnns(self, anns, draw_bbox=False):
显示标注信息。通过plt.gca()获取当前figure的axes(轴),并在该figure上渲染标注内容。
显示数据集中的类别名称和超类
# 获取COCO数据集所有的类别ID
class_ids = coco.getCatIds()
# 基于类别ID得到对应的category结构体内容
cats = coco.loadCats(class_ids)
# 分析category结构体中name属性
names = [cat['name'] for cat in cats]
print('COCO categories: \n{}\n'.format(' '.join(names)))names = set([cat['supercategory'] for cat in cats])
print('COCO supercategories: \n{}'.format(' '.join(names)))
输出信息如下:
COCO categories:
person bicycle car motorcycle airplane bus train truck boat traffic light fire hydrant stop sign parking meter bench bird cat dog horse sheep cow elephant bear zebra giraffe backpack umbrella handbag tie suitcase frisbee skis snowboard sports ball kite baseball bat baseball glove skateboard surfboard tennis racket bottle wine glass cup fork knife spoon bowl banana apple sandwich orange broccoli carrot hot dog pizza donut cake chair couch potted plant bed dining table toilet tv laptop mouse remote keyboard cell phone microwave oven toaster sink refrigerator book clock vase scissors teddy bear hair drier toothbrushCOCO supercategories:
outdoor food indoor appliance sports person animal vehicle furniture accessory electronic kitchen
加载指定img-ID的图片并显示
下面是加载并显示指定image_id的例子。
# get all images containing given categories, select one at random
catIds = coco.getCatIds(catNms=['person','dog','skateboard'])
# 获取catIds对应的所有image_id
imgIds = coco.getImgIds(catIds=catIds )
# 输出[549220, 324158, 279278]
# 指定image_id
imgIds = coco.getImgIds(imgIds = [549220])
# loadImgs() 返回的是只有一个元素的列表, 使用[0]来访问这个元素
# 列表中的这个元素又是字典类型, 关键字有: ["license", "file_name", "coco_url", "height", "width", "date_captured", "id"]
img = coco.loadImgs(imgIds[np.random.randint(0,len(imgIds))])[0]# 加载并显示图片,可以使用两种方式: 1) 加载本地图片, 2) 在线加载远程图片
# 1) 使用本地路径, 对应关键字 "file_name"
I = Image.open('%s/%s/%s'%(dataDir,dataType,img['file_name'])) # 2) 使用 url, 对应关键字 "coco_url"
# I = io.imread(img['coco_url'])
plt.axis('off')
plt.imshow(I)
plt.savefig("plot.png", bbox_inches='tight',pad_inches=0.0)
下面就是指定image_id对应的图像数据:
#pic_center
加载segmentation标注信息并显示在图片上
下面这段代码的作用就是加载segmentation标注信息,并将其显示在图片上。
# 加载并显示标注信息
plt.imshow(I); plt.axis('off')
annIds = coco.getAnnIds(imgIds=img['id'], catIds=catIds, iscrowd=None)
anns = coco.loadAnns(annIds)
coco.showAnns(anns)
plt.savefig("plot.png", bbox_inches='tight',pad_inches=0.0)
输出效果如下:
在showAnns函数中会调用plt.gca()函数来获取当前“figure”对象,如果存在的话直接返回,不存在的话会新建一个”figure“对象,并将其轴返回。
(一) COCO Python API - 使用篇-CSDN博客