Soft—NMS/非極大值抑制

傳統的NMS算法問題:可以看Figure1在Fiugre1中,檢測算法本來應該輸出兩個框,但是傳統的NMS算法可能會把score較低的綠框過濾掉(如果綠框和紅框的IOU大於設定的閾值就會被過濾掉),導致只檢測出一個object(一個馬),顯然這樣object的recall就比較低了。

Figure2是Soft NMS算法的僞代碼。首先是關於三個輸入B、S、Nt,在FIgure2中已經介紹很清楚了。D集合用來放最終的box,在boxes集合B非空的前提下,搜索score集合S中數值最大的數,假設其下標爲m,那麼bm(也是M)就是對應的box。然後將M和D集合合併,並從B集合中去除M。再循環集合B中的每個box,這個時候就有差別了,如果是傳統的NMS操作,那麼當B中的box bi和M的IOU值大於閾值Nt,那麼就從B和S中去除該box;如果是Soft NMS,則對於B中的box bi也是先計算其和M的IOU,然後該IOU值作爲函數f()的輸入,最後和box bi的score si相乘作爲最後該box bi的score。就是這麼簡單!

接下來得重點就是如何確定函數f()了。
首先NMS算法可以用下面的式子表示:

爲了改變NMS這種hard threshold做法,並遵循iou越大,得分越低的原則(iou越大,越有可能是false positive),自然而然想到可以用下面這個公式來表示Soft NMS 

但是上面這個公式是不連續的,這樣會導致box集合中的score出現斷層,因此就有了下面這個Soft NMS式子(也是大部分實驗中採用的式子): 

 代碼解析:

  1. for循環遍歷每一個物體框
  2. 將該物體框與得分最高的物體框進行交換,交換內容包括:面積、座標值,得分
  3. 交換後得分最高的物體框排在當前序列的第一位
  4. 計算其他物體框與最高得分物體框的IOU值
  5. 通過不同的方式計算得分權重值
  6. 遍歷結束,返回得分值高於thread的物體框的索引
def py_cpu_softnms(dets, sc, Nt=0.3, sigma=0.5, thresh=0.001, method=2):
    """
    py_cpu_softnms
    :param dets:   boexs 座標矩陣 format [y1, x1, y2, x2]
    :param sc:     每個 boxes 對應的分數
    :param Nt:     iou 交疊門限
    :param sigma:  使用 gaussian 函數的方差
    :param thresh: 最後的分數門限
    :param method: 使用的方法
    :return:       留下的 boxes 的 index
    """

    # indexes concatenate boxes with the last column
    N = dets.shape[0]
    indexes = np.array([np.arange(N)])
    dets = np.concatenate((dets, indexes.T), axis=1)

    # the order of boxes coordinate is [y1,x1,y2,x2]
    y1 = dets[:, 0]
    x1 = dets[:, 1]
    y2 = dets[:, 2]
    x2 = dets[:, 3]
    scores = sc
    areas = (x2 - x1 + 1) * (y2 - y1 + 1)

    for i in range(N):
        # intermediate parameters for later parameters exchange
        #將物體框dets[i,:]與得分最高的物體框進行交換
        tBD = dets[i, :].copy()
        tscore = scores[i].copy()
        tarea = areas[i].copy()
        pos = i + 1
        if i != N-1:
            maxscore = np.max(scores[pos:], axis=0)
            maxpos = np.argmax(scores[pos:], axis=0)   #maxpos爲scores[pos:]中最大元素的序號,maxpos + pos(=i+1)爲最大元素在scores中的序號
        else:
            maxscore = scores[-1]
            maxpos = 0
        if tscore < maxscore:
            #交換兩個框的座標
            dets[i, :] = dets[maxpos + i + 1, :]
            dets[maxpos + i + 1, :] = tBD
            tBD = dets[i, :]
            #交換兩個框的得分
            scores[i] = scores[maxpos + i + 1]
            scores[maxpos + i + 1] = tscore
            tscore = scores[i]
            # 交換兩個框的面積
            areas[i] = areas[maxpos + i + 1]
            areas[maxpos + i + 1] = tarea
            tarea = areas[i]

        # IoU calculate
        xx1 = np.maximum(dets[i, 1], dets[pos:, 1])
        yy1 = np.maximum(dets[i, 0], dets[pos:, 0])
        xx2 = np.minimum(dets[i, 3], dets[pos:, 3])
        yy2 = np.minimum(dets[i, 2], dets[pos:, 2])

        w = np.maximum(0.0, xx2 - xx1 + 1)
        h = np.maximum(0.0, yy2 - yy1 + 1)
        inter = w * h
        ovr = inter / (areas[i] + areas[pos:] - inter)

        # Three methods: 1.linear 2.gaussian 3.original NMS
        if method == 1:  # linear
            weight = np.ones(ovr.shape)
            weight[ovr > Nt] = weight[ovr > Nt] - ovr[ovr > Nt]
        elif method == 2:  # gaussian
            weight = np.exp(-(ovr * ovr) / sigma)
        else:  # original NMS
            weight = np.ones(ovr.shape)
            weight[ovr > Nt] = 0

        scores[pos:] = weight * scores[pos:]

    # select the boxes and keep the corresponding indexes
    inds = dets[:, 4][scores > thresh]
    keep = inds.astype(int)

    return keep

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章