pycocotools
的使用非常广泛,但是貌似并不支持输出每一类的AP
值,尤其是AP50
,但是在很多数据集上每一类的指标是必须的,所以修改了一些现有的pycocotools
,方便使用。
用法:
在所有的安装流程结束,并且你的算法已经测试可以用之后,卸载自动安装的pycocotools
,重新安装:
1 2 3 4 5 6 |
pip uninstall pycocotools git clone https://github.com/ma3252788/mcjpycocotools.git # Gitee备份 git clone https://gitee.com/mcj686/mcjpycocotools.git cd mcjpycocotools pip install . |
可以完全替代原有pycocotools
,其他功能都没动,只是增加了类别的显示。
结果大概长这个样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
IoU metric: bbox Average Precision (AP) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.136 Average Precision (AP) @[ IoU=0.50 | area= all | maxDets=100 ] = 0.355 Average Precision (AP) @[ IoU=0.75 | area= all | maxDets=100 ] = 0.065 Average Precision (AP) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = -1.000 Average Precision (AP) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.125 Average Precision (AP) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.214 Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets= 1 ] = 0.263 Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets= 10 ] = 0.357 Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.443 Average Recall (AR) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = -1.000 Average Recall (AR) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.386 Average Recall (AR) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.439 Category 0's AP50 - C1 : 0.07680768076807681 Category 1's AP50 - C2 : 0.48648723419809553 Category 2's AP50 - C3 : 0.3595831820151127 Category 3's AP50 - C4 : 0.47395630972138675 Category 4's AP50 - C5 : 0.37917377944691016 (all categories) mAP : 0.3552016372299164 |
目前还没有做美化的事,欢迎提PR
。
一些有用的COCOAPI:
首先需要引入这个包,然后创建一个 coco
对象,创建时要将 annotation
的路径传进去
1 2 |
from pycocotools.coco import COCO coco = COCO('/NAS_REMOTE/PUBLIC/data/coco2017/annotations/instances_train2017.json') |
如果直接运行的话就会输出以下 log
1 2 3 4 |
loading annotations into memory... Done (t=15.79s) creating index... index created! |
创建好了 coco
对象后就可以使用他的一些 API
了,下面列举一些常用的,后续有用到其他的话也会继续更新
getImgIds
coco.getImgIds(imgIds=[], catIds=[])
可以获取到 coco
所有图片对应的 id
号,以便后续处理,另外,传入 catIds
参数的话也可以只返回特定的类别的图片对应的 id
号,但是 catIds
需要通过其他方式获得,假设我们知道狗的 catId
为 18
,我们就可以通过下面代码得到所有有狗的图片的 id
号
1 |
coco.getImgIds(catIds=18) |
[98304, 204800, 524291, 311301, 491525, 147471, 131087, 278550, 581654, 253981, 450590, 106525, 368676, 253988, …]
getCatIds
getCatIds(catNms=[], supNms=[], catIds=[])
可以获取到 coco
类别对应的 label
号,因为 coco
有 80
类,所以如果不传入其他参数的话返回的就是一个拥有 80
个元素的列表(不是连续的数字,中间会跳过几个数字
1 |
coco.getCatIds() |
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, …]
当然也可以传入类别名字,筛选出特定类别代表的 label
,如下说明 dog
的标签为 18
1 |
coco.getCatIds(catNms='dog') |
[18]
getAnnIds
getAnnIds(imgIds=[], catIds=[], areaRng=[], iscrowd=None)
获取某图像对应的 annotation
的 id
号,即 groundtruth
,以下说明了该图有 4
个 标注
1 |
coco.getAnnIds(imgIds=coco.getImgIds()[0]) |
[151091, 202758, 1260346, 1766676]
loadImgs
loadImgs(ids=[])
用的时候会在里面加上图片对应的 id
号返回某张图片的信息
1 |
coco.loadImgs(ids=coco.getImgIds()[0]) |
[{‘coco_url’: ‘http://images.cocoda…391895.jpg’, ‘date_captured’: ‘2013-11-14 11:18:45’, ‘file_name’: ‘000000391895.jpg’, ‘flickr_url’: ‘http://farm9.staticf…8349_z.jpg’, ‘height’: 360, ‘id’: 391895, ‘license’: 3, ‘width’: 640}]
loadCats
loadCats(ids=[])
用法和上面差不多,是通过类别的 label
来返回className
1 |
coco.loadCats(ids=18) |
[{‘id’: 18, ‘name’: ‘dog’, ‘supercategory’: ‘animal’}]
loadAnns
loadAnns(ids=[])
用来加载特定 annotation id
号的标注
1 |
coco.loadAnns(ids=202758) |
[{‘area’: 14107.271300000002, ‘bbox’: […], ‘category_id’: 1, ‘id’: 202758, ‘image_id’: 391895, ‘iscrowd’: 0, ‘segmentation’: […]}]
showAnns
showAnns(anns, draw_bbox=False)
同上,如果获取到了 Anno
信息之后,可以用这个函数直接将 Anno
信息给可视化出来,挺有意思的
1 2 3 4 5 6 7 8 9 10 11 |
dataset_dir = '/NAS_REMOTE/PUBLIC/data/coco2017/train2017/' coco = COCO(os.path.join('/NAS_REMOTE/PUBLIC/data/coco2017', 'annotations', 'instances_' + 'train2017' + '.json')) image_ids = coco.getImgIds() img = coco.loadImgs(image_ids[0])[0] I = io.imread(dataset_dir + img['file_name']) plt.axis('off') plt.imshow(I) annIds = coco.getAnnIds(imgIds=image_ids[0]) annos = coco.loadAnns(ids=annIds) coco.showAnns(annos) plt.show() |
还可以在 showAnns
中将 draw_bbox
参数变成 True
来画出 bbox
其他
类别统计,检测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 |
import pycocotools.coco as COCO def check_coco_json(annot_path): coco = COCO.COCO(annot_path) cats = coco.loadCats(coco.getCatIds()) cat_nms=[cat['name'] for cat in cats] print('-' * 40) print('COCO categories number: {}'.format(len(cats))) print('COCO categories: {}'.format(' |'.join(cat_nms))) # 统计各类的图片数量和标注框数量 catID = coco.getCatIds() # 顺序很重要 imgID = coco.getImgIds() annID = coco.getAnnIds() for i,cid in enumerate(catID): if i+1 != cid: catID_e = True print('\nWaring: catgory id is not right cid:{} != {}'.format(cid,i)) if len(set(imgID)) != len(imgID): print('Error: pic num is not equal to pic id numbers') else: print('\nAll img number:',len(imgID)) if len(set(annID)) != len(annID): annID_e = True print('Error: annID repeat!') else: print('All ann label number:',len(annID)) print('-'*40) print("{:<15} {:<6} {:<10} {:<8}".format('Catetories', 'image_num', 'target_num','class_id')) for cat_name in cat_nms: catId = coco.getCatIds(catNms=cat_name) if len(catId)>1: catId = [catId[-1]] imgId = coco.getImgIds(catIds=catId) annId = coco.getAnnIds(imgIds=imgId, catIds=catId, iscrowd=None) if len(imgId)==0: aa = 1 print("{:<15} {:<6d} {:<10d} {}".format(cat_name, len(imgId), len(annId),catId)) if __name__ == "__main__": annot_path = '/home/kcadmin/user/20200106/aa.json' check_coco_json(annot_path) |
cocoAPI
进行map
,recall
计算
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 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 |
#-*-coding:utf-8-*- ''' cocoAIP 1. 获取类别名称对应的id 2. 获取类别(单类)对应的图片id 4. 获取多类对应的图片id 3. 获取图片id对应的图片名称 4. 通过gt的json文件和pred的json文件计算map 5. 通过gt和pred计算每个类别的ap,recall ''' import pickle, json import numpy as np from pycocotools.coco import COCO from pycocotools.cocoeval import COCOeval import itertools from terminaltables import AsciiTable def read_pickle(pkl): with open(pkl,'rb') as f: data = pickle.load(f) return data def read_json(json_pth): with open(json_pth,'r') as f: data = json.load(f) return data det_json = 'data/det.json' gt_json = 'data/gt.json' CLASSES = ('A','B','C') class_num = len(CLASSES) cocoGt = COCO(gt_json) # 获取所有图片的id all_id = cocoGt.getImgIds() # 获取类别(单类)对应的所有图片id catIds = cocoGt.getCatIds(catNms=list(CLASSES)) #,'long','meihua' # 获取多个类别对应的所有图片的id imgid_list = [] for id_c in catIds: imgIds = cocoGt.getImgIds(catIds=id_c) imgid_list.extend(imgIds) imgid_list = list(set(imgid_list)) # 获取图片id对应的图片路径 img_info = cocoGt.load_imgs([imgid_list[0]])[0] fname = img_info['file_name'] # 通过gt的json文件和pred的json文件计算map det_json = 'data/det.json' gt_json = 'data/gt.json' CLASSES = ('A','B','C') class_num = len(CLASSES) cocoGt = COCO(gt_json) cocoDt = cocoGt.loadRes(det_json) cocoEval = COCOeval(cocoGt, cocoDt, "bbox") cocoEval.params.iouThrs = np.linspace(0.5, 0.95, int(np.round((0.95 - .5) / .05)) + 1, endpoint=True) cocoEval.params.maxDets = list((100, 300, 1000)) cocoEval.evaluate() cocoEval.accumulate() cocoEval.summarize() # 过gt和pred计算每个类别的recall precisions = cocoEval.eval['precision'] # TP/(TP+FP) right/detection recalls = cocoEval.eval['recall'] # iou*class_num*Areas*Max_det TP/(TP+FN) right/gt print('\nIOU:{} MAP:{:.3f} Recall:{:.3f}'.format(cocoEval.params.iouThrs[0],np.mean(precisions[0, :, :, 0, -1]),np.mean(recalls[0, :, 0, -1]))) # Compute per-category AP # from https://github.com/facebookresearch/detectron2/ # precision: (iou, recall, cls, area range, max dets) results_per_category = [] results_per_category_iou50 = [] res_item = [] for idx, catId in enumerate(range(class_num)): name = CLASSES[idx] precision = precisions[:, :, idx, 0, -1] precision_50 = precisions[0, :, idx, 0, -1] precision = precision[precision > -1] recall = recalls[ :, idx, 0, -1] recall_50 = recalls[0, idx, 0, -1] recall = recall[recall > -1] if precision.size: ap = np.mean(precision) ap_50 = np.mean(precision_50) rec = np.mean(recall) rec_50 = np.mean(recall_50) else: ap = float('nan') ap_50 = float('nan') rec = float('nan') rec_50 = float('nan') res_item = [f'{name}', f'{float(ap):0.3f}',f'{float(rec):0.3f}'] results_per_category.append(res_item) res_item_50 = [f'{name}', f'{float(ap_50):0.3f}', f'{float(rec_50):0.3f}'] results_per_category_iou50.append(res_item_50) item_num = len(res_item) num_columns = min(6, len(results_per_category) * item_num) results_flatten = list( itertools.chain(*results_per_category)) headers = ['category', 'AP', 'Recall'] * (num_columns // item_num) results_2d = itertools.zip_longest(*[ results_flatten[i::num_columns] for i in range(num_columns) ]) table_data = [headers] table_data += [result for result in results_2d] table = AsciiTable(table_data) print('\n' + table.table) num_columns_50 = min(6, len(results_per_category_iou50) * item_num) results_flatten_50 = list( itertools.chain(*results_per_category_iou50)) iou_ = cocoEval.params.iouThrs[0] headers_50 = ['category', 'AP{}'.format(iou_),'Recall{}'.format(iou_)] * (num_columns_50 // item_num) results_2d_50 = itertools.zip_longest(*[ results_flatten_50[i::num_columns_50] for i in range(num_columns_50) ]) table_data_50 = [headers_50] table_data_50 += [result for result in results_2d_50] table_50 = AsciiTable(table_data_50) print('\n' + table_50.table) |