天天看點

目标檢測算法評估目标檢測算法評估

目标檢測算法評估

目錄

目标檢測的評估

引論

precision和recall

PR曲線和AP,mAP

VOC的mAP評價代碼

總結

引論

現在關于深度學習的算法層出不窮,那麼我們應該用什麼名額去評判一個算法的優缺點呢?

以目标檢測為例,要想準确地評價目标檢測算法,對目标檢測的評估工作肯定是少不了的。通常包括對速度的評價和對精度的評價,對速度的評價名額有FPS(Frame Per Second)即每秒處理的圖檔數,對精度的評價名額有精度(precision),召回率(recall),準确率(Accuracy),平均準确率(MAP)。

下面我來重點介紹幾個目标檢測的名額。

precision和recall

解釋精确率和召回率之前,先來看下混淆矩陣

目标檢測算法評估目标檢測算法評估

把正例正确分類為正例,表示為TP(true positive),把正例錯誤分類為負例,表示為FN(false negative),

把負例正确分類為負例,表示為TN(true negative), 把負例錯誤分類為正例,表示為FP(false positive)

接着我們應該了解準确率(precision)和召回率(recall)的基本計算方式,參考下圖:

目标檢測算法評估目标檢測算法評估

由圖可知:

  • 左邊一整個矩形中(false negative和true positive)的數表示ground truth之中為1的(即為正确的)資料
  • 右邊一整個矩形中的數表示ground truth之中為0的資料
  • 精度precision的計算是用檢測正确的資料個數/總的檢測個數
  • 召回率recall的計算是用檢測正确的資料個數/ground truth之中所有正資料個數

    可得如下公式

對于目标檢測,我們通常設定一個

IOU的門檻值

來表示是否檢測正确,也就是一個檢測box和相應目标的ground truth的IOU超過一定的門檻值,并且分類正确則認為檢測到一個正确的目标。

PR曲線和AP,mAP

由上述定義可知,召回率和準确率受到了門檻值設定的影響,而且門檻值對于兩個名額的影響是相反的:門檻值增加則準确率增加,召回率降低,反之亦然。那麼我們就可以通過設定一系列的門檻值來得到一系列的(準确率,召回率)的名額對,然後利用這些名額對畫出坐标圖,這就是

PR曲線

,而AP(average precision)就是這個曲線下的面積。

mAP就是多個分類任務的AP的平均值。

VOC的mAP評價代碼

def voc_ap(self, rec, prec, use_07_metric=True):
    if use_07_metric:
        ap = 0.
        # 2010年以前按recall等間隔取11個不同點處的精度值做平均(0., 0.1, 0.2, …, 0.9, 1.0)
        for t in np.arange(0., 1.1, 0.1):
            if np.sum(rec >= t) == 0:
                p = 0
            else:
                # 取最大值等價于2010以後先計算包絡線的操作,保證precision非減
                p = np.max(prec[rec >= t])
            ap = ap + p / 11.
    else:
        # 2010年以後取所有不同的recall對應的點處的精度值做平均
        # first append sentinel values at the end
        mrec = np.concatenate(([0.], rec, [1.]))
        mpre = np.concatenate(([0.], prec, [0.]))

        # 計算包絡線,從後往前取最大保證precision非減
        for i in range(mpre.size - 1, 0, -1):
            mpre[i - 1] = np.maximum(mpre[i - 1], mpre[i])

        # 找出所有檢測結果中recall不同的點
        i = np.where(mrec[1:] != mrec[:-1])[0]

        # and sum (\Delta recall) * prec
        # 用recall的間隔對精度作權重平均
        ap = np.sum((mrec[i + 1] - mrec[i]) * mpre[i + 1])
    return ap

# 計算每個類别對應的AP,mAP是所有類别AP的平均值
def voc_eval(self, detpath,
             classname,
             ovthresh=0.5,
             use_07_metric=True):
    # 提取所有測試圖檔中目前類别所對應的所有ground_truth
    class_recs = {}
    npos = 0
    # 周遊所有測試圖檔
    for imagename in imagenames:
        # 找出所有目前類别對應的object
        R = [obj for obj in recs[imagename] if obj['name'] == classname]
        # 該圖檔中該類别對應的所有bbox
        bbox = np.array([x['bbox'] for x in R])
        difficult = np.array([x['difficult'] for x in R]).astype(np.bool)
        # 該圖檔中該類别對應的所有bbox的是否已被比對的标志位
        det = [False] * len(R)
        # 累計所有圖檔中的該類别目标的總數,不算diffcult
        npos = npos + sum(~difficult)
        class_recs[imagename] = {'bbox': bbox,
                                'difficult': difficult,
                                'det': det}

    # 讀取相應類别的檢測結果檔案,每一行對應一個檢測目标
    if any(lines) == 1:
        # 某一行對應的檢測目标所屬的圖像名
        image_ids = [x[0] for x in splitlines]
        # 讀取該目标對應的置信度
        confidence = np.array([float(x[1]) for x in splitlines])
        # 讀取該目标對應的bbox
        BB = np.array([[float(z) for z in x[2:]] for x in splitlines])

        # 将該類别的檢測結果按照置信度大小降序排列
        sorted_ind = np.argsort(-confidence)
        sorted_scores = np.sort(-confidence)
        BB = BB[sorted_ind, :]
        image_ids = [image_ids[x] for x in sorted_ind]
        # 該類别檢測結果的總數(所有檢測出的bbox的數目)
        nd = len(image_ids)
        # 用于标記每個檢測結果是tp還是fp
        tp = np.zeros(nd)
        fp = np.zeros(nd)
        # 按置信度周遊每個檢測結果
        for d in range(nd):
            # 取出該條檢測結果所屬圖檔中的所有ground truth
            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:
                ......
                overlaps = inters / uni
                ovmax = np.max(overlaps)
                jmax = np.argmax(overlaps)
            # 如果最大的重疊度大于一定的門檻值
            if ovmax > ovthresh:
                # 如果最大重疊度對應的ground truth為difficult就忽略
                if not R['difficult'][jmax]:
                    # 如果對應的最大重疊度的ground truth以前沒被比對過則比對成功,即tp
                    if not R['det'][jmax]:
                        tp[d] = 1.
                        R['det'][jmax] = 1
                    # 若之前有置信度更高的檢測結果比對過這個ground truth,則此次檢測結果為fp
                    else:
                        fp[d] = 1.
            # 該圖檔中沒有對應類别的目标ground truth或者與所有ground truth重疊度都小于門檻值
            else:
                fp[d] = 1.

        # 按置信度取不同數量檢測結果時的累計fp和tp
        # np.cumsum([1, 2, 3, 4]) -> [1, 3, 6, 10]
        fp = np.cumsum(fp)
        tp = np.cumsum(tp)
        # 召回率為占所有真實目标數量的比例,非減的,注意npos本身就排除了difficult,是以npos=tp+fn
        rec = tp / float(npos)
        # 精度為取的所有檢測結果中tp的比例
        prec = tp / np.maximum(tp + fp, np.finfo(np.float64).eps)
        # 計算recall-precise曲線下面積(嚴格來說并不是面積)
        ap = self.voc_ap(rec, prec, use_07_metric)
    # 如果這個類别對應的檢測結果為空,那麼都是-1
    else:
        rec = -1.
        prec = -1.
        ap = -1.

    return rec, prec, ap
           

總結

mAP計算的總體思想總結如下,得到檢測結果dets之後:

  1. 将所有的det_box按det_score進行排序
  2. 計算每個det_box與所有gt_box(ground-truth)的IOU
  3. 取IOU最大(max_IOU)的gt_box作為這個det_box的預測結果是否正确的判斷依據,然後根據max_IOU的結果判斷預測結果是TP還是FP

針對上述的第3步,每個類别單獨處理:

  • preTP:max_IOU大于ovp_thresh,同時分類結果與max_IOU對應的gt_box的類别是一緻的,則該det_box歸為preTP,同時将這個gt_box從候選gt_box中去除。
  • preFP:det_box是A類,但是對應max_IOU的gt_box是B類。
    • 如果一個det_box的max_IOU與一個已經被前面det_score較大的det_box對應并且從候選的gt_box中去除了,則這個det_box也是一個preFP。
  • FN:沒有被檢測到的gt_box,也就是沒有det_box的max_IOU的目标是這個gt_box。

上述的計算過程可以簡化,也就是對每個det_box,我們計算與其預測類别一樣的gt_box的IOU就行,然後取max_IOU,如果max_IOU大于ovp_thresh,并且這個max_IOU對應的gt_box還沒有被别的det_box預測(設定一個found的标志位),則這個det_box就是preTP,并将該gt_box的found設定為true,否則就是preFP。周遊完之後就可以判斷,found為false的gt_box為FN。

注意到,上述的過程中,det_score是沒有用到的,隻是最初做了一個排序,是以求得的是preTP和preFP,還不是最終結果,然後在不同的det_score的門檻值下處理上述的結果,就得到了TP和FP,就可以計算不同門檻值下的recall和precision,畫出PR曲線,計算每個類别的ap,然後得到目标檢測算法的mAP。