学习资源站

15-更换NMS(非极大抑制)之 DIoU-NMS、CIoU-NMS、EIoU-NMS、GIoU-NMS 、SIoU-NMS、Soft-NMS_yolov改进nms

YOLOv5改进系列(14)——更换NMS(非极大抑制)之 DIoU-NMS、CIoU-NMS、EIoU-NMS、GIoU-NMS 、SIoU-NMS、Soft-NMS


☀️一、NMS(非极大抑制)简介

1.1 什么是NMS?

NMS(non maximum suppression)即非极大抑制,顾名思义就是抑制不是极大值的元素,搜索局部的极大值。在最近几年常见的物体检测算法(包括RCNN、SPPNet、Fast-RCNN、Faster-RCNN等)中,最终都会从一张图片中找出很多个可能是物体的矩形框,然后为每个矩形框为做类别分类概率。

如果用一句话概括NMS的意思就是:筛选出一定区域内属于同一种类别得分最大的框。

如下图,网络模型可以给每个检测框一个score,score越大,说明检测框越接近真实值。

《老友记》yyds!

然后非最大值抑制的目的是删除score小的框,只剩下sore最大作为最终的预测结果。 


1.2 NMS的计算过程

  • 定义置信度阈值IOU阈值取值
  • 置信度降序排列边界框bounding_box
  • 从bbox_list中删除置信度小于阈值的预测框
  • 循环遍历剩余框,首先挑选置信度最高的框作为候选框
  • 接着计算其他和候选框属于同一类的所有预测框和当前候选框的IOU
  • 如果上述任两个框的IOU的值大于IOU阈值,那么从box_list中移除置信度较低的预测框
  • 重复此操作,直到遍历完列表中的所有预测框

在这里插入图片描述


1.3  NMS的局限性

(1)需要手动设置阈值,阈值的设置会直接影响重叠目标的检测,太大造成误检,太小达不到理想情况。

(2)低于阈值的直接设置score为0,做法就比较麻烦了

(3)通过IoU来评估,IoU的做法对目标框尺度和距离的影响不同


1.4 NMS的改进思路

(1)根据手动设置阈值的缺陷,通过自适应的方法在目标稀疏时使用小阈值,目标稠密时使用大阈值。例如Adaptive NMS
(2)由于将低于阈值的直接置为0的做法比较困难,所以我们可以通过将其根据IoU大小来进行惩罚衰减,则变得更加平滑。例如Soft NMS,Softer NMS
(3)IoU的做法存在一定缺陷,改进思路是将目标尺度、距离引进IoU的考虑中。如DIoU等


☀️二、DIoU-NMS、CIoU-NMS、EIoU-NMS、GIoU-NMS 、 SIoU-NMS

🌲2.1 更换DIoU-NMS

论文:https://arxiv.org/pdf/1911.08287.pdf.

一个成熟的IoU衡量指标应该要考虑预测框真实框重叠面积中心点距离长宽比三个方面。但是IoU 只考虑到了预测框与真实框重叠区域,并没有考虑到中心点距离、长宽比。

DIoU-NMS在DIoUloss一文中提出,不仅仅考虑IoU,还考虑两个框中心点之间的距离。如果两个框之间IoU比较大,但是两个框的中心距离比较大时,可能会认为这是两个物体的框而不会被过滤掉。由于DIoU的计算考虑到了两框中心点位置的信息,故使用DIoU进行评判的nms效果更符合实际,效果更优。

公式:


第①步 修改general.py

在YOLOv5当中,作者是直接调用了Pytorch官方的NMS的API。

也就是在general.py中的non_max_suppression函数


首先将下面一段代码粘贴到utils/general.py,重新定义NMS模块。这里的计算IoU的函数bbox_iou则是直接引用了YOLOv5中的代码,其简洁的集成了CIoU、SIoU、EIoU、GIoU、DIoU 的计算。

  1. def NMS(boxes, scores, iou_thres, class_nms='CIoU'):
  2. # class_nms=class_nms
  3. GIoU=CIoU=DIoU=EIoU=SIoU=False
  4. if class_nms == 'CIoU':
  5. CIoU=True
  6. elif class_nms == 'DIoU':
  7. DIoU=True
  8. elif class_nms == 'GIoU':
  9. GIoU=True
  10. elif class_nms == 'EIoU':
  11. EIoU=True
  12. else :
  13. SIoU=True
  14. B = torch.argsort(scores, dim=-1, descending=True)
  15. keep = []
  16. while B.numel() > 0:
  17. index = B[0]
  18. keep.append(index)
  19. if B.numel() == 1: break
  20. iou = bbox_iou(boxes[index, :], boxes[B[1:], :], GIoU=GIoU, DIoU=DIoU, CIoU=CIoU, EIoU=EIoU, SIoU=SIoU)
  21. inds = torch.nonzero(iou <= iou_thres).reshape(-1)
  22. B = B[inds + 1]
  23. return torch.tensor(keep)

第②步 更换NMS

然后我们将non_max_suppression 函数中的

i = torchvision.ops.nms(boxes, scores, iou_thres)

替换为

i = NMS(boxes, scores, iou_thres, class_nms='DIoU')

这样就可以还是训练了~


🌲2.2 更换其他的NMS

 其余几个方法都是一样的,只需要在第②步改个名称即可:

  • DloU:
i = NMS(boxes, scores, iou_thres, class_nms='DIoU') 
  • SloU:
i = NMS(boxes, scores, iou_thres, class_nms='SIoU') 
  • GloU:
i = NMS(boxes, scores, iou_thres, class_nms='GIoU') 
  • EloU:
i = NMS(boxes, scores, iou_thres, class_nms='EIoU') 

☀️三、Soft-NMS

论文:https://arxiv.org/abs/1704.04503

根据前面对目标检测中NMS的算法描述,易得出传统NMS的不足:如果一个物体在另一个物体重叠区域出现,即当两个目标框接近时,分数更低的框就会因为与之重叠面积过大而被删掉,从而导致对该物体的检测失败并降低了算法的平均检测率。

在这里插入图片描述

上图中,有两匹马,都是待检测目标,也有两个检测到的框,得分分别是0.80和0.95
如果用NMS算法,得分最高的框是红色框,得分0.95,而绿色框红色框通过计算IoU,肯定是大于一般我们设置的0.5的,那么绿色框就会被删除,导致少检测到一匹马的情况。此外,NMS算法设置阈值也比较麻烦,如果设置过小,那么会出先这样的事情,少检测到目标;如果设置过大,又会经常出先误检。

因此,出现升级版Soft-NMS。具体流程就是我们把NMS算法中去除其他边界框改成,修改其他边界框的置信度。


第①步 修改general.py

同样,首先将下面一段代码粘贴到utils/general.py,重新定义NMS模块。

  1. def my_soft_nms(bboxes, scores, iou_thresh=0.5, sigma=0.5, score_threshold=0.25):
  2. bboxes = bboxes.contiguous()
  3. x1 = bboxes[:, 0]
  4. y1 = bboxes[:, 1]
  5. x2 = bboxes[:, 2]
  6. y2 = bboxes[:, 3]
  7. # 计算每个box的面积
  8. areas = (x2 - x1 + 1) * (y2 - y1 + 1)
  9. # 首先对所有得分进行一次降序排列,仅此一次,以提高后续查找最大值速度. oeder为降序排列后的索引
  10. _, order = scores.sort(0, descending=True)
  11. # NMS后,保存留下来的边框
  12. keep = []
  13. while order.numel() > 0:
  14. if order.numel() == 1: # 仅剩最后一个box的索引
  15. i = order.item()
  16. keep.append(i)
  17. break
  18. else:
  19. i = order[0].item() # 保留首个得分最大的边框box索引,i为scores中实际坐标
  20. keep.append(i)
  21. # 巧妙使用tersor.clamp()函数求取order中当前框[0]之外每一个边框,与当前框[0]的最大值和最小值
  22. xx1 = x1[order[1:]].clamp(min=x1[i])
  23. yy1 = y1[order[1:]].clamp(min=y1[i])
  24. xx2 = x2[order[1:]].clamp(max=x2[i])
  25. yy2 = y2[order[1:]].clamp(max=y2[i])
  26. # 求取order中其他每一个边框与当前边框的交集面积
  27. inter = (xx2 - xx1).clamp(min=0) * (yy2 - yy1).clamp(min=0)
  28. # 计算order中其他每一个框与当前框的IoU
  29. iou = inter / (areas[i] + areas[order[1:]] - inter) # 共order.numel()-1个
  30. idx = (iou > iou_thresh).nonzero().squeeze() # 获取order中IoU大于阈值的其他边框的索引
  31. if idx.numel() > 0:
  32. iou = iou[idx]
  33. newScores = torch.exp(-torch.pow(iou, 2) / sigma) # 计算边框的得分衰减
  34. scores[order[idx + 1]] *= newScores # 更新那些IoU大于阈值的边框的得分
  35. newOrder = (scores[order[1:]] > score_threshold).nonzero().squeeze()
  36. if newOrder.numel() == 0:
  37. break
  38. else:
  39. newScores = scores[order[newOrder + 1]]
  40. maxScoreIndex = torch.argmax(newScores)
  41. if maxScoreIndex != 0:
  42. newOrder[[0, maxScoreIndex],] = newOrder[[maxScoreIndex, 0],]
  43. # 更新order.
  44. order = order[newOrder + 1]
  45. # 返回保留下来的所有边框的索引值,类型torch.LongTensor
  46. return torch.LongTensor(keep)

第②步 更换NMS

general.py中将NMS改为soft nms。

这步也是和上面一样,将non_max_suppression 函数中的

i = torchvision.ops.nms(boxes, scores, iou_thres)

替换为

     i = my_soft_nms(boxes, scores, iou_thres)  # 

最后就可以开始训练了~