NMS(Non-Maximum Suppression,非极大值抑制)解析
非极大值抑制(NMS,Non-Maximum Suppression),在计算机视觉任务中得到了广泛的应用,例如边缘检测、人脸检测、目标检测(DPM,YOLO,SSD,Faster R-CNN)等。
非极大值抑制,简称为NMS算法,英文为Non-Maximum Suppression。其思想是搜素局部最大值,抑制极大值。NMS算法在不同应用中的具体实现不太一样,但思想是一样的。非极大值抑制,在计算机视觉任务中得到了广泛的应用,例如边缘检测、人脸检测、目标检测(DPM,YOLO,SSD,Faster R-CNN)等。
使用非极大抑制的原因在于:
以目标检测为例,目标检测的过程中在同一目标的位置上会产生大量的候选框,这些候选框相互之间可能会有重叠,此时我们需要利用非极大值抑制找到最佳的目标边界框,消除冗余的边界框。
上图中,左边是人脸检测的候选框结果,每个边界框有一个置信度得分(confidence score),如果不使用非极大值抑制,就会有多个候选框出现。右图是使用非极大值抑制之后的结果,符合我们人脸检测的预期结果。
算法流程:
✔️ 给出一张图片和上面许多物体检测的候选框(即每个框可能都代表某种物体),但是这些框很可能有互相重叠的部分,我们要做的就是只保留最优的框。假设有N个框,每个框被分类器计算得到的分数为 Si, 1 ⩽ i ⩽ N。
- 建造一个存放待处理候选框的集合H,初始化为包含全部N个框;建造一个存放最优框的集合M,初始化为空集。
- 将所有集合 H 中的框进行排序,选出分数最高的框 m,从集合 H 移到集合 M;
- 遍历集合 H 中的框,分别与框 m 计算交并比(Interection-over-union,IoU),如果高于某个阈值(一般为0~0.5),则认为此框与 m 重叠,将此框从集合 H 中去除。
- 回到第2步进行迭代,直到集合 H 为空。集合 M 中的框为我们所需。
<Eg> :以人脸检测为例:
已经识别出了5个候选框,但是我们只需要最后保留两个人脸。
① 首先选出分数最大的框(0.98),然后遍历剩余框,计算 IoU,会发现露丝脸上的两个绿框都和 0.98 的框重叠率很大,都要去除。
② 然后只剩下杰克脸上两个框,选出最大框(0.81),然后遍历剩余框(只剩下0.67这一个了),发现0.67这个框与 0.81 的 IoU 也很大,去除。
至此所有框处理完毕,算法结果:
代码:
✔️ NMS算法一般是为了去掉模型预测后的多余框,其一般设有一个nms_threshold=0.5,具体的实现思路如下:
- 选取这类box中scores最大的哪一个,它的index记为 i ,并保留它;
- 计算
boxes[i]
与其余的boxes
的IOU
值; - 如果其
IOU>0.5
了,那么就舍弃这个box(由于可能这两个box表示同一目标,所以保留分数高的哪一个); - 从最后剩余的boxes中,再找出最大scores的哪一个,如此循环往复。
def nms(boxes, scores, threshold=0.5, top_k=200):
'''
Args:
boxes: 预测出的box, shape[M,4]
scores: 预测出的置信度,shape[M]
threshold: 阈值
top_k: 要考虑的box的最大个数
Return:
keep: nms筛选后的box的新的index数组
count: 保留下来box的个数
'''
keep = scores.new(scores.size(0)).zero_().long()
x1 = boxes[:, 0]
y1 = boxes[:, 1]
x2 = boxes[:, 2]
y2 = boxes[:, 3]
area = (x2-x1)*(y2-y1) # 面积,shape[M]
_, idx = scores.sort(0, descending=True) # 降序排列scores的值大小
# 取前top_k个进行nms
idx = idx[:top_k]
count = 0
while idx.numel():
# 记录最大score值的index
i = idx[0]
# 保存到keep中
keep[count] = i
# keep 的序号
count += 1
if idx.size(0) == 1: # 保留框只剩一个
break
idx = idx[1:] # 移除已经保存的index
# 计算boxes[i]和其他boxes之间的iou
xx1 = x1[idx].clamp(min=x1[i])
yy1 = y1[idx].clamp(min=y1[i])
xx2 = x2[idx].clamp(max=x2[i])
yy2 = y2[idx].clamp(max=y2[i])
w = (xx2 - xx1).clamp(min=0)
h = (yy2 - yy1).clamp(min=0)
# 交集的面积
inter = w * h # shape[M-1]
iou = inter / (area[i] + area[idx] - inter)
# iou满足条件的idx
idx = idx[iou.le(threshold)] # Shape[M-1]
return keep, count
其中:
- torch.numel(): 表示一个张量总元素的个数
- torch.clamp(min, max): 设置上下限
- tensor.le(x): 返回tensor<=x的判断
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)