非極大值抑制(NMS)算法詳解

NMS(non maximum suppression)即非極大值抑制,廣泛應用於傳統的特徵提取和深度學習的目標檢測算法中。
NMS原理是通過篩選出局部極大值得到最優解。
在2維邊緣提取中體現在提取邊緣輪廓後將一些梯度方向變化率較小的點篩選掉,避免造成干擾。
在三維關鍵點檢測中也起到重要作用,篩選掉特徵中非局部極值。
在目標檢測方面,無論是One-stage的SSD系列算法、YOLO系列算法還是Two-stage的基於RCNN系列的算法,非極大值抑制都是其中必不可少的一個組件,可以將較小分數的輸出框過濾掉,同樣,在三維基於點雲的目標檢測模型中亦有使用。

在現有的基於anchor的目標檢測算法中,都會產生數量巨大的候選矩形框,這些矩形框有很多是指向同一目標,因此就存在大量冗餘的候選矩形框。非極大值抑制算法的目的正在於此,它可以消除多餘的框,找到最佳的物體檢測位置。
image

IoU(Intersection over Union) :定位精度評價公式。
相當於兩個區域交叉的部分除以兩個區域的並集部分得出的結果。
image
IoU各個取值時的情況展示,一般來說,這個 Score > 0.5 就可以被認爲一個不錯的結果了。
image

IOU計算:

image

如何計算IoU(交併比)
image

選取兩個矩形框左頂角的橫,縱座標的最大值,x21,y21;選取兩個矩形框右下邊角的橫縱座標的最小值,x12,y12;

  • 交集面積計算:

\[{Area(A \cap B)} = |x12 - x21| * |y12 - y21| \]

  • 並集面積計算:

\[{Area(A \cup B)} = |x11 - x12| * |y11 - y12| + |x21 - x22| * |y21 - y22| - {Area(A \cap B)} \]

  • 計算IOU公式

\[IoU = \frac {Area(A \cap B)} {Area(A \cup B)} \]

算法流程如下:

  • 將所有框的得分排序,選中最高分及其對應的框
  • 遍歷其餘的框,如果和當前最高分框的重疊面積(IOU)大於一定閾值(常用的值爲0.5左右),我們就將框刪除。(爲什麼要刪除,是因爲超過設定閾值,認爲兩個框的裏面的物體屬於同一個類別,比如都屬於狗這個類別。我們只需要留下一個類別的可能性框圖即可。)
  • 從未處理的框中繼續選一個得分最高的,重複上述過程。
    image
    image
    image

代碼如下:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
NMS function(Non-Maximum Suppression,  抑制不是極大值的元素)
        psedocode:
            1. choose the highest score element  a_1  in set B, add a_1 to the keep set C
            2. compute the IOU between the chosen element(such as a_1) and others elements in set B
            3. only keep the nums  at set B whose IOU value is less than thresholds (can be set as >=0.5), delete the nums similiar
                to a_1(the higher IOU it is , the more interseciton between a_1 and it will have)
            4. choose the highest score value a_2 left at set B  and add a_2 to set C
            5. repeat the 2-4 until  there is nothing in set B, while set C is the NMS value set

"""
import numpy as np

# boxes表示人臉框的xywh4點座標+相關置信度
boxes = np.array([[100, 100, 210, 210, 0.72],
                  [250, 250, 420, 420, 0.8],
                  [220, 220, 320, 330, 0.92],
                  [230, 240, 325, 330, 0.81],
                  [220, 230, 315, 340, 0.9]])


def py_cpu_nms(dets, thresh):
    # dets:(m,5)  thresh:scaler

    x1 = dets[:, 0]  # [100. 250. 220. 230. 220.]
    y1 = dets[:, 1]  # [100. 250. 220. 240. 230.]
    x2 = dets[:, 2]  # [210. 420. 320. 325. 315.]
    y2 = dets[:, 3]  # [210. 420. 330. 330. 340.]

    areas = (y2 - y1 + 1) * (x2 - x1 + 1)
    scores = dets[:, 4]  # [0 1 3 4 2]
    keep = []
    # index表示按照scores從高到底的相關box的序列號
    index = scores.argsort()[::-1]  # [2 4 3 1 0]

    while index.size > 0:
        print("sorted index of boxes according to scores", index)
        # 選擇得分最高的score直接加入keep列表中
        i = index[0]
        keep.append(i)
        # 計算score最高的box和其他box分別的相關交集座標
        x11 = np.maximum(x1[i], x1[index[1:]])  # [220. 230. 250. 220.]  最高的被提走了,所以要從1開始取後 4位
        y11 = np.maximum(y1[i], y1[index[1:]])  # [230. 240. 250. 220.]
        x22 = np.minimum(x2[i], x2[index[1:]])  # [315. 320. 320. 210.]
        y22 = np.minimum(y2[i], y2[index[1:]])  # [330. 330. 330. 210.]

        print("x1 values by original order:", x1)
        print("x1 value by scores:", x1[index[:]])  #  [220. 220. 230. 250. 100.]
        print("x11 value means  replacing the less value compared" \
              " with the value by the largest score :", x11)
        # 計算交集面積
        w = np.maximum(0, x22 - x11 + 1)  # the weights of overlap
        h = np.maximum(0, y22 - y11 + 1)  # the height of overlap
        overlaps = w * h
        # 計算相關IOU值(交集面積/並集面積,表示邊框重合程度,越大表示越相似,越該刪除)
        # 重疊面積 /(面積1+面積2-重疊面積)
        ious = overlaps / (areas[i] + areas[index[1:]] - overlaps)
        # 只保留iou小於閾值的索引號,重複上步
        idx = np.where(ious <= thresh)[0]
        # 因爲第一步index[0]已經被划走,所以需要原來的索引號需要多加一
        index = index[idx + 1]

    return keep


import matplotlib.pyplot as plt


def plot_bbox(ax, dets, c='b', title_name="title"):
    x1 = dets[:, 0]
    y1 = dets[:, 1]
    x2 = dets[:, 2]
    y2 = dets[:, 3]

    ax.plot([x1, x2], [y1, y1], c)
    ax.plot([x1, x1], [y1, y2], c)
    ax.plot([x1, x2], [y2, y2], c)
    ax.plot([x2, x2], [y1, y2], c)
    ax.set_title(title_name)


if __name__ == '__main__':
    # 1.創建畫板fig
    fig = plt.figure(figsize=(12, 6))

    # 參數解釋,前兩個參數 1,2 表示創建了一個一行兩列的框 第三個參數表示當前所在的框
    ax1 = fig.add_subplot(1, 2, 1)
    ax2 = fig.add_subplot(1, 2, 2)

    plot_bbox(ax1, boxes, 'k', title_name="before nms")  # before nms

    keep = py_cpu_nms(boxes, thresh=0.7)

    plot_bbox(ax2, boxes[keep], 'r', title_name="after nms")  # after nms
    plt.show()

image

參考文獻:
https://blog.csdn.net/weixin_42237113/article/details/105743296
https://blog.csdn.net/lz867422770/article/details/100019587

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