睿智的目標檢測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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章