💡💡💡本专栏所有程序均经过测试,可成功执行💡💡💡
在目标检测领域内,尽管YOLO系列的算法傲视群雄,但在某些方面仍然存在改进的空间。在YOLOv5的损失函数中,默认是使用的CIoU,但是CIoU仍然存在一定的问题。例如CIOU的计算方式相对复杂,需要对边界框的坐标进行更多的处理和计算。本文给大家带来的教程是将原来的CIoU替换为EIoU、SIoU、WIoU、DIoU、FocusIoU。文章在介绍主要的原理后,将手把手教学如何进行模块的代码添加和修改,并将修改后的完整代码放在文章的最后,方便大家一键运行,小白也可轻松上手实践。以帮助您更好地学习深度学习目标检测YOLO系列的挑战。
更多损失函数讲解:百面算法工程师 | 损失函数篇
目录
3. 将EIoU、SIoU、WIoU、DIoU、FocusIoU添加到YOLOv5中
1. ✒️CIoU
1.1 CIoU原理
论文地址:Distance-IoU Loss: Faster and Better Learning for Bounding Box Regression——点击即可跳转
论⽂考虑到bbox回归三要素中的⻓宽⽐还没被考虑到计算中,为此,进⼀步在DIoU的基础上提出了CIoU,同时考虑两个矩形的长宽比,也就是形状的相似性。所以CIOU在DIOU的基础上添加了长宽比的惩罚项。
其中, 是权重函数, 而用来度量长宽比的相似性。计算公式为:
☀️优点
更准确的相似性度量:CIOU考虑了边界框的中心点距离和对角线距离,因此可以更准确地衡量两个边界框之间的相似性,尤其是在目标形状和大小不规则的情况下。 鲁棒性更强:相比传统的IoU,CIOU对于目标形状和大小的变化更具有鲁棒性,能够更好地适应各种尺寸和形状的目标检测任务。
⚡️缺点
计算复杂度增加:CIOU引入了额外的中心点距离和对角线距离的计算,因此相比传统的IoU,计算复杂度有所增加,可能会增加一定的计算成本。 实现难度较高:CIOU的计算方式相对复杂,需要对边界框的坐标进行更多的处理和计算,因此在实现上可能会相对困难一些,需要更多的技术和经验支持。
1.2 CIoU计算
中心点 b、中心点 bgt的坐标分别为:(3,4)、(6,6),由此CIoU计算公式如下:
1.3 📌CIoU代码实现
import numpy as np
import IoU
import DIoU
# box : [左上角x坐标,左上角y坐标,右下角x坐标,右下角y坐标]
box1 = [0, 0, 6, 8]
box2 = [3, 2, 9, 10]
# CIoU
def CIoU(box1, box2):
x1, y1, x2, y2 = box1
x3, y3, x4, y4 = box2
# box1的宽:box1_w,box1的高:box1_h,
box1_w = x2 - x1
box1_h = y2 - y1
# box2的宽:box2_w,box2的高:box2_h,
box2_w = x4 - x3
box2_h = y4 - y3
iou = IoU(box1, box2)
diou = DIoU(box1, box2)
# v用来度量长宽比的相似性
v = (4 / (np.pi) ** 2) * (np.arctan(int(box2_w / box2_h)) - np.arctan(int(box1_w / box1_h)))
# α是权重函数
a = v / ((1 + iou) + v)
ciou = diou - a * v
return ciou
print(CIoU(box1, box2))
输出结果:0.1589460263493413
2. ✒️EIOU(Efficient-IoU)
2.1 EIoU原理
论文地址:Focal and Efficient IOU Loss for Accurate Bounding Box Regression
EIOU是在 CIOU 的惩罚项基础上将预测框和真实框的纵横比的影响因子拆开,分别计算预测框和真实框的长和宽,并且加入Focal聚焦优质的锚框,来解决 CIOU 存在的问题。先前基于iou的损失,例如CIOU和GIOU,不能有效地测量目标盒和锚点之间的差异,这导致BBR(边界框回归)模型优化的收敛速度慢,定位不准确。
针对上述问题,对CIOU损失进行了修正,提出了一种更有效的IOU损失,即EIOU损失,定义如下:
其中和是覆盖两个盒子的最小围框的宽度和高度。也就是说,我们将损失函数分为三个部分:IOU损失LIOU,距离损失和方向损失。这样,我们可以保留CIOU损失的有效特点。同时,EIOU损失直接使目标盒与锚盒宽度和高度的差值最小化,从而使收敛速度更快,定位效果更好。
优点:
更稳定的训练过程:通过引入中心点距离和宽高比的惩罚项,即使在IoU为0的情况下,EIou也能提供有效的梯度信息,确保模型能够继续学习。
提高定位精度:通过综合考虑位置和尺寸的匹配,EIou可以显著提升目标检测模型的定位精度,尤其在目标物体大小和形状变化较大时表现更优。
2.2 代码实现
import numpy as np
def calculate_eiou(box1, box2):
# 计算嵌入向量(这里简化为使用中心点坐标作为嵌入向量)
center1 = np.array([(box1[0] + box1[2]) / 2, (box1[1] + box1[3]) / 2])
center2 = np.array([(box2[0] + box2[2]) / 2, (box2[1] + box2[3]) / 2])
# 计算嵌入向量之间的欧式距离
euclidean_distance = np.linalg.norm(center1 - center2)
# 计算目标框的面积
area_box1 = (box1[2] - box1[0]) * (box1[3] - box1[1])
area_box2 = (box2[2] - box2[0]) * (box2[3] - box2[1])
# 计算交集和并集的面积
intersection = max(0, min(box1[2], box2[2]) - max(box1[0], box2[0])) * \
max(0, min(box1[3], box2[3]) - max(box1[1], box2[1]))
union = area_box1 + area_box2 - intersection
# 计算EIOU
eiou = 1 - intersection / union + euclidean_distance
return eiou
box1 = [0, 0, 6, 8]
box2 = [3, 2, 9, 10]
print(calculate_eiou(box1, box2))
输出结果:4.374782044694758
3. 将EIoU、SIoU、WIoU、DIoU、FocusIoU添加到YOLOv5中
3.1 添加代码
关键步骤一: 在
utils/metrics.py
中,找到bbox_iou
函数,可以把原有的注释掉,换成下面的代码:
def bbox_iou(box1, box2, xywh=True, GIoU=False, DIoU=False, CIoU=False, SIoU=False, EIoU=False, Focal=False, alpha=1, gamma=0.5, eps=1e-7):
# Returns Intersection over Union (IoU) of box1(1,4) to box2(n,4)
# Get the coordinates of bounding boxes
if xywh: # transform from xywh to xyxy
(x1, y1, w1, h1), (x2, y2, w2, h2) = box1.chunk(4, -1), box2.chunk(4, -1)
w1_, h1_, w2_, h2_ = w1 / 2, h1 / 2, w2 / 2, h2 / 2
b1_x1, b1_x2, b1_y1, b1_y2 = x1 - w1_, x1 + w1_, y1 - h1_, y1 + h1_
b2_x1, b2_x2, b2_y1, b2_y2 = x2 - w2_, x2 + w2_, y2 - h2_, y2 + h2_
else: # x1, y1, x2, y2 = box1
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 # ori iou
iou = torch.pow(inter/(union + eps), alpha) # alpha iou
if CIoU or DIoU or GIoU or EIoU or SIoU:
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 or SIoU: # Distance or Complete IoU https://arxiv.org/abs/1911.08287v1
c2 = (cw ** 2 + ch ** 2) ** alpha + eps # convex diagonal squared
rho2 = (((b2_x1 + b2_x2 - b1_x1 - b1_x2) ** 2 + (b2_y1 + b2_y2 - b1_y1 - b1_y2) ** 2) / 4) ** alpha # 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_ciou = v / (v - iou + (1 + eps))
if Focal:
return iou - (rho2 / c2 + torch.pow(v * alpha_ciou + eps, alpha)), torch.pow(inter/(union + eps), gamma) # Focal_CIoU
else:
return iou - (rho2 / c2 + torch.pow(v * alpha_ciou + eps, 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 = torch.pow(cw ** 2 + eps, alpha)
ch2 = torch.pow(ch ** 2 + eps, alpha)
if Focal:
return iou - (rho2 / c2 + rho_w2 / cw2 + rho_h2 / ch2), torch.pow(inter/(union + eps), gamma) # Focal_EIou
else:
return iou - (rho2 / c2 + rho_w2 / cw2 + rho_h2 / ch2) # EIou
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)
if Focal:
return iou - torch.pow(0.5 * (distance_cost + shape_cost) + eps, alpha), torch.pow(inter/(union + eps), gamma) # Focal_SIou
else:
return iou - torch.pow(0.5 * (distance_cost + shape_cost) + eps, alpha) # SIou
if Focal:
return iou - rho2 / c2, torch.pow(inter/(union + eps), gamma) # Focal_DIoU
else:
return iou - rho2 / c2 # DIoU
c_area = cw * ch + eps # convex area
if Focal:
return iou - torch.pow((c_area - union) / c_area + eps, alpha), torch.pow(inter/(union + eps), gamma) # Focal_GIoU https://arxiv.org/pdf/1902.09630.pdf
else:
return iou - torch.pow((c_area - union) / c_area + eps, alpha) # GIoU https://arxiv.org/pdf/1902.09630.pdf
if Focal:
return iou, torch.pow(inter/(union + eps), gamma) # Focal_IoU
else:
return iou # IoU
3.2 回调函数
关键步骤二:在
utils/loss.py
中,找到ComputeLoss
类中的__call__()
函数,把Regression loss中计算iou的代码,换成下面这句:
主要对红框部分替换为以下代码
iou = bbox_iou(pbox, tbox[i], CIoU=True) # iou(prediction, target)
if type(iou) is tuple:
lbox += (iou[1].detach().squeeze() * (1 - iou[0].squeeze())).mean()
iou = iou[0].squeeze()
else:
lbox += (1.0 - iou.squeeze()).mean() # iou loss
iou = iou.squeeze()
最后修改参数就在调用bbox_iou中进行修改即可,比如上面的代码就是使用了CIoU,如果你想使用Focal_EIoU那么你可以修改为下:
iou = bbox_iou(pbox, tbox[i], EIoU=True, Focal=True)
改进后:
4.完整代码分享
https://pan.baidu.com/s/1NIlQkf1lUVl5hKRkT1VVIA?pwd=yp1s
提取码: yp1s
5. 进阶
可以融合其他的注意力机制,修改backbone以及neck,多个模块进行改进。
6. 总结
EIou(Enhanced Intersection over Union)损失函数是对传统IoU损失函数的改进,旨在提高目标检测模型的精度和鲁棒性。与仅考虑预测框与真实框重叠程度的IoU不同,EIou在损失计算中引入了几何中心点的距离和宽高比的差异。通过这种方式,即使在预测框和真实框完全不重叠的情况下,EIou也能提供有效的梯度信息,从而确保模型训练的连续性和稳定性。此外,EIou综合考虑位置和尺寸的匹配,有助于减少预测框的位置偏移和形状不匹配,显著提升目标检测的定位精度。总体而言,EIou损失函数适用于各种需要高精度定位的目标检测任务,如自动驾驶和无人机视觉,能够显著提升检测模型的整体性能。