一、IOU Loss
旷视在2016文章《UnitBox: An Advanced Object Detection Network》中提出了IOU Loss将4个点构成的box看成一个整体做回归。
文章链接:https://arxiv.org/pdf/1608.01471.pdf
1. 函数特性
IOU Loss的定义是先求出预测框和真实框之间的交集和并集之比,再求负对数,但是在实际使用中我们常常将IOU Loss写成1-IOU。如果两个框重合则交并比等于1,Loss为0说明重合度非常高。IOU满足非负性、同一性、对称性、三角不等性,相比于L1/L2等损失函数还具有尺度不变性,不论box的尺度大小,输出的iou损失总是在0-1之间。所以能够较好的反映预测框与真实框的检测效果。
伪代码如下:
IOU的优缺点
普通IOU的优缺点很明显,优点:
1、IOU具有尺度不变性
2、满足非负性
同时,由于IOU并没有考虑框之间的距离,所以它的作为loss函数的时候也有相应的缺点:
1、在A框与B框不重合的时候IOU为0,不能正确反映两者的距离大小。
2、IoU无法精确的反映两者的重合度大小。如下图所示,三种情况IOU都相等,但看得出来他们的重合度是不一样的,左边的图回归的效果最好,右边的最差。
普通IOU是对两个框的距离不敏感的,如缺点一中的两个图图中,左图预测框的坐标要比右图预测框的坐标更接近真实框。但两者的IOU皆为0,如果直接把IOU当作loss函数进行优化,则loss=0,没有梯度回传,所以无法进行训练。
针对IOU的上述缺点GIOU应运而生
二、GIOU Loss
GIOU是为克服IOU的缺点同时充分利用优点而提出的.(论文:Generalized Intersection over Union: A Metric and A Loss for Bounding Box Regression)
https://arxiv.org/pdf/1902.09630.pdf
GIOU公式如下:
计算过程如下:
1.假设A为预测框,B为真实框,S是所有框的集合
2.不管A与B是否相交,C是包含A与B的最小框(包含A与B的最小凸闭合框),C也属于S集合
3.首先计算IoU,A与B的交并比
4.再计算C框中没有A与B的面积,比上C框面积;
5.IoU减去前面算出的比;得到GIoU
过程图示如下所示:
当IOU为0时,意味着A与B非常远时,
A
∪
B
/
C
A ∪ B /C
A∪B/C无限接近于0,GIOU趋近于-1,同理当IOU为1时,两框重合,
A
∪
B
/
C
A ∪ B /C
A∪B/C为1。所以GIOU的取值为(-1, 1]。
GIOU作为loss函数时,为$L = 1 − G I O U $ ,当A、B两框不相交时
A
∪
B
A ∪ B
A∪B值不变,最大化G I O U 就是就小化C,这样就会促使两个框不断靠近。
尽管GIoU解决了在IoU作为损失函数时梯度无法计算的问题,且加入了最小外包框作为惩罚项。但是它任然存在一些问题。下图第一行的三张图片是GIoU迭代时预测框收敛情况。其中黑色框代表anchor,蓝色框代表预测框,绿色框代表真实框。
上图中可以看出,GIoU在开始的时候需要将检测结果方法使其与目标框相交,之后才开始缩小检测结果与GT重合,这就带来了需要较多的迭代次数才能收敛问题,特别是对于水平与垂直框的情况下。此外,其在一个框包含另一个框的情况下,GIoU降退化成IoU,无法评价好坏,见下图所示:
对此,DIOU又被提出
三、DIOU Loss
论文如下:
https://arxiv.org/pdf/1911.08287.pdf
计算公式如下:
式中d = ρ ( A , B ) d = \rho(A, B)d=ρ(A,B)是A框与B框中心点坐标的欧式距离,而c cc则是包住它们的最小方框的对角线距离。
完整的DIoU Loss定义如下:
其优点如下:
1、DIoU的惩罚项是基于中心点的距离和对角线距离的比值,避免了像GIoU在两框距离较远时,产生较大的外包框,Loss值较大难以优化(因为它的惩罚项是A ∪ B比上最小外包框的面积)。所以DIoU Loss收敛速度会比GIoU Loss快。
2、即使在一个框包含另一个框的情况下,c值不变,但d值也可以进行有效度量。
3、与GIoU loss类似,DIoU loss在与目标框不重叠时,仍然可以为边界框提供移动方向。
4、DIoU loss可以直接最小化两个目标框的距离,而GIOU loss优化的是两个目标框之间的面积,因此比GIoU loss收敛快得多。
5、对于包含两个框在水平方向和垂直方向上这种情况,DIoU损失可以使回归非常快,而GIoU损失几乎退化为IoU损失
一个好的目标框回归损失应该考虑三个重要的几何因素:重叠面积、中心点距离、长宽比。
GIoU:为了归一化坐标尺度,利用IoU,并初步解决IoU为零的情况。
DIoU:DIoU损失同时考虑了边界框的重叠面积和中心点距离。
然而,anchor框和目标框之间的长宽比的一致性也是极其重要的。基于此,论文作者提出了Complete-IoU Loss。
四、CIOU Loss
同时DIoU的作者考虑到,在两个框中心点重合时,c与d的值都不变。所以此时需要引入框的宽高比:
其中alpha是权重函数,v 用来度量宽高比的一致性:
最终CIoU Loss定义为:
五、EIoU Loss/Focal EIoU Loss
https://arxiv.org/pdf/2101.08158.pdf
CIoU loss对于长宽比加入loss的设计不太合理,于是将CIoU loss中反应长宽比一致性的部分替换成了分别对于长和宽的一致性loss,形成了EIoU loss。具体的loss公式如下:
EIOU Loss包括三个部分:IOU损失、距离损失、高宽损失(重叠面积、中心点举例、高宽比)
w
c
w^c
wc和
h
c
h^c
hc是预测边界框与真实边界框的最小外接矩形的宽度和高度。ρ是两点之间的欧氏距离。
不太好的回归样本对回归loss产生了比较大的影响,回归质量相对较好的样本则难以进一步优化,所以论文提出Focal EIoU loss进行回归质量较好和质量较差的样本之间的平衡。
Focal Loss针对越困难的样本损失越大,起到的是困难样本挖掘的作用。在这里IOU越高的损失越大,相当于加权作用,给越好的回归目标一个越大的损失,有助于提高回归精度。
其公式如下:
六、SIoU loss
https://arxiv.org/ftp/arxiv/papers/2205/2205.12740.pdf
已有方法匹配真实框和预测框之间的IoU、中心点距离、宽高比等,它们均未考虑真实框和预测框之间不匹配的方向。这种不足导致收敛速度较慢且效率较低,因为预测框可能在训练过程中“徘徊”,最终生成更差的模型。
siou主要包含如下几个部分:
1)角度损失(Angle cost)
Angle cost描述了中心点连接(图1)与x-y轴之间的最小角度,当中心点在x轴或y轴上对齐时,Λ = 0。当中心点连接到x轴45°时,Λ = 1。这一惩罚可以引导anchor box移动到目标框的最近的轴上,减少了BBR的总自由度数。
2)距离损失(Distance cost)
可以看出,当𝛼→0时,Distance cost的贡献大大降低。相反,𝛼越接近π/4,Distance cost贡献越大。随着角度的增大,问题变得越来越难。因此,γ被赋予较大的值。具体来说:以X轴为例,即两框近乎平行时,a趋近于0,这样计算出来两框之间的角度距离接近于0,此时γ也接近于2,那么两框之间的距离对于整体loss的贡献变少了。而当a趋近与45°时,计算出来两框之间的角度为1,此时γ接近1,则两框之间的距离应该被重视,需要占更大的loss。
3)形状损失(Shape cost)
θ控制对形状损失的关注程度,为了避免过于关注形状损失而降低对预测框的移动,θ参数范围为[2, 6]。
最终整体的siou的损失公式如下:
七、WIoU loss
https://arxiv.org/pdf/2301.10051v1.pdf
Focal EIoU v1被提出来解决质量较好和质量较差的样本间的BBR平衡问题,但由于其静态聚焦机制(FM),非单调FM的潜力没有被充分利用,基于这一思想,作者提出了一种基于IoU的损失,该损失具有动态非单调FM,名为Wise IoU(WIoU)。
主要贡献总结如下:
提出了BBR的基于注意力的损失WIoU v1,它在仿真实验中实现了比最先进的SIoU更低的回归误差。
设计了具有单调FM的WIoU v2和具有动态非单调FM的WIoU v3。利用动态非单调FM的明智的梯度增益分配策略,WIoU v3获得了优越的性能。
对低质量的样本的影响进行了一系列详细的研究,证明了动态非单调调频的有效性和效率。
由于训练数据不可避免地包含低质量示例,几何因素(如距离和纵横比)将加重对低质量示例的惩罚,从而降低模型的泛化性能。当anchor box与目标box很好地重合时,一个好的损失函数应该会削弱几何因素的惩罚,而较少的训练干预将使模型获得更好的泛化能力。
WIoU loss定义:
其中 W g W_g Wg, H g H_g Hg表示最小包围框的宽和高。为了防止 R w i o u R_{wiou} Rwiou产生阻碍收敛的梯度, W g W_g Wg和 H g H_g Hg从计算图中分离出来(上标*表示此操作)。因为它有效地消除了阻碍收敛的因素,所以没有引入新的度量,例如纵横比。
1、 R w i o u ∈ [ 1 , e ) R_{wiou}∈[1,e) Rwiou∈[1,e),这将显著放大普通质量anchor box的LIoU。
2、 L i o u ∈ [ 0 , 1 ] L_{iou}∈[0,1] Liou∈[0,1],这将显著降低高质量anchor box的 R w i o u R_{wiou} Rwiou,并在anchor box与目标框重合时,重点关注中心点之间的距离。
代码实现如下:
class WIoU_Scale:
''' monotonous: {
None: origin v1
True: monotonic FM v2
False: non-monotonic FM v3
}
momentum: The momentum of running mean'''
iou_mean = 1.
monotonous = True
_momentum = 1 - 0.5 ** (1 / 7000)
_is_train = True
def __init__(self, iou):
self.iou = iou
self._update(self)
@classmethod
def _update(cls, self):
if cls._is_train: cls.iou_mean = (1 - cls._momentum) * cls.iou_mean + \
cls._momentum * self.iou.detach().mean().item()
@classmethod
def _scaled_loss(cls, self, gamma=1.6, delta=4):
if isinstance(self.monotonous, bool):
if self.monotonous:
return (self.iou.detach() / self.iou_mean).sqrt()
else:
beta = self.iou.detach() / self.iou_mean
alpha = delta * torch.pow(gamma, beta - delta)
return beta / alpha
return 1
def bbox_iou(box1, box2, xywh=True, GIoU=False, DIoU=False, CIoU=False, SIoU=False, EIoU=False, WIoU=False, Focal=False, alpha=1, gamma=0.5, scale=False, 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
if scale:
self = WIoU_Scale(1 - (inter / union))
# 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 or WIoU:
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 or WIoU: # 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
elif WIoU:
if Focal:
raise RuntimeError("WIoU do not support Focal.")
elif scale:
return getattr(WIoU_Scale, '_scaled_loss')(self), (1 - iou) * torch.exp((rho2 / c2)), iou # WIoU https://arxiv.org/abs/2301.10051
else:
return iou, torch.exp((rho2 / c2)) # WIoU v1
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