NMS和soft-nms算法


非極大值抑制算法(nms)

1. 算法原理

  非極大值抑制算法(Non-maximum suppression, NMS)的本質是搜索局部極大值,抑制非極大值元素。

2. 3鄰域情況下NMS的實現

  3鄰域情況下的NMS即判斷一維數組I[W]的元素I[i](2<=i<=W-1)是否大於其左鄰元素I[i-1]和右鄰元素I[i+1],算法流程如下圖所示:

  

  a. 算法流程3-5行判斷當前元素是否大於其左鄰與右鄰元素,如符合條件,該元素即為極大值點。對於極大值點I[i],已知I[i]>I[i+1],故無需對i+1位置元素做進一步處理,直接跳至i+2位置,對應算法流程第12行。

    

  b. 若元素I[i]不滿足算法流程第3行判斷條件,將其右鄰I[i+1]作為極大值候選,對應算法流程第7行。采用單調遞增的方式向右查找,直至找到滿足I[i]>I[i+1]的元素,若i<=W-1,該點即為極大值點,對應算法流程第10-11行。

    

3. NMS在物體檢測中的應用

  物體檢測中應用NMS算法的主要目的是消除多余(交叉重復)的窗口,找到最佳物體檢測位置。

  

  如上圖所示,人臉檢測中,雖然每個窗口均檢測到人臉,但僅需給出一個最有可能表征人臉的窗口

程序整體思路

         先將box中的數據分別存入x1,y1,x2,y2,s中,分別為坐標和置信度,算出每個框的面積,存入area,基於置信度s,從小到達進行排序,做一個while循環,取出置信度最高的,即排序后的最后一個,然后將該框進行保留,存入pick中,然后和其他所有的框進行比對,大於規定閾值就將別的框去掉,並將該置信度最高的框和所有比對過程,大於閾值的框存入suppress,for循環后,將I中滿足suppress條件的置為空。直到I為空退出while。

代碼(C++):

static void sort(int n, const float* x, int* indices)  
{  
// 排序函數(降序排序),排序后進行交換的是indices中的數據  
// n:排序總數// x:帶排序數// indices:初始為0~n-1數目   
  
    int i, j;  
    for (i = 0; i < n; i++)  
        for (j = i + 1; j < n; j++)  
        {  
            if (x[indices[j]] > x[indices[i]])  
            {  
                //float x_tmp = x[i];  
                int index_tmp = indices[i];  
                //x[i] = x[j];  
                indices[i] = indices[j];  
                //x[j] = x_tmp;  
                indices[j] = index_tmp;  
            }  
        }  
}

 

int nonMaximumSuppression(int numBoxes, const CvPoint *points,  
                          const CvPoint *oppositePoints, const float *score,  
                          float overlapThreshold,  
                          int *numBoxesOut, CvPoint **pointsOut,  
                          CvPoint **oppositePointsOut, float **scoreOut)  
{  
  
// numBoxes:窗口數目// points:窗口左上角坐標點// oppositePoints:窗口右下角坐標點  
// score:窗口得分// overlapThreshold:重疊閾值控制// numBoxesOut:輸出窗口數目  
// pointsOut:輸出窗口左上角坐標點// oppositePoints:輸出窗口右下角坐標點  
// scoreOut:輸出窗口得分  
    int i, j, index;  
    float* box_area = (float*)malloc(numBoxes * sizeof(float));    // 定義窗口面積變量並分配空間   
    int* indices = (int*)malloc(numBoxes * sizeof(int));          // 定義窗口索引並分配空間   
    int* is_suppressed = (int*)malloc(numBoxes * sizeof(int));    // 定義是否抑制表標志並分配空間   
    // 初始化indices、is_supperssed、box_area信息   
    for (i = 0; i < numBoxes; i++)  
    {  
        indices[i] = i;  
        is_suppressed[i] = 0;  
        box_area[i] = (float)( (oppositePoints[i].x - points[i].x + 1) *  
                                (oppositePoints[i].y - points[i].y + 1));  
    }  
    // 對輸入窗口按照分數比值進行排序,排序后的編號放在indices中   
    sort(numBoxes, score, indices);  
    for (i = 0; i < numBoxes; i++)                // 循環所有窗口   
    {  
        if (!is_suppressed[indices[i]])           // 判斷窗口是否被抑制   
        {  
            for (j = i + 1; j < numBoxes; j++)    // 循環當前窗口之后的窗口   
            {  
                if (!is_suppressed[indices[j]])   // 判斷窗口是否被抑制   
                {  
                    int x1max = max(points[indices[i]].x, points[indices[j]].x);                     // 求兩個窗口左上角x坐標最大值   
                    int x2min = min(oppositePoints[indices[i]].x, oppositePoints[indices[j]].x);     // 求兩個窗口右下角x坐標最小值   
                    int y1max = max(points[indices[i]].y, points[indices[j]].y);                     // 求兩個窗口左上角y坐標最大值   
                    int y2min = min(oppositePoints[indices[i]].y, oppositePoints[indices[j]].y);     // 求兩個窗口右下角y坐標最小值   
                    int overlapWidth = x2min - x1max + 1;            // 計算兩矩形重疊的寬度   
                    int overlapHeight = y2min - y1max + 1;           // 計算兩矩形重疊的高度   
                    if (overlapWidth > 0 && overlapHeight > 0)  
                    {  
                        float overlapPart = (overlapWidth * overlapHeight) / box_area[indices[j]];    // 計算重疊的比率   
                        if (overlapPart > overlapThreshold)          // 判斷重疊比率是否超過重疊閾值   
                        {  
                            is_suppressed[indices[j]] = 1;           // 將窗口j標記為抑制   
                        }  
                    }  
                }  
            }  
        }  
    }  
  
    *numBoxesOut = 0;    // 初始化輸出窗口數目0   
    for (i = 0; i < numBoxes; i++)  
    {  
        if (!is_suppressed[i]) (*numBoxesOut)++;    // 統計輸出窗口數目   
    }  
  
    *pointsOut = (CvPoint *)malloc((*numBoxesOut) * sizeof(CvPoint));           // 分配輸出窗口左上角坐標空間   
    *oppositePointsOut = (CvPoint *)malloc((*numBoxesOut) * sizeof(CvPoint));   // 分配輸出窗口右下角坐標空間   
    *scoreOut = (float *)malloc((*numBoxesOut) * sizeof(float));                // 分配輸出窗口得分空間   
    index = 0;  
    for (i = 0; i < numBoxes; i++)                  // 遍歷所有輸入窗口   
    {  
        if (!is_suppressed[indices[i]])             // 將未發生抑制的窗口信息保存到輸出信息中   
        {  
            (*pointsOut)[index].x = points[indices[i]].x;  
            (*pointsOut)[index].y = points[indices[i]].y;  
            (*oppositePointsOut)[index].x = oppositePoints[indices[i]].x;  
            (*oppositePointsOut)[index].y = oppositePoints[indices[i]].y;  
            (*scoreOut)[index] = score[indices[i]];  
            index++;  
        }  
  
    }  
  
    free(indices);          // 釋放indices空間   
    free(box_area);         // 釋放box_area空間   
    free(is_suppressed);    // 釋放is_suppressed空間   
  
    return LATENT_SVM_OK;  
}  

軟化非極大值抑制算法(softnms)

參考鏈接:http://blog.csdn.net/app_12062011/article/details/77963494

Motivation

絕大部分目標檢測方法,最后都要用到 NMS-非極大值抑制進行后處理。 通常的做法是將檢測框按得分排序,然后保留得分最高的框,同時刪除與該框重疊面積大於一定比例的其它框。

這種貪心式方法存在如下圖所示的問題: 紅色框和綠色框是當前的檢測結果,二者的得分分別是0.95和0.80。如果按照傳統的NMS進行處理,首先選中得分最高的紅色框,然后綠色框就會因為與之重疊面積過大而被刪掉。

另一方面,NMS的閾值也不太容易確定,設小了會出現下圖的情況(綠色框因為和紅色框重疊面積較大而被刪掉),設置過高又容易增大誤檢。

float

思路:不要粗魯地刪除所有IOU大於閾值的框,而是降低其置信度。

Method

先直接上偽代碼,如下圖:如文章題目而言,就是用一行代碼來替換掉原來的NMS。按照下圖整個處理一遍之后,指定一個置信度閾值,然后最后得分大於該閾值的檢測框得以保留

float

原來的NMS可以描述如下:將IOU大於閾值的窗口的得分全部置為0。

float

文章的改進有兩種形式,一種是線性加權的:

float

一種是高斯加權的:

float

分析上面的兩種改進形式,思想都是:M為當前得分最高框, 為待處理框, 和M的IOU越大, 的得分 就下降的越厲害。

具體地,下面是作者給出的代碼:(當然不止一行T_T)

def cpu_soft_nms(np.ndarray[float, ndim=2] boxes, float sigma=0.5, float Nt=0.3, float threshold=0.001, unsigned int method=0):
    cdef unsigned int N = boxes.shape[0]
    cdef float iw, ih, box_area
    cdef float ua
    cdef int pos = 0
    cdef float maxscore = 0
    cdef int maxpos = 0
    cdef float x1,x2,y1,y2,tx1,tx2,ty1,ty2,ts,area,weight,ov

    for i in range(N):
        maxscore = boxes[i, 4]
        maxpos = i

        tx1 = boxes[i,0]
        ty1 = boxes[i,1]
        tx2 = boxes[i,2]
        ty2 = boxes[i,3]
        ts = boxes[i,4]

        pos = i + 1
    # get max box
        while pos < N:
            if maxscore < boxes[pos, 4]:
                maxscore = boxes[pos, 4]
                maxpos = pos
            pos = pos + 1

    # add max box as a detection 
        boxes[i,0] = boxes[maxpos,0]
        boxes[i,1] = boxes[maxpos,1]
        boxes[i,2] = boxes[maxpos,2]
        boxes[i,3] = boxes[maxpos,3]
        boxes[i,4] = boxes[maxpos,4]

    # swap ith box with position of max box
        boxes[maxpos,0] = tx1
        boxes[maxpos,1] = ty1
        boxes[maxpos,2] = tx2
        boxes[maxpos,3] = ty2
        boxes[maxpos,4] = ts

        tx1 = boxes[i,0]
        ty1 = boxes[i,1]
        tx2 = boxes[i,2]
        ty2 = boxes[i,3]
        ts = boxes[i,4]

        pos = i + 1
    # NMS iterations, note that N changes if detection boxes fall below threshold
        while pos < N:
            x1 = boxes[pos, 0]
            y1 = boxes[pos, 1]
            x2 = boxes[pos, 2]
            y2 = boxes[pos, 3]
            s = boxes[pos, 4]

            area = (x2 - x1 + 1) * (y2 - y1 + 1)
            iw = (min(tx2, x2) - max(tx1, x1) + 1)
            if iw > 0:
                ih = (min(ty2, y2) - max(ty1, y1) + 1)
                if ih > 0:
                    ua = float((tx2 - tx1 + 1) * (ty2 - ty1 + 1) + area - iw * ih)
                    ov = iw * ih / ua #iou between max box and detection box

                    if method == 1: # linear
                        if ov > Nt: 
                            weight = 1 - ov
                        else:
                            weight = 1
                    elif method == 2: # gaussian
                        weight = np.exp(-(ov * ov)/sigma)
                    else: # original NMS
                        if ov > Nt: 
                            weight = 0
                        else:
                            weight = 1

                    boxes[pos, 4] = weight*boxes[pos, 4]

            # if box score falls below threshold, discard the box by swapping with last box
            # update N
                    if boxes[pos, 4] < threshold:
                        boxes[pos,0] = boxes[N-1, 0]
                        boxes[pos,1] = boxes[N-1, 1]
                        boxes[pos,2] = boxes[N-1, 2]
                        boxes[pos,3] = boxes[N-1, 3]
                        boxes[pos,4] = boxes[N-1, 4]
                        N = N - 1
                        pos = pos - 1

            pos = pos + 1

    keep = [i for i in range(N)]
    return keep

 這么做的解釋如下:

如上圖:

假如還檢測出了3號框,而我們的最終目標是檢測出1號和2號框,並且剔除3號框,原始的nms只會檢測出一個1號框並剔除2號框和3號框,而softnms算法可以對1、2、3號檢測狂進行置信度排序,可以知道這三個框的置信度從大到小的順序依次為:1-》2-》3(由於是使用了懲罰,所有可以獲得這種大小關系),如果我們再選擇了合適的置信度閾值,就可以保留1號和2號,同時剔除3號,實現我們的功能。

但是,這里也有一個問題就是置信度的閾值如何選擇,作者在這里依然使用手工設置的值,依然存在很大的局限性,所以該算法依然存在改進的空間。

result:

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM