### 计算 mAP 的过程
1. **初始化和准备工作:**
- 加载并初始化评估参数和结果,包括类别、IoU 阈值、召回率阈值、面积范围和最大检测数等。
- 初始化用于存储精度、召回率、得分和 F1 分数的数组。
2. **遍历每个类别、面积范围和最大检测数量:**
- 对于每个类别、面积范围和最大检测数组合,从 `self.evalImgs` 中提取相应的评估结果。
- 合并所有图像的检测得分、匹配结果和忽略标记。
3. **排序和匹配处理:**
- 根据检测得分进行排序。
- 计算真阳性(TP)和假阳性(FP)。
4. **累积计算:**
- 使用 `np.cumsum` 计算累积真阳性(TP)和假阳性(FP)。
- 根据累积真阳性计算召回率(recall)。
- 根据累积真阳性和假阳性计算精度(precision)。
5. **确保精度的单调递减性:**
- 通过从后向前遍历,确保精度数组是单调递减的,以便于插值计算。
6. **插值计算和存储结果:**
- 根据预定义的召回率阈值,使用 `np.searchsorted` 查找对应的召回率和精度值。
- 计算并存储不同召回率下的精度值、得分和 F1 分数。
### 代码详解
```python
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')if p is None:p = self.paramsp.catIds = p.catIds if p.useCats == 1 else [-1]T = len(p.iouThrs)R = len(p.recThrs)K = len(p.catIds) if p.useCats else 1A = len(p.areaRng)M = len(p.maxDets)precision = -np.ones((T, R, K, A, M))recall = -np.ones((T, K, A, M))f1 = -np.ones((T, K, A, M))scores = -np.ones((T, R, K, A, M))# create dictionary for future indexing_pe = self._paramsEvalcatIds = _pe.catIds if _pe.useCats else [-1]setK = set(catIds)setA = set(map(tuple, _pe.areaRng))setM = set(_pe.maxDets)setI = set(_pe.imgIds)k_list = [n for n, k in enumerate(p.catIds) if k in setK]m_list = [m for n, m in enumerate(p.maxDets) if m in setM]a_list = [n for n, a in enumerate(map(lambda x: tuple(x), p.areaRng)) if a in setA]i_list = [n for n, i in enumerate(p.imgIds) if i in setI]I0 = len(_pe.imgIds)A0 = len(_pe.areaRng)for k, k0 in enumerate(k_list):Nk = k0 * A0 * I0for a, a0 in enumerate(a_list):Na = a0 * I0for m, maxDet in enumerate(m_list):E = [self.evalImgs[Nk + Na + i] for i in i_list]E = [e for e in E if e is not None]if len(E) == 0:continuedtScores = np.concatenate([e['dtScores'][0:maxDet] for e in E])inds = np.argsort(-dtScores, kind='mergesort')dtScoresSorted = dtScores[inds]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:continuetps = np.logical_and(dtm, np.logical_not(dtIg))fps = np.logical_and(np.logical_not(dtm), np.logical_not(dtIg))tp_sum = np.cumsum(tps, axis=1).astype(dtype=np.float32)fp_sum = np.cumsum(fps, axis=1).astype(dtype=np.float32)for t, (tp, fp) in enumerate(zip(tp_sum, fp_sum)):tp = np.array(tp)fp = np.array(fp)nd = len(tp)rc = tp / npigpr = tp / (fp + tp + np.spacing(1))q = np.zeros((R,))ss = np.zeros((R,))if nd:recall[t, k, a, m] = rc[-1]else:recall[t, k, a, m] = 0pr = pr.tolist()q = q.tolist()for i in range(nd - 1, 0, -1):if pr[i] > pr[i - 1]:pr[i - 1] = pr[i]inds = np.searchsorted(rc, p.recThrs, side='left')try:for ri, pi in enumerate(inds):q[ri] = pr[pi]ss[ri] = dtScoresSorted[pi]except:passprecision[t, :, k, a, m] = np.array(q)scores[t, :, k, a, m] = np.array(ss)# 计算单一召回率下的 F1 分数single_recall = rc[-1] # 取最大召回率q_array = pr[-1]if single_recall + q_array != 0:f1_score = 2 * (q_array * single_recall) / (q_array + single_recall + np.spacing(1))else:f1_score = np.zeros_like(q_array)f1[t, k, a, m] = f1_scoreself.eval = {'params': p,'counts': [T, R, K, A, M],'date': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),'precision': precision,'recall': recall,'scores': scores,'f1': f1 # 新增 F1 分数}toc = time.time()print('DONE (t={:0.2f}s).'.format(toc - tic))
```
### 总结
- COCO 计算 mAP 的过程是逐步增加检测数量,计算相应的召回率和精度值。
- 通过累积真阳性和假阳性数 (`tp_sum` 和 `fp_sum`),计算累积召回率和精度。
- 确保精度数组是单调递减的,以便于插值计算。
- 根据预定义的召回率阈值,插值计算并存储不同召回率下的精度值和 F1 分数。
这一过程确保了 mAP 计算的准确性和一致性,使得评估结果能够反映模型在不同召回率阈值下的综合表现。