softnms和softer nms是nms的两个改进算法
传统nms存在的问题
传统的NMS方法是基于分类分数的,只有最高分数的预测框能留下来,但是大多数情况下IoU和分类分数不是强相关,很多分类标签置信度高的框都位置都不是很准
还会有别的物体的框因为和当前物体的框重合部分过多被删掉的情况
Soft-NMS
发现了这些问题,让我们想想如何避免:
首先说第一个问题:物体重叠是由于输出多个框中存在某些其实是另一个物体,但是也不小心被NMS去掉了。这个问题的解法最终是要落在“将某个候选框删掉”这一步骤上,我们需要找到一种方法,更小心的删掉S中的框,而不是暴力的把所有和最高分框删掉,于是有了这个想法:
对于与最高分框overlap大于阈值t的框M,我们不把他直接去掉,而是将他的置信度降低,这样的方法可以使多一些框被保留下来,从而一定程度上避免overlap的情况出现。
那么读者可能会问,如果只是把置信度降低,那么可能原来周围的多个表示同一个物体的框也没办法去掉了。Soft-NMS这样来解决这个问题,同一个物体周围的框有很多,每次选择分数最高的框,抑制其周围的框,与分数最高的框的IoU越大,抑制的程度越大,一般来说,表示同一个物体的框(比如都是前面的马)的IoU是会比另一个物体的框(比如后面的马)的IoU大,因此,这样就会将其他物体的框保留下来,而同一个物体的框被去掉。
接下来就是Soft NMS的完整算法:
红色的部分表示原始NMS算法,绿色部分表示Soft-NMS算法,区别在于,绿色的框只是把si降低了,而不是把bi直接去掉,极端情况下,如果f只返回0,那么等同于普通的NMS。
接下来说一下f函数:f函数是为了降低目标框的置信度,满足条件,如果bi和M的IoU越大,f(iou(M, bi))就应该越小,Soft-NMS提出了两种f函数:
线性函数:
这个比较好理解,当iou条件满足时,si乘上一个1-iou,线性变小
高斯函数惩罚,越接近高斯分布中心,惩罚力度越大
Soft-NMS的效果也比较明显:重叠的物体被更大程度的保留下来,以下是效果图
不过Soft-NMS还是需要调整阈值的,由于没有“去掉”某些框的操作,因此,最后所有框都会被加入D中,只是那些被去掉的框的置信度明显降低,所以需要一个阈值(通常很小)来过滤D中的输出框,从而输出最后的预测框。所以Soft-NMS还是需要一些调参的
我们可以看到,对于NMS而言,其直接将 与得分最大的框 重合程度较高的其它预测剔除。而Soft-NMS则以一个权重的形式,将获得的IOU取高斯指数后乘上原得分,之后重新排序。继续循环。
一直是idxs在变
from torch import Tensor import torch import torchvision def box_area(boxes: Tensor) -> Tensor: return (boxes[:, 2] - boxes[:, 0]) * (boxes[:, 3] - boxes[:, 1]) def box_iou(boxes1: Tensor, boxes2: Tensor) -> Tensor: area1 = box_area(boxes1) # 每个框的面积 (N,) area2 = box_area(boxes2) # (M,) lt = torch.max(boxes1[:, None, :2], boxes2[:, :2]) # [N,M,2] # N中一个和M个比较; 所以由N,M 个 rb = torch.min(boxes1[:, None, 2:], boxes2[:, 2:]) # [N,M,2] wh = (rb - lt).clamp(min=0) # [N,M,2] # 删除面积小于0 不相交的 clamp 钳;夹钳; inter = wh[:, :, 0] * wh[:, :, 1] # [N,M] # 切片的用法 相乘维度减1 iou = inter / (area1[:, None] + area2 - inter) return iou # NxM, boxes1中每个框和boxes2中每个框的IoU值; def soft_nms(boxes: Tensor, scores: Tensor, soft_threshold=0.01, iou_threshold=0.7, weight_method=1, sigma=0.5): """ :param boxes: [N, 4], 此处传进来的框,是经过筛选(选取的得分TopK)之后的 :param scores: [N] :param iou_threshold: 0.7 :param soft_threshold soft nms 过滤掉得分太低的框 (手动设置) :param weight_method 权重方法 1. 线性 2. 高斯 :return: """ keep = [] idxs = scores.argsort() while idxs.numel() > 0: # 循环直到null; numel(): 数组元素个数 # 由于scores得分会改变,所以每次都要重新排序,获取得分最大值 idxs = scores.argsort() # 评分排序 if idxs.size(0) == 1: # 就剩余一个框了; keep.append(idxs[-1]) break keep_len = len(keep) #例如idxs一共4个值,进行一轮之后只看前3个了,再一轮之后只看前2个了.... #后面的那些并不像以前一样直接删掉,因为可能每次乘了一些值之后又往前提了 max_score_index = idxs[-(keep_len + 1)] max_score_box = boxes[max_score_index][None, :] # [1, 4] idxs = idxs[:-(keep_len + 1)] other_boxes = boxes[idxs] # [?, 4] keep.append(max_score_index) # 位置不能边 ious = box_iou(max_score_box, other_boxes) # 一个框和其余框比较 1XM # Soft NMS 处理, 和 得分最大框 IOU大于阈值的框, 进行得分抑制 if weight_method == 1: # 线性抑制 # 整个过程 只修改分数 ge_threshod_idxs = idxs[ious[0] >= iou_threshold] scores[ge_threshod_idxs] *= (1. - ious[0][ious[0] >= iou_threshold]) # 小于IoU阈值的不变 # idxs = idxs[scores[idxs] >= soft_threshold] # 小于soft_threshold删除, 经过抑制后 阈值会越来越小; elif weight_method == 2: # 高斯抑制, 不管大不大于阈值,都计算权重 scores[idxs] *= torch.exp(-(ious[0] * ious[0]) / sigma) # 权重(0, 1] # idxs = idxs[scores[idxs] >= soft_threshold] print(idxs) keep = idxs.new(keep) # Tensor keep = keep[scores[keep] > soft_threshold] # 最后处理阈值 boxes = boxes[keep] # 保留下来的框 scores = scores[keep] # soft nms抑制后得分 return boxes, scores box = torch.tensor([[2,3.1,7,5],[3,4,8,4.8],[4,4,5.6,7],[0.1,0,8,1]]) score = torch.tensor([0.5, 0.3, 0.2, 0.4]) output = soft_nms(boxes=box, scores=score, iou_threshold=0.3) print('IOU of bboxes:') print(output)
softer nms
Soft-NMS只解决了三个问题中的第一个问题,那么剩下两个问题如何解决呢?
我们先分析第三个问题,对于分类置信度和框的IoU不是强相关的问题,我们需要找到一种方法来衡量框的“位置置信度”,对于第二个问题我们也有办法:只需要让多个框加权合并生成最终的框就可以了,softernms提出这两个想法:
- 构建一种IoU的置信度,来建模有多大把握认为当前框和GT是重合的
- 提出一种方法根据IoU置信度加权合并多个框优化最终生成框