python計算iou以及nms

python計算iou以及nms

iou

iou即交併比,如下圖所示:

python計算iou以及nms_1

就是拿兩個矩形的交集/並集,我們設交集爲inner_area,矩形1面積爲area1,矩形1面積爲area2,則對應iou爲inner_area/(area1+area2-inner_area) ,而兩個矩形的面積很好計算,這裏關鍵是計算兩個矩形的交集,因爲這個交集可能的情況有多種,比如:

python計算iou以及nms_2

具體可以參考這篇文章 https://blog.csdn.net/u014061630/article/details/82818112 ,所以我們如果寫代碼,則要對應很多個if-else,顯然不優美,所以考慮能不能找到一個更加泛化的式子來表示,所以這裏就先用了一個線段來演示:

在這裏插入圖片描述

我們可以看到線段l1和線段l2會有一個交,這個交我們可以用以下式子表示:

inner_x1 = max(l1_x1,l2_x1)
inner_x2 = min(l1_x2,l2_x2)
inner_l = max((inner_x2 - inner_x1),0)

因爲線段比較簡單,所以我們可以分析得到上面的式子能夠將所有情況概括。而矩形是由多個線段組成,所以很自然的將上述公式擴展到矩形上面,我們可以將矩形看作下面的情況:

python計算iou以及nms_4

可以看出我們把矩形變成了兩個線段,而線段的交我們上面是計算過的,所以對於矩形而言,我們可以用下面的式子表示,其中x1,y1,x2,y2代表左上角和右下角的座標

inner_x1 = max(box1_x1,box2_x1)
inner_x2 = min(box1_x2,box2_x2)
w = max((inner_x2 - inner_x1),0)

inner_y1 = max(box1_y1,box2_y1)
inner_y2 = min(box1_y2,box2_y2)
h = max((inner_y2 - inner_y1),0)

上面的inner_x1,inner_y1,inner_x2,inner_y2 代表的是交的左上角和右下角座標,通過上面的式子,我們得到inner_area = w*h,並且涵蓋了所有情況。

對應代碼如下:


def iou(box1, box2):
    '''  
    box:[x1, y1, x2, y2]
    '''
    in_h = min(box1[3], box2[3]) - max(box1[1], box2[1])
    in_w = min(box1[2], box2[2]) - max(box1[0], box2[0])
    inner = 0 if in_h<0 or in_w<0 else in_h*in_w
    union = (box1[2] - box1[0]) * (box1[3] - box1[1]) + \
            (box2[2] - box2[0]) * (box2[3] - box2[1]) - inner
    print(inner)
    iou = inner / union
    return iou
box1 = [0,0,10,10]
box2 = [5,5,15,15]
print(iou(box1,box2))

nms

nms是非極大值抑制(Non-Maximum Suppression, NMS),也就是將那些不是最大值的給抑制下去,比如下面圖片:

python計算iou以及nms_5

ross面部有很多框,但是我們最終只保留得分最大的,對於jack也是一樣,但是目前有一個難點,我們程序接受的是這個圖片裏面的所有框,但是我們並不知道每個框代表是誰,所以這裏就用到iou了,我們將iou的值比較大的就歸於同一個物體,這樣我們只需要計算iou即可。

nms算法的流程如下:

  • 1.我們先將所有候選框的置信度排序,因爲我們最終是要最大的
  • 2.將置信度最大的加入到最終的返回值中
  • 3.將其他的候選框和當前的最大的那個計算iou
  • 4.如果iou大於一個閾值,則可刪除(說明和置信度大的那個是重疊的)
  • 5.將剩下的框重複以上過程

裏面的iou計算上面已經提到過了,這裏因爲使用numpy,所以一次計算多個,所以剩下的就是選框,刪框。

對應的代碼:


import cv2
import numpy as np
# x1 y1 x2 y2 score
boxes=np.array([[100,100,210,210,0.72],
        [250,250,420,420,0.8],
        [220,220,320,330,0.92],
        [100,100,210,210,0.72],
        [230,240,325,330,0.81],
        [220,230,315,340,0.9]]) 
def nms(bboxes,threshold):
    
    # 計算所有候選框面積,爲了iou做準備(因爲numpy可以一次算多個,所以這裏一次算完)
    x1 = bboxes[:, 0]
    y1 = bboxes[:, 1]
    x2 = bboxes[:, 2]
    y2 = bboxes[:, 3]
    score = bboxes[:, 4]
    areas =(x2 - x1 + 1) * (y2 - y1 + 1) # areas.shape [6,]

    # 對置信度進行排序, argsort最終得到的是對應index, 默認從小到大排序,所以最後一個是最大(-1
    order = np.argsort(score)
    keep = [] # 返回值

    while order.size > 0:
        # 將當前置信度最大的框加入返回值列表中 對應的1 2步
        index = order[-1]
        keep.append(index)

        # 對應第3步 計算其他框和當前選定的框的iou,因爲這裏數據類型是np,所以一次是計算的多個
        inner_x1 = np.maximum(x1[index], x1[order[:-1]])
        inner_x2 = np.minimum(x2[index], x2[order[:-1]])

        inner_y1 = np.maximum(y1[index], y1[order[:-1]])
        inner_y2 = np.minimum(y2[index], y2[order[:-1]])
        in_w = np.maximum(0.0, inner_x2 - inner_x1 + 1)
        in_h = np.maximum(0.0, inner_y2 - inner_y1 + 1)
        inner = in_w * in_h

        # 利用相交的面積和兩個框自身的面積計算框的交併比, 將交併比大於閾值的框刪除 對應第4步
        ratio = inner / (areas[index] + areas[order[:-1]] - inner)
        left = np.where(ratio < threshold) # left裏面對應的就是<thr的索引
        order = order[left] # 將所有<thr的索引取出來

    return keep


import matplotlib.pyplot as plt
def plot_bbox(dets, c='k'):
    x1 = dets[:,0]
    y1 = dets[:,1]
    x2 = dets[:,2]
    y2 = dets[:,3]
    
    plt.plot([x1,x2], [y1,y1], c)
    plt.plot([x1,x1], [y1,y2], c)
    plt.plot([x1,x2], [y2,y2], c)
    plt.plot([x2,x2], [y1,y2], c)
    plt.title(" nms")
 
    
plt.figure(1)
ax1 = plt.subplot(1,2,1)
ax2 = plt.subplot(1,2,2)
 
plt.sca(ax1)
plot_bbox(boxes,'k')   # before nms
 
keep = nms(boxes,0.7)
plt.sca(ax2)
plot_bbox(boxes[keep], 'r')# after nm
plt.show()

代碼數據來源於https://blog.csdn.net/a1103688841/article/details/89711120 最後效果如下:

python計算iou以及nms_6

因爲用python可能速度比較慢, 而在目標檢測中這個算法又是比較耗時的,所以一般都是用c直接寫,或者cuda。可以參考https://hellozhaozheng.github.io/z_post/%E8%AE%A1%E7%AE%97%E6%9C%BA%E8%A7%86%E8%A7%89-NMS-Implementation/

#include <iostream>
#include <vector>
#include <algorithm>

struct Bbox {
    int x1;
    int y1;
    int x2;
    int y2;
    float score;
    Bbox(int x1_, int y1_, int x2_, int y2_, float s):
    x1(x1_), y1(y1_), x2(x2_), y2(y2_), score(s) {};
};

float iou(Bbox box1, Bbox box2) {
    float area1 = (box1.x2 - box1.x1 + 1) * (box1.y2 - box1.y1 + 1);
    float area2 = (box2.x2 - box2.x1 + 1) * (box2.y2 - box2.y1 + 1);
    int x11 = std::max(box1.x1, box2.x1);
    int y11 = std::max(box1.y1, box2.y1);
    int x22 = std::min(box1.x2, box2.x2);
    int y22 = std::min(box1.y2, box2.y2);
    float intersection = (x22 - x11 + 1) * (y22 - y11 + 1);
    return intersection / (area1 + area2 - intersection);
}

std::vector<Bbox> nms(std::vector<Bbox> &vecBbox, float threshold) {
    auto cmpScore = [](Bbox box1, Bbox box2) {
    return box1.score < box2.score; // 升序排列, 令score最大的box在vector末端
    };
    std::sort(vecBbox.begin(), vecBbox.end(), cmpScore);

    std::vector<Bbox> pickedBbox;
    while (vecBbox.size() > 0) {
        pickedBbox.emplace_back(vecBbox.back());
        vecBbox.pop_back();
        for (size_t i = 0; i < vecBbox.size(); i++) {
            if (iou(pickedBbox.back(), vecBbox[i]) >= threshold) {
                vecBbox.erase(vecBbox.begin() + i);
            }
        }
    }
    return pickedBbox;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章