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([])