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)