Object Detection[^1]

Ground truth

Class + X coordinate + Y coordinate + Box Width + Box Height

评估架构

1
2
3
4
5
6
7
8
9
10
{% mermaid graph LR %}
A(置信度阈值)--模型的预测-->C;
B(IoU阈值)--后期的评估-->C;
C(TP,FP,TN,FN)-->D(precision);
C-->E(recall);
C-->G(accuracy);
D-->F(mAP);
E-->F
G-->F
{% endmermaid %}

IoU - Intersection over Union

FP,FN,TP,TN[^2]

为了计算precision和recall,与所有机器学习问题一样,我们必须鉴别出TP、FP、TN、FN。

指标 含义
True Positive 真正例 被判定为正样本,事实上也是证样本。
False Positive 假正例 被判定为正样本,但事实上是负样本。
True Negative 真负例 被判定为负样本,事实上也是负样本。
False Negative 假负例 被判定为负样本,但事实上是正样本。

P(Positive)和N(Negative) 代表模型的判断结果

T(True)和F(False) 评价模型的判断结果是否正确

Accuracy,Precision,Recall[^2]

指标 含义 公式
准确率(Accuracy) 模型判断正确的数据占总数据的比例 $Acc=\frac{TP+TN}{TP+TN+FP+FN}$
精确率/查准率(Precision) 针对模型判断出的所有正例而言,其中真正例占的比例 $Pre=\frac{TP}{TP+FP}$
召回率(Recall) 模型正确判断出的正例(TP)占数据集中所有正例的比例. $Rec=\frac{TP}{TP+FN}$

mAP[^1]

原因

  • 目标检测问题中的模型的分类和定位都需要进行评估,每个图像都可能具有不同类别的不同目标,因此,在图像分类问题中所使用的标准度量不能直接应用于目标检测问题。

计算

理解

  • mAP的计算收到IoU和置信度阈值两个参数的影响

    • IoU是一个简单的几何度量,可以很容易标准化,比如在PASCAL VOC竞赛中采用的IoU阈值为0.5,而COCO竞赛中在计算mAP较复杂,其计算了一系列IoU阈值(0.05至0.95)下的mAP。
    • 置信度却在不同模型会差异较大,可能在我的模型中置信度采用0.5却等价于在其它模型中采用0.8置信度,这会导致precision-recall曲线变化。
  • PASCAL VOC提出了一种可以用于任何模型的评估指标。此为2007年提出,比较简单,后续:2010年提出使用所有数据点而非11个recall[^3],COCO数据集中还需计算不同IoU阈值和物体大小下的AP[^4]。

    For a given task and class, the precision/recall curve is computed from a method’s ranked output.

    The AP summarises the shape of the precision/recall curve, and is defined as the mean precision at a set of eleven equally spaced recall levels [0,0.1,…,1]

    1. 对模型预测结果进行排序(ranked output,按照各个预测值置信度降序排列)。那么给定一个rank,Recall和Precision仅在高于该rank值的预测结果中计算,改变rank值会改变recall值。这里共选择11个不同的recall([0, 0.1, …, 0.9, 1.0]),可以认为是选择了11个rank,由于按照置信度排序,所以实际上等于选择了11个不同的置信度阈值。
    2. 那么,AP就定义为在这11个recall下precision的平均值,其可以表征整个precision-recall曲线。
    3. 对于各个类别,分别按照上述方式计算AP,取所有类别的AP平均值就是mAP。这就是在目标检测问题中mAP的计算方法。

例子

与上一小节中理解的叙述有区别(单图特定类->数据集特定类->数据集多类/整体),该例子的计算方法特别简单(未涉及置信度),记录下来只为加深理解。实际使用上还是要参考code。

1. 对于每个图像,我们都有ground truth的数据(即知道每个图像的真实目标信息),因此也知道了该图像中给定类别的实际目标(B)的数量。因此我们计算该类模型的精度(A/B) ,即给定一张图像的类别C的Precision=图像正确预测(True Positives)的数量除以在图像中这一类的总的目标数量

$$Precision_C=\frac{N(TruePositives)_C}{N(TotalObjects)_C}$$

2. 假如现在有一个给定的类,验证集中有100个图像,并且我们知道每个图像都有其中的所有类(基于ground truth)。所以我们可以得到100个精度值,计算这100个精度值的平均值,得到的就是该类的平均精度。 即一个C类的平均精度=在验证集上所有的图像对于类C的精度值的和/有类C这个目标的所有图像的数量。

$$AveragePrecision_C=\frac{\sum Precision_C}{N(TotalImages)_C}$$

3. 现在假如我们整个集合中有20个类,则可以获得20个不同的平均精度值。使用这些平均精度值,我们可以轻松的判断任何给定类别的模型的性能。为了选用一个单一的数字来表示一个模型的表现,我们可以取所有类的平均精度值的平均值,即MAP(均值平均精度)。

$$MeanAveragePrecision=\frac{\sum AveragePrecision_C}{N(Classes)}$$

其他

当比较mAP值,记住以下要点:

  • mAP通常是在一个数据集上计算得到的。
  • 虽然解释模型输出的绝对量化并不容易,但mAP作为一个相对较好的度量指标可以帮助我们。 当我们在流行的公共数据集上计算这个度量时,该度量可以很容易地用来比较目标检测问题的新旧方法。
  • 根据训练数据中各个类的分布情况,mAP值可能在某些类(具有良好的训练数据)非常高,而其他类(具有较少/不良数据)却比较低。所以你的mAP可能是中等的,但是你的模型可能对某些类非常好,对某些类非常不好。因此,建议在分析模型结果时查看各个类的AP值。这些值也许暗示你需要添加更多的训练样本。

代码

VOC数据集

Facebook开源的Detectron包含VOC数据集的mAP计算,这里贴出其核心实现,以对mAP的计算有更深入的理解。

  1. 首先是precision和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
    # 按照置信度降序,排序预测框坐标
    sorted_ind = np.argsort(-confidence)
    BB = BB[sorted_ind, :]  
    image_ids = [image_ids[x] for x in sorted_ind] # 各个预测框的对应图片id
    # 遍历预测框,并统计TPs和FPs
    nd = len(image_ids)
    tp = np.zeros(nd)
    fp = np.zeros(nd)
    for d in range(nd):
       R = class_recs[image_ids[d]]
       bb = BB[d, :].astype(float)
       ovmax = -np.inf
       BBGT = R['bbox'].astype(float)  # ground truth

       if BBGT.size > 0:
           # intersection
           ixmin = np.maximum(BBGT[:, 0], bb[0])
           iymin = np.maximum(BBGT[:, 1], bb[1])
           ixmax = np.minimum(BBGT[:, 2], bb[2])
           iymax = np.minimum(BBGT[:, 3], bb[3])
           iw = np.maximum(ixmax - ixmin + 1., 0.) # +1?
           ih = np.maximum(iymax - iymin + 1., 0.)
           inters = iw * ih        
    # union
           uni = ((bb[2] - bb[0] + 1.) * (bb[3] - bb[1] + 1.) +
                  (BBGT[:, 2] - BBGT[:, 0] + 1.) *
                  (BBGT[:, 3] - BBGT[:, 1] + 1.) - inters)
    # 计算IoU
    overlaps = inters / uni
    # 取最大的IoU,并返回对应索引
           ovmax = np.max(overlaps)
           jmax = np.argmax(overlaps)  

       if ovmax > ovthresh:  # 是否大于阈值
           if not R['difficult'][jmax]:  # 非difficult物体
               if not R['det'][jmax]:    # 未被检测
                   tp[d] = 1.
                   R['det'][jmax] = 1    # 标记已被检测
               else:
                   fp[d] = 1.
       else:
           fp[d] = 1.
    # 计算precision recall
    fp = np.cumsum(fp)
    tp = np.cumsum(tp)
    rec = tp / float(npos)# avoid divide by zero in case the first detection matches a difficult
    # ground truth
    prec = tp / np.maximum(tp + fp, np.finfo(np.float64).eps)
  2. 这里最终得到一系列的precision和recall值,并且这些值是按照置信度降低排列统计的,可以认为是取不同的置信度阈值(或者rank值)得到的。然后据此可以计算AP

    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
    def voc_ap(rec, prec, use_07_metric=False):
       """Compute VOC AP given precision and recall. If use_07_metric is true, uses
       the VOC 07 11-point method (default:False).
       """
       if use_07_metric:  # 使用07年方法
           # 11 个点
           ap = 0.
           for t in np.arange(0., 1.1, 0.1):
               if np.sum(rec >= t) == 0:
                   p = 0
               else:
                   p = np.max(prec[rec >= t])  # 插值
               ap = ap + p / 11.
       else:  # 新方式,计算所有点
           # correct AP calculation
           # first append sentinel values at the end
           mrec = np.concatenate(([0.], rec, [1.]))
           mpre = np.concatenate(([0.], prec, [0.]))
           
           # compute the precision 曲线值(也用了插值)
           for i in range(mpre.size - 1, 0, -1):
               mpre[i - 1] = np.maximum(mpre[i - 1], mpre[i])
           
           # to calculate area under PR curve, look for points
           # where X axis (recall) changes value
           i = np.where(mrec[1:] != mrec[:-1])[0]
           
           # and sum (\Delta recall) * prec
           ap = np.sum((mrec[i + 1] - mrec[i]) * mpre[i + 1])
       return ap
  3. 计算各个类别的AP值后,取平均值就可以得到最终的mAP值了。

COCO数据集[^5]

[^1]: 绝对不容错过:最完整的检测模型评估指标mAP计算指南(附代码)在这里!
[^2]: FP,FN,TP,TN与精确率(Precision),召回率(Recall),准确率(Accuracy)
[^3]: Everingham M, Eslami S M A, Van Gool L, et al. The pascal visual object classes challenge: A retrospective[J]. International journal of computer vision, 2015, 111(1): 98-136. paper
[^4]: Cocodataset / Detection Evaluation
[^5]: https://github.com/cocodataset/cocoapi/blob/master/PythonAPI/pycocotools/cocoeval.py