前言:Hello大家好,我是小哥谈。YOLOv5中的NMS指非极大值抑制(Non-Maximum Suppression),它是一种用于目标检测算法中的后处理技术。在检测到多个重叠的边界框时,NMS可以帮助选择最佳的边界框。NMS的工作原理是首先根据预测边界框的置信度对它们进行排序,然后从置信度最高的边界框开始遍历,将与当前边界框的重叠度(通常使用IoU,即交并比)大于某个阈值的边界框移除。这样可以保留置信度最高的边界框,并且消除冗余的边界框。本节课就简单介绍一下常见的NMS并重点讲解如何去更换NMS!🌈
前期回顾:
YOLOv5算法改进(1)— 如何去改进YOLOv5算法
YOLOv5算法改进(2)— 添加SE注意力机制
YOLOv5算法改进(3)— 添加CBAM注意力机制
YOLOv5算法改进(4)— 添加CA注意力机制
YOLOv5算法改进(5)— 添加ECA注意力机制
YOLOv5算法改进(6)— 添加SOCA注意力机制
YOLOv5算法改进(7)— 添加SimAM注意力机制
YOLOv5算法改进(8)— 替换主干网络之MobileNetV3
YOLOv5算法改进(9)— 替换主干网络之ShuffleNetV2
YOLOv5算法改进(10)— 替换主干网络之GhostNet
YOLOv5算法改进(11)— 替换主干网络之EfficientNetv2
YOLOv5算法改进(12)— 替换主干网络之Swin Transformer
YOLOv5算法改进(13)— 替换主干网络之PP-LCNet
YOLOv5算法改进(14)— 更换Neck之BiFPN
YOLOv5算法改进(15)— 更换Neck之AFPN
YOLOv5算法改进(16)— 增加小目标检测层
YOLOv5算法改进(17)— 更换损失函数(EIoU、AlphaIoU、SIoU和WIoU)
YOLOv5算法改进(18)— 更换激活函数(SiLU、ReLU、ELU、Hardswish、Mish、Softplus等)
目录
🚀1.NMS(非极大抑制)概念
🚀2.NMS(非极大抑制)工作原理
🚀3.DIoU-NMS、CIoU-NMS、EIoU-NMS、GIoU-NMS和SIoU-NMS
💥💥3.1 更换DIoU-NMS
💞步骤1:修改general.py
💞步骤2:更换NMS
💥💥3.2 更换其他NMS
🚀4.Soft-NMS
💞步骤1:修改general.py
💞步骤2:更换NMS
🚀1.NMS(非极大抑制)概念
NMS(Non-Maximum Suppression)是一种目标检测算法中常用的技术,用于在多个候选框中选择最佳的目标框。其主要目的是消除冗余的候选框,只保留最具代表性的目标框。在最近几年常见的物体检测算法(包括RCNN、Fast-RCNN、Faster-RCNN等)中,最终都会从一张图片中找出很多个可能是物体的矩形框,然后为每个矩形框为做类别分类概率。
举例:目标检测的过程中,在同一目标的位置上会产生大量的候选框,这些候选框相互之间可能会有重叠,此时我们需要利用NMS(非极大值抑制)找到最佳的目标边界框,消除冗余的边界框。🌿
说明:♨️♨️♨️
NMS(Non-Maximum Suppression)在目标检测的最后一个阶段使用,即在候选框生成和分类完成后,用于消除冗余的候选框,保留最具代表性的目标框。
🚀2.NMS(非极大抑制)工作原理
NMS 的工作原理如下:
- 根据模型预测结果,得到一系列候选框,并计算其置信度得分。
- 按照置信度得分降序排列候选框。
- 选择置信度得分最高的候选框,并将其添加到最终输出的目标框列表中。
- 逐个遍历剩余的候选框,与已选择的目标框计算重叠度(如交并比)。
- 如果某个候选框与已选择的目标框重叠度高于设定的阈值,则将该候选框舍弃。
- 重复步骤4和5,直到遍历完所有候选框。
这样,NMS 可以在一系列候选框中选择出最具代表性的目标框,并消除冗余。这对于目标检测任务中的多目标检测非常重要。🌴
名词解释:
IOU即交并比,是目标检测中衡量目标检测算法准确度的一个重要指标,顾名思义,即交集与并集的比值。
局限性:
- 循环步骤,GPU难以并行处理,运算效率低;
- 以分类置信度为优先衡量指标分类置信度高的定位不一定最准,降低了模型的定位准确度;
- 直接提高阈值暴力去除bbox 将得分较低的边框强制性地去掉,如果物体出现较为密集时,本身属 于两个物体的边框,其中得分较低的框就很有可能被抑制掉,从而降低了模型的召回率,且阈值设定完全依赖自身经验。
🚀3.DIoU-NMS、CIoU-NMS、EIoU-NMS、GIoU-NMS和SIoU-NMS
💥💥3.1 更换DIoU-NMS
非最大抑制(NMS)主要用于基于深度学习的目标检测模型输出的后处理,从而去除冗余的检测框,获得正确的检测结果。其具体意义是指在目标检测的预测阶段时,会输出许多候选的anchor box,其中有很多是明显重叠的预测边界框都围绕着同一个目标,这时候就可以使用NMS来合并同一目标的类似边界框,或者说是保留这些边界框中最好的一个。
在经典的NMS中,得分最高的检测框和其它检测框逐一算出一个对应的IOU值,并将该值超过NMS threshold的框全部过滤掉。可以看出,在经典NMS算法中,IOU是唯一考量的因素。但是在实际应用场景中,当两个不同物体挨得很近时,由于IOU值比较大,往往经过NMS处理后,只剩下一个检测框,这样导致漏检的错误情况发生。基于此,DIOU-NMS就不仅仅考虑IOU,还考虑两个框中心点之间的距离。如果两个框之间IOU比较大,但是两个框的距离比较大时,可能会认为这是两个物体的框而不会被过滤掉。
实验证明,将NMS替换为DIoU-NMS,可初步改善YOLOv5对重叠遮挡目标的识别。🌱
公式:
论文题目:《Distance-IoU Loss: Faster and Better Learning for Bounding Box Regression》
论文地址: https://arxiv.org/pdf/1911.08287.pdf
代码实现: https://github.com/Zzh-tju/DIoU
💞步骤1:修改general.py
将如下代码复制粘贴到utils / general.py。
def NMS(boxes, scores, iou_thres, GIoU=False, DIoU=True, CIoU=False, EIoU=False, SIoU=False):
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 = bbox_iou(boxes[index, :], boxes[B[1:], :], GIoU=GIoU, DIoU=DIoU, CIoU=CIoU, EIoU=EIoU, SIoU=SIoU)
inds = torch.nonzero(iou <= iou_thres).reshape(-1)
B = B[inds + 1]
return torch.tensor(keep)
def soft_nms(bboxes, scores, iou_thresh=0.5, sigma=0.5, score_threshold=0.25):
order = scores.argsort(descending=True).to(bboxes.device)
keep = []
while order.numel() > 1:
if order.numel() == 1:
keep.append(order[0])
break
else:
i = order[0]
keep.append(i)
iou = bbox_iou(bboxes[i], bboxes[order[1:]]).squeeze()
idx = (iou > iou_thresh).nonzero().squeeze()
if idx.numel() > 0:
iou = iou[idx]
new_scores = torch.exp(-torch.pow(iou, 2) / sigma)
scores[order[idx + 1]] *= new_scores
new_order = (scores[order[1:]] > score_threshold).nonzero().squeeze()
if new_order.numel() == 0:
break
else:
max_score_index = torch.argmax(scores[order[new_order + 1]])
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)
然后导入包:
from utils.metrics import box_iou, fitness, bbox_iou
💞步骤2:更换NMS
将non_max_suppression函数中的代码:
i = torchvision.ops.nms(boxes, scores, iou_thres)
替换为:
i = NMS(boxes, scores, iou_thres, class_nms='DIoU')
然后就可以训练了!~🌟
说明:♨️♨️♨️
经过实际检验,NMS耗时对比:
NMS:2.7ms
DIoU-NMS:9.1ms
💥💥3.2 更换其他NMS
更换其他NMS与上述更换DIoU-NMS步骤是一样的,区别在于需要在步骤2改个名称。👇
关于更换其他NMS,具体代码分别如下所示:
DIoU-NMS:
i = NMS(boxes, scores, iou_thres, class_nms='DIoU')
CIoU-NMS:
i = NMS(boxes, scores, iou_thres, class_nms='CIoU')
EIoU-NMS:
i = NMS(boxes, scores, iou_thres, class_nms='EIoU')
GIoU-NMS :
i = NMS(boxes, scores, iou_thres, class_nms='GIoU')
SIoU-NMS:
i = NMS(boxes, scores, iou_thres, class_nms='SIoU')
🚀4.Soft-NMS
Soft-NMS是一种目标检测算法,用于提高检测结果的准确性。传统的非极大抑制(NMS)算法在处理重叠的边界框时,会选择具有最高置信度的边界框,并抑制与其IoU(交并比)大于一定阈值的其他边界框。然而,这种方法可能会忽略具有稍低置信度但仍然准确的边界框。
Soft-NMS通过降低与最高置信度边界框重叠的边界框的得分,而不是直接抑制它们,来解决这个问题。它引入了一个衰减函数,根据两个边界框之间的IoU值来减少得分。这样,即使是与最高置信度边界框重叠较多的边界框也能保留一定得分,并且不会被完全抑制。🌻
论文题目:《Improving Object Detection With One Line of Code》
论文地址: https://arxiv.org/pdf/1704.04503v2.pdf
代码实现: mirrors / bharatsingh430 / soft-nms · GitCode
💞步骤1:修改general.py
将如下代码复制粘贴到utils / general.py。
def my_soft_nms(bboxes, scores, iou_thresh=0.5, sigma=0.5, score_threshold=0.25):
bboxes = bboxes.contiguous()
x1 = bboxes[:, 0]
y1 = bboxes[:, 1]
x2 = bboxes[:, 2]
y2 = bboxes[:, 3]
# 计算每个box的面积
areas = (x2 - x1 + 1) * (y2 - y1 + 1)
# 首先对所有得分进行一次降序排列,仅此一次,以提高后续查找最大值速度. oeder为降序排列后的索引
_, order = scores.sort(0, descending=True)
# NMS后,保存留下来的边框
keep = []
while order.numel() > 0:
if order.numel() == 1: # 仅剩最后一个box的索引
i = order.item()
keep.append(i)
break
else:
i = order[0].item() # 保留首个得分最大的边框box索引,i为scores中实际坐标
keep.append(i)
# 巧妙使用tersor.clamp()函数求取order中当前框[0]之外每一个边框,与当前框[0]的最大值和最小值
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])
# 求取order中其他每一个边框与当前边框的交集面积
inter = (xx2 - xx1).clamp(min=0) * (yy2 - yy1).clamp(min=0)
# 计算order中其他每一个框与当前框的IoU
iou = inter / (areas[i] + areas[order[1:]] - inter) # 共order.numel()-1个
idx = (iou > iou_thresh).nonzero().squeeze() # 获取order中IoU大于阈值的其他边框的索引
if idx.numel() > 0:
iou = iou[idx]
newScores = torch.exp(-torch.pow(iou, 2) / sigma) # 计算边框的得分衰减
scores[order[idx + 1]] *= newScores # 更新那些IoU大于阈值的边框的得分
newOrder = (scores[order[1:]] > score_threshold).nonzero().squeeze()
if newOrder.numel() == 0:
break
else:
newScores = scores[order[newOrder + 1]]
maxScoreIndex = torch.argmax(newScores)
if maxScoreIndex != 0:
newOrder[[0, maxScoreIndex],] = newOrder[[maxScoreIndex, 0],]
# 更新order.
order = order[newOrder + 1]
# 返回保留下来的所有边框的索引值,类型torch.LongTensor
return torch.LongTensor(keep)
💞步骤2:更换NMS
将non_max_suppression函数中的代码:
i = torchvision.ops.nms(boxes, scores, iou_thres)
替换为:
i = my_soft_nms(boxes, scores, iou_thres) #
然后就可以训练了!~🌟