NMS存在一个非常简约的实现方法,算法输入包含了所有预测框的 得分、左上点坐标、右下点坐标一共5个预测量,以及一个设定的IoU阈 值。具体流程如下:
(1)按照得分,对所有边框进行降序排列,记录下排列的索引 order,并新建一个列表keep,作为最终筛选后的边框索引结果。
(2)将排序后的第一个边框置为当前边框,并将其保留到keep 中,再求当前边框与剩余所有框的IoU。
(3)在order中,仅仅保留IoU小于设定阈值的索引,重复第(2) 步,直到order中仅仅剩余一个边框,则将其保留到keep中,退出循环, NMS结束。
利用PyTorch,可以很方便地实现NMS模块。具体代码如下:
def nms(self, bboxes, scores, thresh=0.5):
x1 = bboxes[:,0]
y1 = bboxes[:,1]
x2 = bboxes[:,2]
y2 = bboxes[:,3]
# 计算每个box的面积
areas = (x2-x1+1)*(y2-y1+1)
# 对得分进行降序排列,order为降序排列的索引
_, order = scores.sort(0, descending=True)
# keep保留了NMS留下的边框box
keep = []
while order.numel() > 0:
if order.numel() == 1: # 保留框只剩一个
i = order.item()
keep.append(i)
break
else:
i = order[0].item() # 保留scores最大的那个框box[i]
keep.append(i)
# 巧妙利用tensor.clamp函数求取每一个框与当前框的最大值和最小值
xx1 = x1[order[1:]].clamp(min=x1[i])
yy1 = y1[order[1:]].clamp(min=y1[i])
xx2 = x2[order[1:]].clamp(max=x2[i])
yy2 = y2[order[1:]].clamp(max=y2[i])
# 求取每一个框与当前框的重合部分面积
inter = (xx2-xx1).clamp(min=0) * (yy2-yy1).clamp(min=0)
# 计算每一个框与当前框的IoU
iou = inter / (areas[i]+areas[order[1:]]-inter)
# 保留IoU小于阈值的边框索引
idx = (iou <= threshold).nonzero().squeeze()
if idx.numel() == 0:
break
# 这里的+1是为了补充idx与order之间的索引差
order = order[idx+1]
# 返回保留下的所有边框的索引值,类型为torch.LongTensor
return torch.LongTensor(keep)