python計算iou以及nms
iou
iou即交併比,如下圖所示:
就是拿兩個矩形的交集/並集,我們設交集爲inner_area
,矩形1面積爲area1
,矩形1面積爲area2
,則對應iou爲inner_area/(area1+area2-inner_area)
,而兩個矩形的面積很好計算,這裏關鍵是計算兩個矩形的交集,因爲這個交集可能的情況有多種,比如:
具體可以參考這篇文章 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)
因爲線段比較簡單,所以我們可以分析得到上面的式子能夠將所有情況概括。而矩形是由多個線段組成,所以很自然的將上述公式擴展到矩形上面,我們可以將矩形看作下面的情況:
可以看出我們把矩形變成了兩個線段,而線段的交我們上面是計算過的,所以對於矩形而言,我們可以用下面的式子表示,其中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),也就是將那些不是最大值的給抑制下去,比如下面圖片:
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可能速度比較慢, 而在目標檢測中這個算法又是比較耗時的,所以一般都是用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;
}