https://blog.csdn.net/m0_37605642/article/details/98358864
參考博客
目標定位和檢測系列(3):交並比(IOU)和非極大值抑制(NMS)的python實現
一、NMS(非極大抑制)概念
NMS即non maximum suppression即非極大抑制,顧名思義就是抑制不是極大值的元素,搜索局部的極大值。在最近幾年常見的物體檢測算法(包括rcnn、sppnet、fast-rcnn、faster-rcnn等)中,最終都會從一張圖片中找出很多個可能是物體的矩形框,然后為每個矩形框為做類別分類概率。
就像上面的圖片一樣,定位一個車輛,最后算法就找出了一堆的方框,我們需要判別哪些矩形框是沒用的。
所謂非極大值抑制:先假設有6個矩形框,根據分類器類別分類概率做排序,從小到大分別屬於車輛的概率分別為A<B<C<D<E<F。
(1) 從最大概率矩形框F開始,分別判斷A、B、C、D、E與F的重疊度IOU是否大於某個設定的閾值;
(2) 假設B、D與F的重疊度超過閾值,那么就扔掉B、D;並標記第一個矩形框F,是我們保留下來的。
(3) 從剩下的矩形框A、C、E中,選擇概率最大的E,然后判斷A、C與E的重疊度,重疊度大於一定的閾值,那么就扔掉;並標記E是我們保留下來的第二個矩形框。
(4) 重復這個過程,找到所有被保留下來的矩形框。
二、YOLO中的NMS
參考文章 目標檢測算法之YOLO
對於每一個種類的概率,比如Dog,我們將所有98個框按照預測概率從高到低排序(為方便計算,排序前可以剔除極小概率的框,也就是把它們的概率置為0),然后通過非極大抑制NMS方法,繼續剔除多余的框:
NMS方法在這里如何運行呢?首先因為經過了排序,所以第一個框是概率最大的框(下圖橘色)。然后繼續掃描下一個框跟第一個框,看是否IOU大於0.5:
的確IOU大於0.5,那么第二個框是多余的,將它剔除:
繼續掃描到第三個框,它與最大概率框的IOU小於0.5,需要保留:
繼續掃描到第四個框,同理需要保留:
繼續掃描后面的框,直到所有框都與第一個框比較完畢。此時保留了不少框。
接下來,以次大概率的框(因為一開始排序過,它在順序上也一定是保留框中最靠近上一輪的基礎框的)為基礎,將它后面的其它框於之比較。
如比較第4個框與之的IOU:
IOU大於0.5,所以可以剔除第4個框:
總之在經歷了所有的掃描之后,對Dog類別只留下了兩個框:
這時候,或許會有疑問:明顯留下來的藍色框,並非Dog,為什么要留下?因為對計算機來說,圖片可能出現兩只Dog,保留概率不為0的框是安全的。不過的確后續設置了一定的閾值(比如0.3)來刪除掉概率太低的框,這里的藍色框在最后並沒有保留,因為它在20種類別里要么因為IOU不夠而被刪除,要么因為最后閾值不夠而被剔除。
上面描述了對Dog種類進行的框選擇。接下來,我們還要對其它19種類別分別進行上面的操作。最后進行縱向跨類的比較(為什么?因為上面就算保留了橘色框為最大概率的Dog框,但該框可能在Cat的類別也為概率最大且比Dog的概率更大,那么我們最終要判斷該框為Cat而不是Dog)。判定流程和法則如下:
得到最后的結果:
三、Python程序實現NMS
NMS的算法步驟如下:
# INPUT:所有預測出的bounding box (bbx)信息(坐標和置信度confidence), IOU閾值(大於該閾值的bbx將被移除) for object in all objects: (1) 獲取當前目標類別下所有bbx的信息 (2) 將bbx按照confidence從高到低排序,並記錄當前confidence最大的bbx (3) 計算最大confidence對應的bbx與剩下所有的bbx的IOU,移除所有大於IOU閾值的bbx (4) 對剩下的bbx,循環執行(2)和(3)直到所有的bbx均滿足要求(即不能再移除bbx)
需要注意的是,NMS是對所有的類別分別執行的。舉個栗子,假設最后預測出的矩形框有2類(分別為cup, pen),在NMS之前,每個類別可能都會有不只一個bbx被預測出來,這個時候我們需要對這兩個類別分別執行一次NMS過程。
我們用python編寫NMS代碼,假設對於一張圖片,所有的bbx信息已經保存在一個字典中,保存形式如下:
predicts_dict: {"cup": [[x1_1, y1_1, x2_1, y2_1, scores1], [x1_2, y1_2, x2_2, y2_2, scores2], ...], "pen": [[x1_1, y1_1, x2_1, y2_1, scores1], [x1_2, y1_2, x2_2, y2_2, scores2], ...]}
即目標的位置和置信度用列表儲存,每個列表中的一個子列表代表一個bbx信息。詳細的代碼如下:
import numpy as np def non_max_suppress(predicts_dict, threshold=0.2): """ implement non-maximum supression on predict bounding boxes. Args: predicts_dict: {"stick": [[x1, y1, x2, y2, scores1], [...]]}. threshhold: iou threshold Return: predicts_dict processed by non-maximum suppression """ for object_name, bbox in predicts_dict.items(): #對每一個類別的目標分別進行NMS bbox_array = np.array(bbox, dtype=np.float) ## 獲取當前目標類別下所有矩形框(bounding box,下面簡稱bbx)的坐標和confidence,並計算所有bbx的面積 x1, y1, x2, y2, scores = bbox_array[:,0], bbox_array[:,1], bbox_array[:,2], bbox_array[:,3], bbox_array[:,4] areas = (x2-x1+1) * (y2-y1+1) #print("areas shape = ", areas.shape) ## 對當前類別下所有的bbx的confidence進行從高到低排序(order保存索引信息) order = scores.argsort()[::-1] print("order = ", order) keep = [] #用來存放最終保留的bbx的索引信息 ## 依次從按confidence從高到低遍歷bbx,移除所有與該矩形框的IOU值大於threshold的矩形框 while order.size > 0: i = order[0] keep.append(i) #保留當前最大confidence對應的bbx索引 ## 獲取所有與當前bbx的交集對應的左上角和右下角坐標,並計算IOU(注意這里是同時計算一個bbx與其他所有bbx的IOU) xx1 = np.maximum(x1[i], x1[order[1:]]) #當order.size=1時,下面的計算結果都為np.array([]),不影響最終結果 yy1 = np.maximum(y1[i], y1[order[1:]]) xx2 = np.minimum(x2[i], x2[order[1:]]) yy2 = np.minimum(y2[i], y2[order[1:]]) inter = np.maximum(0.0, xx2-xx1+1) * np.maximum(0.0, yy2-yy1+1) iou = inter/(areas[i]+areas[order[1:]]-inter) print("iou =", iou) print(np.where(iou<=threshold)) #輸出沒有被移除的bbx索引(相對於iou向量的索引) indexs = np.where(iou<=threshold)[0] + 1 #獲取保留下來的索引(因為沒有計算與自身的IOU,所以索引相差1,需要加上) print("indexs = ", type(indexs)) order = order[indexs] #更新保留下來的索引 print("order = ", order) bbox = bbox_array[keep] predicts_dict[object_name] = bbox.tolist() predicts_dict = predicts_dict return predicts_dict
四、行人檢測中的NMS
參考博客:https://blog.csdn.net/weixin_38632246/article/details/100542184
如果兩個人靠得很近,將很難確定NMS的閾值,太大則會導致誤檢多,太小導致漏檢多