【代码】【未完】COCO评价准则的Python代码解读

cocoeval.py的COCOeval类

1. _prepare

  • 载入gts和dts,这两个都是列表,每一个元素包含bbox,img_id,类别等信息。
  • 如果存在ignore_id的话,将对应gt的’ignore’设置为TRUE
        self._gts = defaultdict(list)       # gt for evaluation
        self._dts = defaultdict(list)       # dt for evaluation
        for gt in gts:
            self._gts[gt['image_id'], gt['category_id']].append(gt)
        for dt in dts:
            self._dts[dt['image_id'], dt['category_id']].append(dt)
        self.evalImgs = defaultdict(list)   # per-image per-category evaluation results
        self.eval     = {}                  # accumulated evaluation results
  • 构建字典self._gts: [img_id, cat_id]和 self._dts: [img_id, cat_id],第一个索引代表输入哪张图片,第二个索引代表哪个类别。

2. evaluate

  • 计算IOU,对每一张图片,每一个类别计算IOU,字典self.ious : [img_id, cat],每一个元素为一个矩阵,行为d,列为g,存储iou值。
        self.ious = {(imgId, catId): computeIoU(imgId, catId) \
                        for imgId in p.imgIds
                        for catId in catIds}

然后调用evaluateImg评估每一个类别,每一个区域。每一个id。主要是进行匹配。得到 self.evalImgs:

evaluateImg返回值:
 'image_id':     imgId,
 'category_id':  catId,
 'aRng':         aRng,
 'maxDet':       maxDet,
 'dtIds':        [d['id'] for d in dt],
 'gtIds':        [g['id'] for g in gt],
 'dtMatches':    dtm,
 'gtMatches':    gtm,
 'dtScores':     [d['score'] for d in dt],
 'gtIgnore':     gtIg,
 'dtIgnore':     dtIg,
self.evalImgs = [evaluateImg(imgId, catId, areaRng, maxDet)
         for catId in catIds
         for areaRng in p.areaRng
         for imgId in p.imgIds
     ]
2.1 evaluateImg(self, imgId, catId, aRng, maxDet)
  • 将gt和dt,按照imgId和catId取出。
            gt = self._gts[imgId,catId]
            dt = self._dts[imgId,catId]
  • 将gt中area不在aRng内的_ignore标签设置为1,并将_ignore=0的排在前面。
  • 将dt的score按照从大到小排列。
  • iou矩阵取出来。
        gtind = np.argsort([g['_ignore'] for g in gt], kind='mergesort')
        gt = [gt[i] for i in gtind]
        dtind = np.argsort([-d['score'] for d in dt], kind='mergesort')
        dt = [dt[i] for i in dtind[0:maxDet]]
        iscrowd = [int(o['iscrowd']) for o in gt]
        # load computed ious
        ious = self.ious[imgId, catId][:, gtind] if len(self.ious[imgId, catId]) > 0 else self.ious[imgId, catId]

  • 设置一些下标:T : 阈值,G:gt,D:dt
  • 设置一些变量:gtm[T, G]:gt匹配的dt的id。dtm[T, D]:dt匹配的gt的id。
    gtIg[G]:gt的_ignore标签,dtIg[T, D]:dt匹配的gt的_ignore标签。

进行匹配,在给定的阈值下,各阈值互不影响
对每一个dt,寻找与之最为匹配的gt,

  • 如果gt已经匹配上了,continue
  • 如果dt已经匹配了一个没有ignore的gt,并且当前gt被ignore(由于之前有排序,这意味着剩下的gt都是ignore的,不必再排序),break
  • 如果iou小于已匹配值,continue
  • 记录下iou和m(匹配的id)。
    简而言之,将每个dt匹配给和它最接近的gt。

收尾:

  • 将那些匹配上的dt(dtm==1)中在areaRng范围外的det设置为ignore

3. accumulate(self, p = None)

设置各种变量,索引

    T           = len(p.iouThrs)
    R           = len(p.recThrs)
    K           = len(p.catIds) if p.useCats else 1
    A           = len(p.areaRng)
    M           = len(p.maxDets)
    precision   = -np.ones((T,R,K,A,M)) # -1 for the precision of absent categories
    recall      = -np.ones((T,K,A,M))
    scores      = -np.ones((T,R,K,A,M))
  • T : IOU阈值,10
  • R:recall阈值(recall的采样点,PR曲线的横轴),101
  • K:类别,80
  • A:区域范围,4
  • M:最大det数量,3
  • precision:TxRxKxAxM,-1代表缺失类别。记录各个recall阈值的precision值。
  • recall:TxKxAxM,只记录最大recall,因此没有R这一dim。
  • scores:TxRxKxAxM,与precision的index一致,记录各个recall阈值的recall值。
  • klist:catId。m_list:maxDet。a_list:area。i_list:image
    之后,
  • 遍历类别K
  • 遍历区域A
  • 遍历M
  • 由于evalImgs是按照K,A,I(imgId)的顺序摆放的,
    因此E为所有
# 遍历所有类别
for k, k0 in enumerate(k_list):
	Nk = k0*A0*I0
	# 遍历所有Area
	for a, a0 in enumerate(a_list):
		Na = a0*I0
		# 遍历所有MaxDet
		for m, maxDet in enumerate(m_list):
			# 由于evalImgs的存放顺序是:按照K,A,I(imgId)
			# E相当于取出了所有Image在当前k0,当前a0下的evaluate结果
			E = [self.evalImgs[Nk + Na + i] for i in i_list]
			E = [e for e in E if not e is None]
			if len(E) == 0:
				continue
			# 根据maxDet截取dtScores,并将所有图片的分数合并到一起。
			dtScores = np.concatenate([e['dtScores'][0:maxDet] for e in E])

			# different sorting method generates slightly different results.
			# mergesort is used to be consistent as Matlab implementation.
			inds = np.argsort(-dtScores, kind='mergesort')
			dtScoresSorted = dtScores[inds]
			
			# 将dt的匹配结果按照score的顺序取出。
			# dtm有两个维度。第一个维度为T,代表不同阈值;第二个维度才是匹配结果
			# 最后也是将所有图片的结果拼接到一起
			dtm  = np.concatenate([e['dtMatches'][:,0:maxDet] for e in E], axis=1)[:,inds]
			dtIg = np.concatenate([e['dtIgnore'][:,0:maxDet]  for e in E], axis=1)[:,inds]
			gtIg = np.concatenate([e['gtIgnore'] for e in E])
			npig = np.count_nonzero(gtIg==0 )
			if npig == 0:
				continue
			# True Positive就是匹配上
			# 这个操作对所有阈值都进行了
			tps = np.logical_and(               dtm,  np.logical_not(dtIg) )
			# False Positive就是没有匹配上
			fps = np.logical_and(np.logical_not(dtm), np.logical_not(dtIg) )
			# 对tps和fps进行积分,tps是单调递增序列,并且增加幅度最大为1。
			tp_sum = np.cumsum(tps, axis=1).astype(dtype=np.float)
			fp_sum = np.cumsum(fps, axis=1).astype(dtype=np.float)
			# 对每个阈值,计算Precision
			for t, (tp, fp) in enumerate(zip(tp_sum, fp_sum)):
				tp = np.array(tp)
				fp = np.array(fp)
				nd = len(tp)
				# 计算recall和precision
				# 其中recall的值单调递增,序号代表一个一个的dt,值代表在此序号之前的recall是多少
				# precision的值不一定。序号也是代表dt。非单调递减
				rc = tp / npig
				pr = tp / (fp+tp+np.spacing(1))
				q  = np.zeros((R,))
				ss = np.zeros((R,))
				# recall就是最大的recall
				if nd:
					recall[t,k,a,m] = rc[-1]
				else:
					recall[t,k,a,m] = 0

				# numpy is slow without cython optimization for accessing elements
				# use python array gets significant speed improvement
				pr = pr.tolist(); q = q.tolist()
				# 从后往前遍历pr,将pr修剪为单调递减的形状(该形状为包裹住原pr曲线的最小梯形)。
				for i in range(nd-1, 0, -1):
					if pr[i] > pr[i-1]:
						pr[i-1] = pr[i]
				# 搜索rec阈值(101个)在rc中的index,并用这个index来获得Precision
				inds = np.searchsorted(rc, p.recThrs, side='left')
				try:
					for ri, pi in enumerate(inds):
						q[ri] = pr[pi]
						ss[ri] = dtScoresSorted[pi]
				except:
					pass
				# q中存储的就是PR曲线在各个recall阈值的precision值
				precision[t,:,k,a,m] = np.array(q)
				# scores存储的是dt在各个recall阈值的score值。
				scores[t,:,k,a,m] = np.array(ss)

你可能感兴趣的:(代码)