1、计算IOU
计算两个bbox的IOU
import numpy as np
def ComputeIOU(boxA, boxB):
## 计算相交框的坐标
x1 = np.max([boxA[0], boxB[0]])
x2 = np.min([boxA[2], boxB[2]])
y1 = np.max([boxA[1], boxB[1]])
y2 = np.min([boxA[3], boxB[3]])
w = np.max(x2-x1+1, 0)
h = np.max(y2-y1+1, 0)
area = w*h
iou = area/((boxA[2]-boxA[0]+1)*(boxA[3]-boxA[1]+1) + (boxB[2]-boxB[0]+1)*(boxB[3]-boxB[1]+1) - area)
return iou
boxA = [1,1,3,3]
boxB = [2,2,4,4]
IOU = ComputeIOU(boxA, boxB)
计算两组bbox的IOU,可以用矩阵运算加速两个for循环
import numpy as np
def iou_batch(bb_test, bb_gt):
"""
From SORT: Computes IOU between two bboxes in the form [x1,y1,x2,y2]
"""
bb_gt = np.expand_dims(bb_gt, 0) #
bb_test = np.expand_dims(bb_test, 1)
# np.max()仅返回一个最大值,np.maximu()返回每个维度的最大值
# 比如[[ \ 12 32]
# [10 12 32]
# [30 30 32]]
# [[12 32]] [[10][30]] np.maximu()返回
# [[12. 32.]
# [30. 32.]]
xx1 = np.maximum(bb_test[..., 0], bb_gt[..., 0])
yy1 = np.maximum(bb_test[..., 1], bb_gt[..., 1])
xx2 = np.minimum(bb_test[..., 2], bb_gt[..., 2])
yy2 = np.minimum(bb_test[..., 3], bb_gt[..., 3])
w = np.maximum(0., xx2 - xx1)
h = np.maximum(0., yy2 - yy1)
wh = w * h
o = wh / ((bb_test[..., 2] - bb_test[..., 0]) * (bb_test[..., 3] - bb_test[..., 1])
+ (bb_gt[..., 2] - bb_gt[..., 0]) * (bb_gt[..., 3] - bb_gt[..., 1]) - wh)
return o
detections = np.array([[10,10,20,30,0.95],[30,15,40,40,0.95]])
trackers = np.array([[12,12,22,32,1],[32,15,42,42,1]])
iou_matrix = iou_batch(detections, trackers)
print(type(iou_matrix))
2、NMS
Non-Maximum Suppression(NMS)非极大值抑制。从字面意思理解,抑制那些非极大值的元素,保留极大值元素。其主要用于目标检测,目标跟踪,3D重建,数据挖掘等。
nms
算法核心
根据置信度(从大到小)对bbox排序,每次取置信度最大的bbox,计算与其他bbox的IOU,剔除大于阈值的bbox。重复上述操作,直到没有重叠的bbox。
代码实例
def hard_nms(preds, iou_thresh=0.7, score_th=None, condidates_num=200):
"""
Params:
preds(numpy.array): detection preds before nms, with shape(N, 4)
iou_thresh(float): iou thershold
score_th: detection thershold (optional)
Return:
keeps(nump.array): keeped anchor indexes
"""
# if no bbox in preds
if preds.size==0:
return None
# sort by scores
bboxes = preds[np.argsort(preds[:,4])]
if score_th:
mask = bboxes[:,4]>=score_th
bboxes = bboxes[mask]
# print(bboxes)
keeps = []
while len(bboxes) > 0:
current = bboxes[-1]
keeps.append(current)
# if keeped num equal with condidates_num or only one anchor left
if len(bboxes) == 1 or len(keeps) == condidates_num:
break
bboxes = bboxes[:-1]
ious = iou_batch(current, bboxes).flatten()
mask = ious <= iou_thresh
# print(mask)
bboxes = bboxes[mask]
return np.array(keeps)
利用上一节的iou_batch计算IOU,但是每次计算时面积都会重复计算,因此可以先把所有bbox的面积求出来,直接用。
def nms(preds, iou_thresh=0.5, score_th=None):
if preds.size==0:
return None
bboxes = np.array(preds)
# 根据置信度阈值进行初步筛选
if score_th:
mask = np.where(bboxes[:,4]>=score_th)
bboxes = bboxes[mask]
# 先记录
x1 = bboxes[:, 0]
y1 = bboxes[:, 1]
x2 = bboxes[:, 2]
y2 = bboxes[:, 3]
score = bboxes[:, 4]
area = (x2-x1+1) * (y2-y1+1)
# 用idxs来记录下标,后期只需要维护idxs就可以
idxs = np.argsort(score)
res = []
while idxs.size>0:
cur = idxs[-1]
res.append(bboxes[cur])
if idxs.size==1:
break
# 计算iou
xx1 = np.maximum(x1[cur], x1[idxs[:-1]])
yy1 = np.maximum(y1[cur], y1[idxs[:-1]])
xx2 = np.minimum(x2[cur], x2[idxs[:-1]])
yy2 = np.minimum(y2[cur], y2[idxs[:-1]])
w = np.maximum(xx2-xx1+1, 0.)
h = np.maximum(yy2-yy1+1, 0.)
iner = w * h
outer = area[cur] + area[idxs[:-1]] - iner
ious = iner / outer
# 根据iou筛选bbox
mask = np.where(ious<=iou_thresh)
idxs = idxs[mask]
# 另一种写法
# mask = ious <= iou_thresh
# idxs = idxs[:-1][mask]
'''
两种写法的区别在于np.where()直接生成满足条件的下标,
而ious <= iou_thresh是生成长度为原数组的Boolean数组,因为求IOU的时候没求自己和自己的,
所以IOU数把组长度少了一个,所以要先把idxs长度-1
'''
return np.array(res)
dets = np.array([[187, 82, 337, 317, 0.9], [150, 67, 305, 282, 0.75], [246, 121, 368, 304, 0.8], [1,1,200,300, 0.2]])
dets_nms = nms(dets, 0.5, 0.5)
print(dets_nms)
soft-nms
当遇到密集场景,不同物体间重叠度较大时,nms根据iou剔除置信度较小的相邻物体,导致漏检。而提高iou阈值,可能会导致误检。
因此有学者提出了soft-nms,如图所示。
算法核心
对于iou超过阈值的目标,没有直接暴力删除,而是降低其置信度,重叠度越高,则置信度越低。
- 通常有两种方法,一种是线性衰减:
s i = { s i I o u ( M , b i ) < N t s i ( 1 − I o u ( M , b i ) ) I o u ( M , b i ) ≥ N t s_i= \begin{cases} s_i& \text{ $ Iou(M,b_i)<N_t $ } \\ s_i(1-Iou(M,b_i))& \text{ $ Iou(M,b_i)\ge N_t$ } \end{cases} si={ sisi(1−Iou(M,bi)) Iou(M,bi)<Nt Iou(M,bi)≥Nt
它是一个跳跃性变化的函数(小于阈值,score不变,大于阈值,score乘上一个小于1的系数,相当于在 N t N_t Nt的位置发生了突变),作者认为该惩罚函数应该是连续的,否则会导致anchor排序的突变。
- 另一种是高斯平滑衰减:
s i = s i e − i o u ( M , b i ) 2 σ , ∀ b i ∉ D s_i=s_ie^{-\frac{iou(M,b_i)^2}{\sigma}},\forall b_i \notin D si=sie−σiou(M,bi)2,∀bi∈/D
对于靠近M的anchor的score给予更大的惩罚,即乘上一个很小的系数,对于远离M的anchor的分值,给予小的惩罚,iou为0,则惩罚为0。
代码示例
以下代码是自己写的,和官方实现方式不同,不知道是否所有情况都能使用。
def soft_nms(preds, score_th=0.25, sigma=0.5):
'''
:param preds: detections:List of[x1,y1,x2,y2,score]
:param score_th: 置信度阈值
:param sigma: 高斯方差,一般默认0.5
:return:
'''
if len(preds)==0:
return None
bboxes = np.array(preds)
if score_th:
mask = np.where(bboxes[:,4]>score_th)
bboxes = bboxes[mask]
x1 = bboxes[:, 0]
y1 = bboxes[:, 1]
x2 = bboxes[:, 2]
y2 = bboxes[:, 3]
area = (x2-x1+1) * (y2-y1+1)
# 用now记录和筛选下次要比较的bbox
# 如果已经作为最大值的bbox比较过了,或者置信度小于阈值,则置为False
now = [True for _ in range(len(bboxes))]
res = []
while 1:
# 因为每次操作会修改置信度,因此需要重新排序
idxs = np.argsort(bboxes[:, 4])
# 对于已经作为最大值的bbox比较过了,或者置信度小于阈值的bbox,剔除掉
# print(now)
# print(idxs)
mask = np.array([True if now[idx] else False for idx in idxs])
# print(mask)
idxs = idxs[mask]
# print(idxs)
if len(idxs)==1:
cur = idxs[-1]
res.append(bboxes[cur])
break
if len(idxs)==0:
break
cur = idxs[-1]
now[cur] = False
res.append(bboxes[cur])
# 计算iou
xx1 = np.maximum(x1[cur], x1[idxs[:-1]])
yy1 = np.maximum(y1[cur], y1[idxs[:-1]])
xx2 = np.minimum(x2[cur], x2[idxs[:-1]])
yy2 = np.minimum(y2[cur], y2[idxs[:-1]])
w = np.maximum(xx2-xx1+1, 0.)
h = np.maximum(yy2-yy1+1, 0.)
iner = w * h
outer = area[cur] + area[idxs[:-1]] - iner
ious = iner / outer
# 计算iou是按照置信度排的序,所以惩罚置信度的时候也要按照这个顺序
bboxes[idxs[:-1],4] *= np.exp(-(ious*ious)/sigma)
# 剔除小于阈值的bbox,这里对所有bbox进行筛选,是为了和now的长度对应
mask = bboxes[:,4]>score_th
now = [mask[i] and now[i] for i in range(len(mask))]
return np.array(res)
dets = np.array([[187, 82, 337, 317, 0.9], [150, 67, 305, 282, 0.75], [246, 121, 368, 304, 0.8], [1,1,200,300, 0.2]])
dets_nms = soft_nms(dets, 0.5)
print(dets_nms)
'''SOFT_NMS
[[187. 82. 337. 317. 0.9 ]
[246. 121. 368. 304. 0.57206931]]
'''
'''NMS
[[187. 82. 337. 317. 0.9]
[246. 121. 368. 304. 0.8]]
'''
3、K-means聚类anchors
参考 YOLOV3中k-means聚类获得anchor boxes过程详解
import numpy as np
def iou_batch(boxs,clusters):
boxs = np.expand_dims(boxs,1)
clusters = np.expand_dims(clusters,0)
x = np.minimum(boxs[...,0], clusters[..., 0])
y = np.minimum(boxs[...,1], clusters[..., 1])
iner = x * y
outer = boxs[...,0] * boxs[...,1] + clusters[..., 0] * clusters[..., 1] - iner
o = iner / outer
return o
def avg_iou(box, cluster):
return np.mean([np.max(cas_iou(box[i], cluster)) for i in range(box.shape[0])])
def kmeans(box, k):
# 取出一共有多少框
row = box.shape[0]
# 每个框各个点的位置
distance = np.empty((row, k))
# 最后的聚类位置
last_clu = np.zeros((row,))
np.random.seed()
# 随机选5个当聚类中心
cluster = box[np.random.choice(row, k, replace=False)]
# cluster = random.sample(row, k)
while True:
# 计算每一行距离五个点的iou情况。
# for i in range(row):
# distance[i] = 1 - cas_iou(box[i], cluster)
distance = 1 - iou_batch(box,cluster)
# print(distance.shape)
# 取出最小点
label = np.argmin(distance, axis=1) # 记录每个bbox属于哪一类anchor
# 如果分类结果与上一步相同,则终止
if (last_clu == label).all():
break
# 求每一个类的中位点
for j in range(k):
cluster[j] = np.median(
box[label == j], axis=0)
# print(cluster)
last_clu = label
return cluster
if __name__ == '__main__':
data = []
num = 100
SIZE = 446
np.random.seed(123)
for _ in range(num):
bbox = np.random.uniform(20,100,2).astype(dtype=int)
data.append(bbox)
data = np.array(data)
# print(data)
# 使用k聚类算法
out = kmeans(data, 9)
# 从小到大排序
out = out[np.argsort(out[:, 0])]
print('acc:{:.2f}%'.format(avg_iou(data, out) * 100))
print(out)
4、手撕快排
sort是快排实现,时间复杂度为O(nlogn),最坏情况是O(n^2),
以下是手写快排,采用递归的思想,每次取数组中间的数作为基准,然后把小于基准的放到左边,大于基准的放在右边,递归执行,有点给定前序和中序遍历,求后序遍历的感觉。
def quick_sort(nums):
n = len(nums)
if n<2:
return nums
mid = n//2
left = []
right = []
for i, num in enumerate(nums):
if i == mid: continue
if num <= nums[mid]:
left.append(num)
else:
right.append(num)
left = quick_sort(left)
right = quick_sort(right)
return left + [nums[mid]] + right
import random
k = 10
nums = [0] * k
for i in range(k):
nums[i] = random.randint(1,100)
print(nums)
print(quick_sort(nums))