NMS的英文是Non-maximum suppression的缩写。python
简单的说,就是模型给出了多个重叠在一块儿的候选框,咱们只须要保留一个就能够了。其余的重叠的候选框就删掉了,效果可见下图:
git
IoU的英文全称Intersection over Union,就是两个候选框区域的交集面积比上并集的面积,用下图能够理解:
github
hard-nms其实就是经典版本的NMS的方法。就是根据模型给出每一个box的置信度从大到小进行排序,而后保留最大的,删除因此与这个最大置信度的候选框的IoU大于阈值的其余候选框。数组
举个例子吧,如今有4个候选框:
(box1,0.8),(box2,0.9),
(box3,0.7),(box4,0.5)app
咱们把这四个候选框按照置信度从大到小排序:
box2>box1>box3>box4less
如今咱们保留置信度最大的候选框box2,而后计算剩下三个box与box2之间的IoU,若是IoU大于一个事先设置的阈值,那么就删除这个box。假设,阈值是0.5:
IoU(box1,box2)=0.1<0.5,保留;IoU(box3,box2)=0.7<0.5,删除;IoU(box4,box2)=0.2<0.5,保留;ide
如今还有box1和box4,而后再重复上面的过程,排序,而后删除。spa
下面是python实现的hard-NMS:3d
def hard_nms(box_scores, iou_threshold, top_k=-1, candidate_size=200): """ Args: box_scores (N, 5): box的集合,N为框的数量,5即4(位置信息)+1(可能为物体的几率) iou_threshold: 咱们用IOU标准去除多余检测框的阈值 top_k: 保留多少个计算后留下来的候选框,若是为-1则全保留 candidate_size: 参与计算的boxes数量 Returns: picked: 通过nms计算后保留下来的box """ scores = box_scores[:, -1] # 首先咱们取出box中的最后一个元素也就是当前box检测到物体的几率 boxes = box_scores[:, :-1] # 取出box中的四个坐标(左上、右下) picked = [] _, indexes = scores.sort(descending=True) # 按照降序排列全部的物体的几率,获得排序后在原数组中的索引信息 indexes indexes = indexes[:candidate_size] # 只保留前 candidate_size 个 boxes 其他的不考虑了 while len(indexes) > 0: current = indexes[0] # 每次取出当前在 indexes 中 检测到物体几率最大的一个 picked.append(current.item()) # 将这个最大的存在结果中 if 0 < top_k == len(picked) or len(indexes) == 1: break current_box = boxes[current, :] # 当前第一个也就是最高几率的box indexes = indexes[1:] rest_boxes = boxes[indexes, :] # 剩下其他的box iou = iou_of( # 将当前的box与剩下其他的boxes用IOU标准进行筛选 rest_boxes, current_box.unsqueeze(0), ) indexes = indexes[iou <= iou_threshold]# 保留与当前box的IOU小于必定阈值的boxes, return box_scores[picked, :]
如何计算iou的面积呢?实现方法在下面:rest
def area_of(left_top, right_bottom) -> torch.Tensor: """Compute the areas of rectangles given two corners. Args: left_top (N, 2): left top corner. right_bottom (N, 2): right bottom corner. Returns: area (N): return the area. """ hw = torch.clamp(right_bottom - left_top, min=0.0) return hw[..., 0] * hw[..., 1] def iou_of(boxes0, boxes1, eps=1e-5): """Return intersection-over-union (Jaccard index) of boxes. Args: boxes0 (N, 4): ground truth boxes. boxes1 (N or 1, 4): predicted boxes. eps: a small number to avoid 0 as denominator. Returns: iou (N): IoU values. """ overlap_left_top = torch.max(boxes0[..., :2], boxes1[..., :2]) overlap_right_bottom = torch.min(boxes0[..., 2:], boxes1[..., 2:]) overlap_area = area_of(overlap_left_top, overlap_right_bottom) area0 = area_of(boxes0[..., :2], boxes0[..., 2:]) area1 = area_of(boxes1[..., :2], boxes1[..., 2:]) return overlap_area / (area0 + area1 - overlap_area + eps)
在密集目标检测任务中,hard-NMS会有一些问题,看下面的例子:
两个物体重叠起来了,可是根据hard-NMS绿色的框会被掉。
Soft-NMS就改动了一个地方。 在判断最高的置信度的box和其余box的IoU的时候增长了一个系数,能够更好的选择哪些才是多余的box。
对于hard-NMS来讲,\(iou(M,b_i)<N_t\)的时候,保留,大于等于的时候删除,\(s\)表示置信度:
对于soft-NMS来讲,\(iou(M,b_i)<N_t\)的时候,保留,大于的时候削减:
能够看出来,hard-NMS对于IoU大于阈值的候选框,直接把其置信度变成0,这样就至关于删除了这个box;可是soft-NMS的会根据IoU的大小,去适当的削减置信度,从而留下一些余地。
【如何削减】
这里有两种方法来下降重叠候选框的置信度:
第二种方法更为常见。
下面是python来实现的soft-NMS,其实跟hard-NMS相比,就多了一行代码罢了:
def soft_nms(box_scores, score_threshold, sigma=0.5, top_k=-1): """Soft NMS implementation. References: https://arxiv.org/abs/1704.04503 https://github.com/facebookresearch/Detectron/blob/master/detectron/utils/cython_nms.pyx Args: box_scores (N, 5): boxes in corner-form and probabilities. score_threshold: boxes with scores less than value are not considered. sigma: the parameter in score re-computation. scores[i] = scores[i] * exp(-(iou_i)^2 / simga) top_k: keep top_k results. If k <= 0, keep all the results. Returns: picked_box_scores (K, 5): results of NMS. """ picked_box_scores = [] while box_scores.size(0) > 0: max_score_index = torch.argmax(box_scores[:, 4]) cur_box_prob = torch.tensor(box_scores[max_score_index, :]) picked_box_scores.append(cur_box_prob) if len(picked_box_scores) == top_k > 0 or box_scores.size(0) == 1: break cur_box = cur_box_prob[:-1] box_scores[max_score_index, :] = box_scores[-1, :] box_scores = box_scores[:-1, :] ious = iou_of(cur_box.unsqueeze(0), box_scores[:, :-1]) # 如下这句是新加的,若是没有这句就是Hard-NMS了 box_scores[:, -1] = box_scores[:, -1] * torch.exp(-(ious * ious) / sigma) box_scores = box_scores[box_scores[:, -1] > score_threshold, :] if len(picked_box_scores) > 0: return torch.stack(picked_box_scores) else: return torch.tensor([])