傳統的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式子(也是大部分實驗中採用的式子):
代碼解析:
- for循環遍歷每一個物體框
- 將該物體框與得分最高的物體框進行交換,交換內容包括:面積、座標值,得分
- 交換後得分最高的物體框排在當前序列的第一位
- 計算其他物體框與最高得分物體框的IOU值
- 通過不同的方式計算得分權重值
- 遍歷結束,返回得分值高於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