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

文章目录

    • 一、NMS(非极大抑制)简介
      • 1.1 什么是NMS?
      • 1.2 NMS的计算过程
      • 1.3 NMS的局限性
      • 1.4 NMS的改进思路
    • 二、DIoU-NMS、CIoU-NMS、EIoU-NMS、GIoU-NMS 、 SIoU-NMS
      • 2.1 更换DIoU-NMS
        • 第①步 修改general.py
        • 第②步 更换NMS
      • 2.2 更换其他的NMS
    • 三、Soft-NMS
        • 第①步 修改general.py
        • 第②步 更换NMS

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

1.1 什么是NMS?

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

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

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

img《老友记》yyds!

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

img


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

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

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

公式:img


第①步 修改general.py

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

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

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

def NMS(boxes, scores, iou_thres, class_nms='CIoU'):
    # 初始化不同的IoU (Intersection over Union) 类型标志
    GIoU = CIoU = DIoU = EIoU = SIoU = False

    # 根据 class_nms 参数选择要使用的 IoU 类型
    if class_nms == 'CIoU':
        CIoU = True
    elif class_nms == 'DIoU':
        DIoU = True
    elif class_nms == 'GIoU':
        GIoU = True
    elif class_nms == 'EIoU':
        EIoU = True
    else:
        SIoU = True

    # 根据分数对边界框索引进行排序(从高到低)
    B = torch.argsort(scores, dim=-1, descending=True)

    # 存储最终选定的边界框索引
    keep = []

    while B.numel() > 0:
        # 选择分数最高的边界框的索引
        index = B[0]
        # 将该索引添加到最终保留的列表中
        keep.append(index)
        if B.numel() == 1:
            break

        # 计算当前边界框与其余边界框的IoU
        iou = bbox_iou(boxes[index, :], boxes[B[1:], :], GIoU=GIoU, DIoU=DIoU, CIoU=CIoU, EIoU=EIoU, SIoU=SIoU)
        # 找到IoU小于等于指定阈值的边界框索引
        inds = torch.nonzero(iou <= iou_thres).reshape(-1)
        # 更新B以排除已经被选择的边界框
        B = B[inds + 1]

    # 返回最终选定的边界框索引作为结果
    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

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

在这里插入图片描述

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

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


第①步 修改general.py

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

def my_soft_nms(bboxes, scores, iou_thresh=0.5, sigma=0.5, score_threshold=0.25):
    # 将边界框张量转换为连续内存布局
    bboxes = bboxes.contiguous()
    x1, y1, x2, y2 = bboxes[:, 0], bboxes[:, 1], bboxes[:, 2], bboxes[:, 3]
    
    # 计算每个边界框的面积
    areas = (x2 - x1 + 1) * (y2 - y1 + 1)
    
    # 对得分进行降序排序,并获取排序后的索引
    _, order = scores.sort(0, descending=True)
    
    # 存储最终选定的边界框索引
    keep = []

    while order.numel() > 0:
        i = order[0].item()
        keep.append(i)

        # 计算当前边界框与其他边界框的交集坐标
        xx1 = x1[order[1:]].clamp(min=x1[i])
        yy1 = y1[order[1:]].clamp(min=y1[i])
        xx2 = x2[order[1:]].clamp(max=x2[i])
        yy2 = y2[order[1:]].clamp(max=y2[i])

        # 计算交集的面积
        inter = (xx2 - xx1).clamp(min=0) * (yy2 - yy1).clamp(min=0)
        
        # 计算当前边界框与其他边界框的IoU(交并比)
        iou = inter / (areas[i] + areas[order[1:]] - inter)

        # 找到IoU大于指定阈值的边界框索引
        idx = (iou > iou_thresh).nonzero().squeeze()

        if idx.numel() > 0:
            iou = iou[idx]
            
            # 计算IoU对应的新得分,并应用得分衰减
            new_scores = torch.exp(-torch.pow(iou, 2) / sigma)
            
            # 更新具有高IoU的边界框的得分
            scores[order[idx + 1]] *= new_scores

        # 根据得分阈值过滤边界框
        new_order = (scores[order[1:]] > score_threshold).nonzero().squeeze()

        if new_order.numel() == 0:
            break
        else:
            new_scores = scores[order[new_order + 1]]
            max_score_index = torch.argmax(new_scores)

            if max_score_index != 0:
                new_order[[0, max_score_index]] = new_order[[max_score_index, 0]]

            order = order[new_order + 1]

    # 返回最终选定的边界框索引作为结果
    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)  # 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小酒馆燃着灯

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值