睿智的目標檢測31——非極大抑制NMS與Soft-NMS
學習前言
非極大抑制是目標檢測中非常非常非常非常非常重要的一部分,瞭解一下原理,撕一下代碼是必要的!
什麼是非極大抑制NMS
非極大抑制的概念只需要看這兩幅圖就知道了:
下圖是經過非極大抑制的。
下圖是未經過非極大抑制的。
可以很明顯的看出來,未經過非極大抑制的圖片有許多重複的框,這些框都指向了同一個物體!
可以用一句話概括非極大抑制的功能就是:
篩選出一定區域內屬於同一種類得分最大的框。
1、非極大抑制NMS的實現過程
本博文實現的是多分類的非極大抑制,該非極大抑制使用在我的pytorch-yolov3例子中:
輸入shape爲[ batch_size, all_anchors, 5+num_classes ]
第一個維度是圖片的數量。
第二個維度是所有的預測框。
第三個維度是所有的預測框的預測結果。
非極大抑制的執行過程如下所示:
1、對所有圖片進行循環。
2、找出該圖片中得分大於門限函數的框。在進行重合框篩選前就進行得分的篩選可以大幅度減少框的數量。
3、判斷第2步中獲得的框的種類與得分。取出預測結果中框的位置與之進行堆疊。此時最後一維度裏面的內容由5+num_classes變成了4+1+2,四個參數代表框的位置,一個參數代表預測框是否包含物體,兩個參數分別代表種類的置信度與種類。
4、對種類進行循環,非極大抑制的作用是篩選出一定區域內屬於同一種類得分最大的框,對種類進行循環可以幫助我們對每一個類分別進行非極大抑制。
5、根據得分對該種類進行從大到小排序。
6、每次取出得分最大的框,計算其與其它所有預測框的重合程度,重合程度過大的則剔除。
實現代碼如下:
import numpy as np
def non_max_suppression(boxes, num_classes, conf_thres=0.5, nms_thres=0.4):
bs = np.shape(boxes)[0]
# 將框轉換成左上角右下角的形式
shape_boxes = np.zeros_like(boxes[:,:,:4])
shape_boxes[:,:,0] = boxes[:,:,0] - boxes[:,:,2]/2
shape_boxes[:,:,1] = boxes[:,:,1] - boxes[:,:,3]/2
shape_boxes[:,:,2] = boxes[:,:,0] + boxes[:,:,2]/2
shape_boxes[:,:,3] = boxes[:,:,1] + boxes[:,:,3]/2
boxes[:,:,:4] = shape_boxes
output = []
# 1、對所有圖片進行循環。
for i in range(bs):
prediction = boxes[i]
# 2、找出該圖片中得分大於門限函數的框。在進行重合框篩選前就進行得分的篩選可以大幅度減少框的數量。
mask = prediction[:,4] >= conf_thres
prediction = prediction[mask]
if not np.shape(prediction)[0]:
continue
# 3、判斷第2步中獲得的框的種類與得分。
# 取出預測結果中框的位置與之進行堆疊。
# 此時最後一維度裏面的內容由5+num_classes變成了4+1+2,
# 四個參數代表框的位置,一個參數代表預測框是否包含物體,兩個參數分別代表種類的置信度與種類。
class_conf = np.expand_dims(np.max(prediction[:, 5:5 + num_classes], 1),-1)
class_pred = np.expand_dims(np.argmax(prediction[:, 5:5 + num_classes], 1),-1)
detections = np.concatenate((prediction[:, :5], class_conf, class_pred), 1)
unique_class = np.unique(detections[:,-1])
if len(unique_class) == 0:
continue
best_box = []
# 4、對種類進行循環,
# 非極大抑制的作用是篩選出一定區域內屬於同一種類得分最大的框,
# 對種類進行循環可以幫助我們對每一個類分別進行非極大抑制。
for c in unique_class:
cls_mask = detections[:,-1] == c
detection = detections[cls_mask]
scores = detection[:,4]
# 5、根據得分對該種類進行從大到小排序。
arg_sort = np.argsort(scores)[::-1]
detection = detection[arg_sort]
print(detection)
while np.shape(detection)[0]>0:
# 6、每次取出得分最大的框,計算其與其它所有預測框的重合程度,重合程度過大的則剔除。
best_box.append(detection[0])
if len(detection) == 1:
break
ious = iou(best_box[-1],detection[1:])
detection = detection[1:][ious<nms_thres]
output.append(best_box)
return np.array(output)
def iou(b1,b2):
b1_x1, b1_y1, b1_x2, b1_y2 = b1[0], b1[1], b1[2], b1[3]
b2_x1, b2_y1, b2_x2, b2_y2 = b2[:, 0], b2[:, 1], b2[:, 2], b2[:, 3]
inter_rect_x1 = np.maximum(b1_x1, b2_x1)
inter_rect_y1 = np.maximum(b1_y1, b2_y1)
inter_rect_x2 = np.minimum(b1_x2, b2_x2)
inter_rect_y2 = np.minimum(b1_y2, b2_y2)
inter_area = np.maximum(inter_rect_x2 - inter_rect_x1, 0) * \
np.maximum(inter_rect_y2 - inter_rect_y1, 0)
area_b1 = (b1_x2-b1_x1)*(b1_y2-b1_y1)
area_b2 = (b2_x2-b2_x1)*(b2_y2-b2_y1)
iou = inter_area/np.maximum((area_b1+area_b2-inter_area),1e-6)
return iou
2、柔性非極大抑制Soft-NMS的實現過程
柔性非極大抑制和普通的非極大抑制相差不大,只差了幾行代碼。
柔性非極大抑制認爲不應該直接只通過重合程度進行篩選,如圖所示,很明顯圖片中存在兩匹馬,但是此時兩匹馬的重合程度較高,此時我們如果使用普通nms,後面那匹得分比較低的馬會直接被剔除。
Soft-NMS認爲在進行非極大抑制的時候要同時考慮得分和重合程度。
我們直接看NMS和Soft-NMS的代碼差別:
如下爲NMS:
while np.shape(detection)[0]>0:
# 6、每次取出得分最大的框,計算其與其它所有預測框的重合程度,重合程度過大的則剔除。
best_box.append(detection[0])
if len(detection) == 1:
break
ious = iou(best_box[-1],detection[1:])
detection = detection[1:][ious<nms_thres]
如下爲Soft-NMS:
while np.shape(detection)[0]>0:
best_box.append(detection[0])
if len(detection) == 1:
break
ious = iou(best_box[-1],detection[1:])
detection[1:,4] = np.exp(-(ious * ious) / sigma)*detection[1:,4]
detection = detection[1:]
scores = detection[:,4]
arg_sort = np.argsort(scores)[::-1]
detection = detection[arg_sort]
我們可以看到,對於NMS而言,其直接將 與得分最大的框 重合程度較高的其它預測剔除。而Soft-NMS則以一個權重的形式,將獲得的IOU取高斯指數後乘上原得分,之後重新排序。繼續循環。
實現代碼如下:
import numpy as np
def non_max_suppression(boxes, num_classes, conf_thres=0.5, sigma=0.5, nms_thres=0.4):
bs = np.shape(boxes)[0]
# 將框轉換成左上角右下角的形式
shape_boxes = np.zeros_like(boxes[:,:,:4])
shape_boxes[:,:,0] = boxes[:,:,0] - boxes[:,:,2]/2
shape_boxes[:,:,1] = boxes[:,:,1] - boxes[:,:,3]/2
shape_boxes[:,:,2] = boxes[:,:,0] + boxes[:,:,2]/2
shape_boxes[:,:,3] = boxes[:,:,1] + boxes[:,:,3]/2
boxes[:,:,:4] = shape_boxes
output = []
# 1、對所有圖片進行循環。
for i in range(bs):
prediction = boxes[i]
# 2、找出該圖片中得分大於門限函數的框。在進行重合框篩選前就進行得分的篩選可以大幅度減少框的數量。
mask = prediction[:,4] >= conf_thres
prediction = prediction[mask]
if not np.shape(prediction)[0]:
continue
# 3、判斷第2步中獲得的框的種類與得分。
# 取出預測結果中框的位置與之進行堆疊。
# 此時最後一維度裏面的內容由5+num_classes變成了4+1+2,
# 四個參數代表框的位置,一個參數代表預測框是否包含物體,兩個參數分別代表種類的置信度與種類。
class_conf = np.expand_dims(np.max(prediction[:, 5:5 + num_classes], 1),-1)
class_pred = np.expand_dims(np.argmax(prediction[:, 5:5 + num_classes], 1),-1)
detections = np.concatenate((prediction[:, :5], class_conf, class_pred), 1)
unique_class = np.unique(detections[:,-1])
if len(unique_class) == 0:
continue
best_box = []
# 4、對種類進行循環,
# 非極大抑制的作用是篩選出一定區域內屬於同一種類得分最大的框,
# 對種類進行循環可以幫助我們對每一個類分別進行非極大抑制。
for c in unique_class:
cls_mask = detections[:,-1] == c
detection = detections[cls_mask]
scores = detection[:,4]
# 5、根據得分對該種類進行從大到小排序。
arg_sort = np.argsort(scores)[::-1]
detection = detection[arg_sort]
print(detection)
while np.shape(detection)[0]>0:
best_box.append(detection[0])
if len(detection) == 1:
break
ious = iou(best_box[-1],detection[1:])
# 將獲得的IOU取高斯指數後乘上原得分,之後重新排序
detection[1:,4] = np.exp(-(ious * ious) / sigma)*detection[1:,4]
detection = detection[1:]
scores = detection[:,4]
arg_sort = np.argsort(scores)[::-1]
detection = detection[arg_sort]
output.append(best_box)
return np.array(output)
def iou(b1,b2):
b1_x1, b1_y1, b1_x2, b1_y2 = b1[0], b1[1], b1[2], b1[3]
b2_x1, b2_y1, b2_x2, b2_y2 = b2[:, 0], b2[:, 1], b2[:, 2], b2[:, 3]
inter_rect_x1 = np.maximum(b1_x1, b2_x1)
inter_rect_y1 = np.maximum(b1_y1, b2_y1)
inter_rect_x2 = np.minimum(b1_x2, b2_x2)
inter_rect_y2 = np.minimum(b1_y2, b2_y2)
inter_area = np.maximum(inter_rect_x2 - inter_rect_x1, 0) * \
np.maximum(inter_rect_y2 - inter_rect_y1, 0)
area_b1 = (b1_x2-b1_x1)*(b1_y2-b1_y1)
area_b2 = (b2_x2-b2_x1)*(b2_y2-b2_y1)
iou = inter_area/np.maximum((area_b1+area_b2-inter_area),1e-6)
return iou