秋招面试专栏推荐 :深度学习算法工程师面试问题总结【百面算法工程师】——点击即可跳转
💡💡💡本专栏所有程序均经过测试,可成功执行💡💡💡
专栏目录: 《YOLOv5入门 + 改进涨点》专栏介绍 & 专栏目录 |目前已有80+篇内容,内含各种Head检测头、损失函数Loss、Backbone、Neck、NMS等创新点改进
本文给大家介绍的是Soft-NMS,Soft-NMS (Soft Non-Maximum Suppression) 是目标检测算法中用来筛选边界框的一个技术,它是对传统 NMS(Non-Maximum Suppression)的改进。文章在介绍主要的原理后,将手把手教学如何进行模块的代码添加和修改,并将修改后的完整代码放在文章的最后,方便大家一键运行,小白也可轻松上手实践。以帮助您更好地学习深度学习目标检测YOLO系列的挑战。
目录
1. 原理
论文地址:Improving Object Detection With One Line of Code——点击即可跳转
官方代码:官方代码仓库——点击即可跳转
Soft-NMS 是一种改进的非极大值抑制 (NMS) 算法,用于目标检测任务中减少误检并提高平均精度。传统的 NMS 算法会将与当前最高得分框重叠程度超过阈值的框的得分置为 0,从而消除误检。然而,这种方法也容易导致漏检,因为一些实际存在的目标可能由于得分略低而被错误地抑制。 Soft-NMS 的主要原理是通过降低重叠框的得分,而不是将其完全抑制,来避免漏检。具体来说,Soft-NMS 根据框与最高得分框的重叠程度,将其得分按线性或高斯函数进行衰减。这样,重叠框仍然保留在检测结果中,但得分更低,从而降低了误检的可能性,同时避免了漏检。 Soft-NMS 的优势在于:
-
减少漏检: 通过降低重叠框的得分,而不是将其完全抑制,Soft-NMS 能够保留一些实际存在的目标,从而提高检测的召回率。
-
提高平均精度: Soft-NMS 能够在多个重叠阈值下提高平均精度,因为它避免了传统 NMS 在低阈值下漏检和高阈值下误检的问题。
-
易于实现: Soft-NMS 的实现非常简单,只需修改传统 NMS 算法中的一行代码即可。
-
无需额外训练: Soft-NMS 无需对模型进行额外的训练,可以直接应用于现有的目标检测模型。
Soft-NMS 的工作原理
Soft-NMS 的工作流程与传统的 NMS 类似,主要区别在于对重叠框的得分进行衰减,而不是将其置为 0。具体步骤如下:
-
排序: 将所有检测框按照得分进行排序。
-
选择最高得分框: 选择得分最高的检测框作为当前框。
-
更新重叠框的得分: 对于与当前框重叠程度超过阈值的检测框,将其得分按照线性或高斯函数进行衰减。
-
重复步骤 2-3: 重复选择得分最高的检测框并更新重叠框的得分,直到所有检测框都被处理完毕。
Soft-NMS 的参数
Soft-NMS 有两个主要参数:
-
重叠阈值 (Nt): 与传统 NMS 中的重叠阈值相同,用于确定哪些框被认为是重叠框。
-
衰减参数 (σ): 用于控制得分衰减的程度,σ 越大,衰减程度越大。
Soft-NMS 的应用
Soft-NMS 可以应用于任何基于 NMS 的目标检测模型,例如 Faster R-CNN、R-FCN 等。实验结果表明,Soft-NMS 能够在各种目标检测任务中提高平均精度,并减少漏检。
2. NMS 和 soft- nms的对比
特性 | NMS (Non-Maximum Suppression) | Soft-NMS (Soft Non-Maximum Suppression) |
---|---|---|
抑制方式 | 硬阈值抑制:直接删除重叠度高的框 | 软抑制:逐渐降低重叠框的得分 |
边界框得分处理 | 保留最高得分的框,删除其他 | 根据重叠程度调整边界框得分 |
误杀问题 | 容易误删正确的边界框 | 减少误删,有助于保留正确的边界框 |
适应性 | 对密集目标表现较差 | 对密集和重叠目标更为鲁棒 |
算法复杂度 | 较低 | 略高,但仍然接近 NMS |
检测精度 | 在某些情况下可能精度下降 | 提高了检测精度,尤其是在多目标密集场景中 |
通用性 | 普遍使用,直接应用 | 可以无缝替换传统 NMS,适配性强 |
使用场景 | 适用于目标分布较分散的情况 | 适用于目标分布密集或边界模糊的情况 |
3. Soft-nms替换nms
步骤一:
复制下面的代码,粘贴到\yolov5\utils\general.py的non_max_suppression函数上面【在1000行附近】
def nms_iou(box1, box2, GIoU=False, DIoU=False, CIoU=False, SIoU=False, EIou=False, eps=1e-7):
# Returns Intersection over Union (IoU) of box1(1,4) to box2(n,4)
b1_x1, b1_y1, b1_x2, b1_y2 = box1.chunk(4, -1)
b2_x1, b2_y1, b2_x2, b2_y2 = box2.chunk(4, -1)
w1, h1 = b1_x2 - b1_x1, (b1_y2 - b1_y1).clamp(eps)
w2, h2 = b2_x2 - b2_x1, (b2_y2 - b2_y1).clamp(eps)
# Intersection area
inter = (b1_x2.minimum(b2_x2) - b1_x1.maximum(b2_x1)).clamp(0) * \
(b1_y2.minimum(b2_y2) - b1_y1.maximum(b2_y1)).clamp(0)
# Union Area
union = w1 * h1 + w2 * h2 - inter + eps
# IoU
iou = inter / union
if CIoU or DIoU or GIoU or EIou:
cw = b1_x2.maximum(b2_x2) - b1_x1.minimum(b2_x1) # convex (smallest enclosing box) width
ch = b1_y2.maximum(b2_y2) - b1_y1.minimum(b2_y1) # convex height
if CIoU or DIoU or EIou: # Distance or Complete IoU https://arxiv.org/abs/1911.08287v1
c2 = cw ** 2 + ch ** 2 + eps # convex diagonal squared
rho2 = ((b2_x1 + b2_x2 - b1_x1 - b1_x2) ** 2 + (b2_y1 + b2_y2 - b1_y1 - b1_y2) ** 2) / 4 # center dist ** 2
if CIoU: # https://github.com/Zzh-tju/DIoU-SSD-pytorch/blob/master/utils/box/box_utils.py#L47
v = (4 / math.pi ** 2) * (torch.atan(w2 / h2) - torch.atan(w1 / h1)).pow(2)
with torch.no_grad():
alpha = v / (v - iou + (1 + eps))
return iou - (rho2 / c2 + v * alpha) # CIoU
elif EIou:
rho_w2 = ((b2_x2 - b2_x1) - (b1_x2 - b1_x1)) ** 2
rho_h2 = ((b2_y2 - b2_y1) - (b1_y2 - b1_y1)) ** 2
cw2 = cw ** 2 + eps
ch2 = ch ** 2 + eps
return iou - (rho2 / c2 + rho_w2 / cw2 + rho_h2 / ch2)
return iou - rho2 / c2 # DIoU
c_area = cw * ch + eps # convex area
return iou - (c_area - union) / c_area # GIoU https://arxiv.org/pdf/1902.09630.pdf
elif SIoU:
# SIoU Loss https://arxiv.org/pdf/2205.12740.pdf
s_cw = (b2_x1 + b2_x2 - b1_x1 - b1_x2) * 0.5 + eps
s_ch = (b2_y1 + b2_y2 - b1_y1 - b1_y2) * 0.5 + eps
sigma = torch.pow(s_cw ** 2 + s_ch ** 2, 0.5)
sin_alpha_1 = torch.abs(s_cw) / sigma
sin_alpha_2 = torch.abs(s_ch) / sigma
threshold = pow(2, 0.5) / 2
sin_alpha = torch.where(sin_alpha_1 > threshold, sin_alpha_2, sin_alpha_1)
angle_cost = torch.cos(torch.arcsin(sin_alpha) * 2 - math.pi / 2)
rho_x = (s_cw / cw) ** 2
rho_y = (s_ch / ch) ** 2
gamma = angle_cost - 2
distance_cost = 2 - torch.exp(gamma * rho_x) - torch.exp(gamma * rho_y)
omiga_w = torch.abs(w1 - w2) / torch.max(w1, w2)
omiga_h = torch.abs(h1 - h2) / torch.max(h1, h2)
shape_cost = torch.pow(1 - torch.exp(-1 * omiga_w), 4) + torch.pow(1 - torch.exp(-1 * omiga_h), 4)
return iou - 0.5 * (distance_cost + shape_cost)
return iou # IoU
def soft_nms(bboxes, scores, iou_thresh=0.5, sigma=0.5, score_threshold=0.25):
order = torch.arange(0, scores.size(0)).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 = nms_iou(bboxes[i], bboxes[order[1:]], CIoU=True).squeeze()
idx = (iou > iou_thresh).nonzero().squeeze()
if idx.numel() > 0:
iou = iou[idx]
newScores = torch.exp(-torch.pow(iou, 2) / sigma)
scores[order[idx + 1]] *= newScores
newOrder = (scores[order[1:]] > score_threshold).nonzero().squeeze()
if newOrder.numel() == 0:
break
else:
maxScoreIndex = torch.argmax(scores[order[newOrder + 1]])
if maxScoreIndex != 0:
newOrder[[0, maxScoreIndex],] = newOrder[[maxScoreIndex, 0],]
order = order[newOrder + 1]
return torch.LongTensor(keep)
步骤二:
将non_max_suppression函数中 i = torchvision.ops.nms(boxes, scores, iou_thres) # NMS 这句代码替换为 i = soft_nms(boxes, scores, iou_thres)
i = torchvision.ops.nms(boxes, scores, iou_thres) # NMS
替换为
i = soft_nms(boxes, scores, iou_thres)
4. 完整代码
https://pan.baidu.com/s/1XRLupL_qDsnyogbIzXzgPQ?pwd=hy91
提取码: hy91
5.进阶
可以结合损失函数或者卷积模块进行多重改进
YOLOv5改进 | 损失函数 | EIoU、SIoU、WIoU、DIoU、FocuSIoU等多种损失函数——点击即可跳转
6. 总结
Soft-NMS 的主要原理是在进行边界框抑制时,不是直接删除与最高得分框重叠度(IoU)超过阈值的其他边界框,而是根据这些边界框与最高得分框的重叠程度逐渐降低它们的得分。具体来说,Soft-NMS 会对每个候选框根据其与最高得分框的 IoU 计算一个新的得分,该得分会随 IoU 的增加而衰减,而不是一刀切地将得分较高的框保留、将得分较低的框完全删除。通过这种方式,Soft-NMS 保留了更多潜在的正确边界框,尤其是在多个目标靠近或边界模糊的情况下,从而减少了误杀(删除正确边界框)的情况,最终提高了目标检测的整体精度和鲁棒性。