参考 https://zhuanlan.zhihu.com/p/60707912
mmap的解释:
给定一组IOU阈值,在每个IOU阈值下面,求所有类别的AP,并将其平均起来,作为这个IOU阈值下的检测性能,称为mAP(比如[email protected]就表示IOU阈值为0.5时的mAP);最后,将所有IOU阈值下的mAP进行平均,就得到了最终的性能评价指标:mmAP。
值得注意的一个细节是:在实际计算时,mmAP并不会把所有检测结果都考虑进来,因为那样总是可以达到Recall=100%,为了更有效地评价检测结果,mmAP只会考虑每张图片的前100个结果。这个数字应该随着数据集变化而改变,比如如果是密集场景数据集,这个数字应该提高。
分别读取检测结果的json文件和gt的json文件,然后进行评估
(1)COCOeval的初始化
cocoEval = COCOeval(cocoGt, cocoDt, iou_type)
其中cocoGt和cocoDt都是pycocotools.coco.COCO类
self.cocoGt = cocoGt # ground truth COCO API
self.cocoDt = cocoDt # detections COCO API
self.evalImgs = defaultdict(list) # per-image per-category evaluation results [KxAxI] elements
self.eval = {} # accumulated evaluation results
self._gts = defaultdict(list) # gt for evaluation
self._dts = defaultdict(list) # dt for evaluation
self.params = Params(iouType=iouType) # parameters
self._paramsEval = {} # parameters for evaluation
self.stats = [] # result summarization
self.ious = {} # ious between all gts and dts
if not cocoGt is None:
self.params.imgIds = sorted(cocoGt.getImgIds())
self.params.catIds = sorted(cocoGt.getCatIds())
(2)设置cocoEval的相关属性
cocoEval.params.catIds = self.cat_ids
cocoEval.params.imgIds = self.img_ids
cocoEval.params.maxDets = list(proposal_nums)
cocoEval.params.iouThrs = iou_thrs
p = self.params
p.imgIds = list(np.unique(p.imgIds))
p.catIds = list(np.unique(p.catIds))
p.maxDets = sorted(p.maxDets)
#prepare:将gt和dt重新处理为字典。
self._prepare()
gts=self.cocoGt.loadAnns(self.cocoGt.getAnnIds(imgIds=p.imgIds))
dts=self.cocoDt.loadAnns(self.cocoDt.getAnnIds(imgIds=p.imgIds))
self._gts = defaultdict(list)
for gt in gts:
#(image_id,category_id)作为key,value是一个list,包含了该图该类别下的每一个gt
self._gts[gt['image_id'], gt['category_id']].append(gt)
dts做相同的处理,得到self._dts
#self.ious是一个字典,key为(imgId,catId),value是一个N*M的array,N是该图该类别的gt_bbox,M是该图该类别的det_bbox,array是gt_bbox和det_bbox的IoU
self.ious = {(imgId, catId): computeIoU(imgId, catId) for imgId in p.imgIds for catId in catIds}
#self.evalImgs生成每张图片每个类别在10个不同的IoU的阈值(0.5-0.95)下的匹配情况。
self.evalImgs = [evaluateImg(imgId, catId, areaRng, maxDet)
for catId in
catIds
for areaRng in p.areaRng
for imgId in p.imgIds
]
def evaluateImg(self, imgId, catId, aRng, maxDet):
self.evalImgs共imagescateroryarea range条,每一条表示当前图像、当前类别、当前目标范围的各个目标的检测结果。
self.evalImgs[0]中的dtMatches:
共10行,每一行表示在当前IoU阈值下,dt box的匹配情况。因为我们总共有10个阈值(array([0.5 , 0.55, 0.6 , 0.65, 0.7 , 0.75, 0.8 , 0.85, 0.9 , 0.95])),所以结果有10行。
每一行的具体含义,以第一行(1,0,2)为例,表示dt box 1<->gt box1,dt box3<->gt box2。可以参考下面的self._dts和self._gts来理解。
self.evalImgs[0]中的gtMatches:
gtMatches的含义与dtMatches基本一致。
注意:dtIds和gtMatches是针对全部的检测目标而言,而不是针对某张图或某个类别。所以在后面的匹配信息中,dtIds和gtMatches都会很大
def accumulate(self, p = None):
'''
Accumulate per image evaluation results and store the result in self.eval
:param p: input params for evaluation
:return: None
'''
print('Accumulating evaluation results...')
tic = time.time()
if not self.evalImgs:
print('Please run evaluate() first')
# allows input customized parameters
if p is None:
p = self.params
p.catIds = p.catIds if p.useCats == 1 else [-1]
T = len(p.iouThrs)#T=10,iouThrs为array([0.5 , 0.55, 0.6 , 0.65, 0.7 , 0.75, 0.8 , 0.85, 0.9 , 0.95])
R = len(p.recThrs)# R01,p.recThrs为0-1,0.001为间隔
K = len(p.catIds) if p.useCats else 1#K是类别数,K=4
A = len(p.areaRng)#A=4,p.areaRng[[0, 10000000000.0], [0, 1024], [1024, 9216], [9216, 10000000000.0]]
M = len(p.maxDets)#M=3,maxDets为[100, 300, 1000]
precision = -np.ones((T,R,K,A,M)) # shape为(10,101,4,4,3)-1 for the precision of absent categories
recall = -np.ones((T,K,A,M))#shape为(4,4,3)
scores = -np.ones((T,R,K,A,M))# shape为(10,101,4,4,3)
# create dictionary for future indexing
_pe = self._paramsEval
catIds = _pe.catIds if _pe.useCats else [-1]
setK = set(catIds)#{0, 1, 2, 3}
setA = set(map(tuple, _pe.areaRng))#{(0, 10000000000.0), (1024, 9216), (0, 1024), (9216, 10000000000.0)}
setM = set(_pe.maxDets)#{1000, 100, 300}
setI = set(_pe.imgIds)#所有的图像
# get inds to evaluate
k_list = [n for n, k in enumerate(p.catIds) if k in setK]#[0, 1, 2, 3]
m_list = [m for n, m in enumerate(p.maxDets) if m in setM]#[100, 300, 1000]
a_list = [n for n, a in enumerate(map(lambda x: tuple(x), p.areaRng)) if a in setA]#[0, 1, 2, 3]
i_list = [n for n, i in enumerate(p.imgIds) if i in setI]
I0 = len(_pe.imgIds)#1468
A0 = len(_pe.areaRng)#4
# retrieve E at each category, area range, and max number of detections
#T、R、K、A、M分别代表(IoU的threshold的个数,0.5-0.95共10个),(recall的阈值个数,从0.0-1.0共分了100组),(类别数,每个类别分别统计),(area的个数,目前共all,small,middle,big4个值),(MaxDet的个数,共100,300,1000这3种选项)
输出结果为
precision,shape为T R K A M,以precision[0,0,0,0,0],其含义为(IoU阈值选为0.5时),(只挑选det score大于某个阈值的det_box,使得good_det_box/all_gt_box的值小于0.01,但是大于0.00),(针对某个类别),(针对某个区域大小),(在指定的MaxDet)下的precision,即满足上述条件的det_box/all_det_box. 当R的阈值很小时,我们对recall的要求低,因此我们尽量挑选出质量高的det_box,所以precision大。
scores, shape为T R K A M.以scores[0,0,0,0,0]为例,其含义为(IoU阈值选为0.5时),(只挑选det score大于某个阈值的det_box,使得good_det_box/all_gt_box的值小于0.01,但是大于0.00),(针对某个类别),(针对某个区域大小),(在指定的MaxDet)下的score, 当R的阈值较小时,要求我们选出来高质量的det_box, score的值也比较大。
recall,shape为TKAM,表示recall[0,0,0,0]表示(IoU阈值选为0.5时),(该类别),(该区域大小下),(MaxDet为指定值)时的recall值,即所有的det_box中的good_det_box/all_gt_box
实际使用时:
(1)AP,mAP,或mmAP评估更科学,但是不太直观。针对某一具体问题,我们需要知道某一类别在指定IoU阈值,指定Det_box的分数阈值时,该类别的TP,FP,FN。目前的COCO eval并没有给出这些,需要我们去修改pycocotools/cocoeval.py,保存gt的个数、TP的个数、FP的个数。
gt_all[k,a,m] = npig
tp_all[t,k,a,m] = tp[-1]
fp_all[t,k,a,m] = fp[-1]
修改coco的evaluate函数,保存tp,fp,gt
IoU_pr_p,k_pr_p,area_pr_p,maxdet_pr_p = pr_params
tp_pr_p = cocoEval.eval['tp_all'][IoU_pr_p,k_pr_p,area_pr_p,maxdet_pr_p]
fp_pr_p = cocoEval.eval['fp_all'][IoU_pr_p,k_pr_p,area_pr_p,maxdet_pr_p]
gt_pr_p = cocoEval.eval['gt_all'][k_pr_p,area_pr_p,maxdet_pr_p]
with open(osp.join(out_dir,'tp_fp_fn.csv'),'w') as t_f_file:
t_f_file.write(f'tp:{tp_pr_p} fp:{fp_pr_p} gt:{gt_pr_p} \r\n')
#构造iou_thrs array([0.5 , 0.55, 0.6 , 0.65, 0.7 , 0.75, 0.8 , 0.85, 0.9 , 0.95])
iou_thrs = np.linspace(.5, 0.95, int(np.round((0.95 - .5) / .05)) + 1, endpoint=True)
result_files, tmp_dir = self.format_results(results, jsonfile_prefix)
#我们的metrics只有1个,[bbox]
for metric in metrics:
#将检测结果转为指定的格式<pycocotools.coco.COCO object at 0x7f464c2c4e10>
cocoDt = cocoGt.loadRes(result_files[metric])
#cocoGt,cocoDt分别表示gt和检测结果,iou_type为'bbox'
cocoEval = COCOeval(cocoGt, cocoDt, iou_type)
#cocoEval.params的属性catIds、imgIds(各个图片的image_id)、maxDets(用于计算recall,比如recall@100,recall@300,recall@1000,默认值[100,300,1000])
cocoEval.params
#
cocoEval.evaluate()
cocoEval.accumulate()
cocoEval.summarize()
https://zhuanlan.zhihu.com/p/60707912
其他的语句就是一些plt.相关的用来画图的语句了。
plt.grid是用来在图中加网格。
plt.legend是用来在图中添加注释。