IOU
IoU全称Intersection over Union,交并比。
IoU是一种测量在特定数据集中检测相应物体准确度的一个标准。只要是在输出中得出一个预测范围(bounding boxes)的任务都可以用IoU来进行测量。
IoU算法是使用最广泛的算法,大部分的检测算法都是使用的这个算法。在目标识别中,我们的预测框与实际框的某种比值就是IoU。
IoU Loss的定义是先求出预测框和真实框之间的交集和并集之比,再求负对数,但是在实际使用中我们常常将IOU Loss写成1-IOU。如果两个框重合则交并比等于1,Loss为0说明重合度非常高。
I
o
U
=
∣
A
∩
B
∣
∣
A
∪
B
∣
IoU=\frac{\mid{A \cap B}\mid}{\mid{A \cup B}\mid}
IoU=∣A∪B∣∣A∩B∣
L
I
o
U
=
1
−
∣
A
∩
B
∣
∣
A
∪
B
∣
L_{IoU}=1-\frac{\mid{A \cap B}\mid}{\mid{A \cup B}\mid}
LIoU=1−∣A∪B∣∣A∩B∣
IOU的优点:
- IOU具有尺度不变性
- 满足非负性
IOU的缺点: - 如果两个框没有相交,根据定义,IoU=0,不能反映两者的距离大小(重合度)。同时因为loss=0,没有梯度回传,无法进行学习训练。
- 当预测框和真实框的交并比相同,但是预测框所在位置不同,因为计算出来的损失一样,所以这样并不能判断哪种预测框更加准确
# box1和box2的坐标为左上和右下的坐标
# IoU
def IoU(box1, box2):
b1_x1, b1_y1, b1_x2, b1_y2 = box1
b2_x1, b2_y1, b2_x2, b2_y2 = box2
# 用于求∩集
# 左上角坐标选大的
xx1 = np.maximum(b1_x1, b2_x1)
yy1 = np.maximum(b1_y1, b2_y1)
# 右下角坐标选小的,但是右下角坐标比左下角大
xx2 = np.minimum(b1_x2, b2_x2)
yy2 = np.minimum(b1_y2, b2_y2)
w = np.maximum(0.0, xx2 - xx1)
h = np.maximum(0.0, yy2 - yy1)
# 交集的面积
inter = w * h
# 并集为A+B-交集
IoU = inter / ((b1_x2 - b1_x1) * (b1_y2 - b1_y1) + (b2_x2 - b2_x1) * (b2_y2 - b2_y1) - inter)
return IoU
box1 = np.array([100, 100, 210, 210])
box2 = np.array([150, 150, 230, 220])
print(IoU(box1, box2)) #0.2553191489361702
GIOU
GIoU比IoU多了一个‘Generalized’,能在更广义的层面上计算IoU。当检测框和真实框没有出现重叠的时候IoU的loss都是一样的,因此GIoU就引入了最小封闭形状C(C可以把A,B包含在内),在不重叠情况下能让预测框尽可能朝着真实框前进,这样就可以解决检测框和真实框没有重叠的问题 。
G
I
O
U
=
I
O
U
−
∣
C
−
(
A
∪
B
∣
)
∣
C
∣
GIOU=IOU-\frac{\mid{C-(A \cup B}\mid)}{\mid C\mid}
GIOU=IOU−∣C∣∣C−(A∪B∣)
G
I
O
U
=
−
1
+
(
A
∪
B
C
)
i
f
I
o
U
=
0
GIOU=-1+(\frac{A\cup B}{C}) ~~if~~IoU=0
GIOU=−1+(CA∪B) if IoU=0IoU取值[0,1]
,但是GIoU有对称区间,取值范围(-1,1]
。在在两者无交集且无限远的时候无限趋近于最小值-1,两者重合的时候取最大值1, 因此GIoU是一个非常好的距离度量指标。
GIoU不仅可以关注重叠区域,还可以关注其他非重合区域,能比较好的反映两个框在闭包区域中的相交情况。损失函数为
L
G
I
o
U
=
1
−
G
I
o
U
=
1
−
I
o
U
+
∣
C
−
(
A
∪
B
∣
)
∣
C
∣
L_{GIoU}=1-GIoU=1-IoU+\frac{\mid{C-(A \cup B}\mid)}{\mid C\mid}
LGIoU=1−GIoU=1−IoU+∣C∣∣C−(A∪B∣)
GIou的缺点
- 对每个预测框与真实框均要去计算最小外接矩形,计算及收敛速度受到限制
- 在一个框包含另一个框的情况下,这时GIoU将会退化为IoU,不能反映出实际情况
# box1和box2的坐标为左上和右下的坐标
# IoU
def GIoU(box1, box2):
b1_x1, b1_y1, b1_x2, b1_y2 = box1
b2_x1, b2_y1, b2_x2, b2_y2 = box2
# 用于求∩集
# 左上角坐标选大的
xx1 = np.maximum(b1_x1, b2_x1)
yy1 = np.maximum(b1_y1, b2_y1)
# 右下角坐标选小的,但是右下角坐标比左下角大
xx2 = np.minimum(b1_x2, b2_x2)
yy2 = np.minimum(b1_y2, b2_y2)
inter_w = np.maximum(0.0, xx2 - xx1)
inter_h = np.maximum(0.0, yy2 - yy1)
# 交集的面积
inter = inter_h * inter_w
# 并集为A+B面积
union = (b1_x2 - b1_x1) * (b1_y2 - b1_y1) + (b2_x2 - b2_x1) * (b2_y2 - b2_y1) - inter
# 求闭包C
C_xx1 = np.minimum(b1_x1, b2_x1)
C_yy1 = np.minimum(b1_y1, b2_y1)
C_xx2 = np.maximum(b1_x2, b2_x2)
C_yy2 = np.maximum(b1_y2, b2_y2)
C_aera = (C_xx2 - C_xx1) * (C_yy2 - C_yy1)
iou = inter / union
giou = iou - abs((C_aera - union) / C_aera)
return giou
box1 = np.array([100, 100, 210, 210])
box2 = np.array([150, 150, 230, 220])
print(GIoU(box1, box2)) # 0.15916530278232405
DIoU
DIoU考虑到GIoU的缺点,也是增加了C检测框,将真实框和预测框都包含了进来,但是DIoU计算的不是框之间的交并,而是计算的每个检测框(利用中心点)之间的欧氏距离。
DIoU要比GIou更加符合目标框回归的机制,将目标与anchor之间的距离,重叠率以及尺度都考虑进去,使得目标框回归变得更加稳定,不会像IoU和GIoU一样出现训练过程中发散等问题。
D
I
o
U
=
I
o
U
−
ρ
2
(
A
,
B
)
c
2
DIoU=IoU-\frac{\rho^2(A,B)}{c^2}
DIoU=IoU−c2ρ2(A,B)
其中
d
=
ρ
(
A
,
B
)
d=\rho(A,B)
d=ρ(A,B)是A与B框中心坐标的欧式距离,c为包住它们的最小方框的对角线距离。
欧式距离也称欧几里得距离,是最常见的距离度量,衡量的是多维空间中两个点之间的绝对距离。在二维和三维空间中的欧式距离的就是两点之间的距离
二维
:
d
=
(
x
1
−
x
2
)
2
+
(
y
1
−
y
2
)
2
二维: d=\sqrt{(x_1-x_2)^2+(y_1-y_2)^2}
二维:d=(x1−x2)2+(y1−y2)2
三维
:
d
=
(
x
1
−
x
2
)
2
+
(
y
1
−
y
2
)
2
+
(
z
1
−
z
2
)
2
三维: d=\sqrt{(x_1-x_2)^2+(y_1-y_2)^2+(z_1-z_2)^2}
三维:d=(x1−x2)2+(y1−y2)2+(z1−z2)2
n
维:
X
:
(
x
1
,
y
1
,
z
1
,
n
1
,
.
.
.
.
m
1
)
Y
:
(
x
2
,
y
2
,
z
2
,
n
2
,
.
.
.
.
.
,
m
2
)
d
(
X
,
Y
)
=
∑
i
=
1
m
(
X
i
−
Y
i
)
2
\begin{align} n维:X:(x_1,y_1,z_1,n_1,....m_1)\\ Y:(x_2,y_2,z_2,n_2,.....,m_2)\\ d(X,Y)=\sqrt{\sum_{i=1}^m(X_i-Y_i)^2} \end{align}
n维:X:(x1,y1,z1,n1,....m1)Y:(x2,y2,z2,n2,.....,m2)d(X,Y)=i=1∑m(Xi−Yi)2
最后,
L
D
I
O
U
L_{DIOU}
LDIOU的公式为
L
D
I
o
U
=
1
−
D
I
o
U
=
1
−
I
o
U
+
ρ
2
(
A
,
B
)
c
2
L_{DIoU}=1-DIoU=1-IoU+\frac{\rho^2(A,B)}{c^2}
LDIoU=1−DIoU=1−IoU+c2ρ2(A,B)
# box1和box2的坐标为左上和右下的坐标,box1为预测框 box2为真实框(gt)
# IoU
def DIoU(box1, box2):
b1_x1, b1_y1, b1_x2, b1_y2 = box1
b2_x1, b2_y1, b2_x2, b2_y2 = box2
# 用于求∩集
# 左上角坐标选大的
xx1 = np.maximum(b1_x1, b2_x1)
yy1 = np.maximum(b1_y1, b2_y1)
# 右下角坐标选小的,但是右下角坐标比左下角大
xx2 = np.minimum(b1_x2, b2_x2)
yy2 = np.minimum(b1_y2, b2_y2)
inter_w = np.maximum(0.0, xx2 - xx1)
inter_h = np.maximum(0.0, yy2 - yy1)
# 交集的面积
inter = inter_h * inter_w
# 并集为A+B面积
union = (b1_x2 - b1_x1) * (b1_y2 - b1_y1) + (b2_x2 - b2_x1) * (b2_y2 - b2_y1) - inter
# 求闭包C
C_xx1 = np.minimum(b1_x1, b2_x1)
C_yy1 = np.minimum(b1_y1, b2_y1)
C_xx2 = np.maximum(b1_x2, b2_x2)
C_yy2 = np.maximum(b1_y2, b2_y2)
# 闭包的对角线
C_distance = (C_xx2 - C_xx1) ** 2 + (C_yy2 - C_yy1) ** 2
# 求A B的Distance
center_b_x = (b1_x1 + b1_x2) / 2
center_b_y = (b1_y1 + b1_y2) / 2
center_gtb_x = (b2_x1 + b2_x2) / 2
center_gtb_y = (b2_y1 + b2_y2) / 2
center_d = (center_gtb_x - center_b_x) ** 2 + (center_gtb_y - center_b_y) ** 2
iou = inter / union
diou = iou - center_d / C_distance
return diou
box1 = np.array([100, 100, 210, 210])
box2 = np.array([150, 150, 230, 220])
print(DIoU(box1, box2)) # 0.18742777513425327
DIoU的优点
- DIoU的惩罚项是基于中心点的距离和对角线距离的比值,避免了像GIoU在两框距离较远时,产生较大的外包框,Loss值较大难以优化(因为它的惩罚项是A ∪ B比上最小外包框的面积)。所以DIoU Loss收敛速度会比GIoU Loss快。
- 即使在一个框包含另一个框的情况下,c值不变,但d值也可以进行有效度量。
- 与GIoU loss类似,DIoU loss在与目标框不重叠时,仍然可以为边界框提供移动方向。
- DIoU loss可以直接最小化两个目标框的距离,而GIOU loss优化的是两个目标框之间的面积,因此比GIoU loss收敛快得多。
- 对于包含两个框在水平方向和垂直方向上这种情况,DIoU损失可以使回归非常快,而GIoU损失几乎退化为IoU损失
一个好的目标框回归损失应该考虑三个重要的几何因素:重叠面积、中心点距离、长宽比。
DIoU的缺点
没有考虑长宽比
CIoU
DIoU的作者考虑到,在两个框中心点重合时,c与d的值都不变。所以此时需要引入框的宽高比
C
I
o
U
=
I
o
U
−
ρ
2
(
A
c
t
r
,
B
c
t
r
)
c
2
−
α
v
CIoU=IoU-\frac{\rho^2{(A_{ctr},B_{ctr}})}{c^2}-\alpha v
CIoU=IoU−c2ρ2(Actr,Bctr)−αv
α
=
v
(
1
−
I
o
U
)
+
v
\alpha=\frac{v}{(1-IoU)+v}
α=(1−IoU)+vv
v
=
4
π
2
(
a
r
c
t
a
n
w
g
t
h
g
t
−
a
r
c
t
a
n
w
h
)
2
v=\frac{4}{\pi^2}(arctan{\frac{w^{gt}}{h^{gt}}-arctan{\frac w h}})^2
v=π24(arctanhgtwgt−arctanhw)2
公式中,A,B代表两个框,
A
c
t
r
,
B
c
t
r
A_{ctr},B_{ctr}
Actr,Bctr代表A和B的中心点,其中
α
\alpha
α是权重函数,v 用来度量宽高比的一致性。最终的CIoU的损失函数为
L
C
I
o
U
=
1
−
I
o
U
(
A
,
B
)
+
ρ
2
(
A
c
t
r
,
B
c
t
r
)
c
2
+
α
v
L_{CIoU}=1-IoU(A,B)+\frac{\rho^2{(A_{ctr},B_{ctr}})}{c^2}+\alpha v
LCIoU=1−IoU(A,B)+c2ρ2(Actr,Bctr)+αv
所以CIOU的前两部分和DIOU是一致的(这里的LOSS就是1-CIOU)。唯一增加的部分是后面的av,这个就是对长宽比的考量。
# box1和box2的坐标为左上和右下的坐标,box1为预测框 box2为真实框(gt)
# IoU
def CIoU(box1, box2):
b1_x1, b1_y1, b1_x2, b1_y2 = box1
b2_x1, b2_y1, b2_x2, b2_y2 = box2
# 用于求∩集
# 左上角坐标选大的
xx1 = np.maximum(b1_x1, b2_x1)
yy1 = np.maximum(b1_y1, b2_y1)
# 右下角坐标选小的,但是右下角坐标比左下角大
xx2 = np.minimum(b1_x2, b2_x2)
yy2 = np.minimum(b1_y2, b2_y2)
inter_w = np.maximum(0.0, xx2 - xx1)
inter_h = np.maximum(0.0, yy2 - yy1)
# 交集的面积
inter = inter_h * inter_w
# 并集为A+B面积
union = (b1_x2 - b1_x1) * (b1_y2 - b1_y1) + (b2_x2 - b2_x1) * (b2_y2 - b2_y1) - inter
iou = inter / union
# 求闭包C
C_xx1 = np.minimum(b1_x1, b2_x1)
C_yy1 = np.minimum(b1_y1, b2_y1)
C_xx2 = np.maximum(b1_x2, b2_x2)
C_yy2 = np.maximum(b1_y2, b2_y2)
C_distance = (C_xx2 - C_xx1) ** 2 + (C_yy2 - C_yy1) ** 2
# 求A B的Distance
center_b_x = (b1_x1 + b1_x2) / 2
center_b_y = (b1_y1 + b1_y2) / 2
center_gtb_x = (b2_x1 + b2_x2) / 2
center_gtb_y = (b2_y1 + b2_y2) / 2
center_d = (center_gtb_x - center_b_x) ** 2 + (center_gtb_y - center_b_y) ** 2
# 长宽比,计算a和v
pred_w=b1_x2-b1_x1
pred_h=b1_y2-b1_y1
gt_w=b2_x2-b2_x1
gt_h=b2_y2-b2_y1
v=(4/(np.pi)**2)*(np.arctan(gt_w/gt_h)-np.arctan(pred_w/pred_h))**2
alpha=v/(1-iou+v)
ciou = iou - center_d / C_distance-alpha*v
return ciou
box1 = np.array([100, 100, 210, 210])
box2 = np.array([150, 150, 230, 220])
print(CIoU(box1, box2)) # 0.18742345427133264
CIoU的缺点
- 如果预测框和gt框的长宽比是相同的,那么长宽比的惩罚项恒为0,不合理
- 观察CIoU中w, h相对于v的梯度,发现这两个梯度是一对相反数,也就是说,w和h不能同时增大或减小,这显然也不够合理的。
EIoU
EIOU 是在 CIOU 的惩罚项基础上将预测框和真实框的纵横比的影响因子拆开,分别计算预测框和真实框的长和宽,来解决 CIOU 存在的问题。
EIoU包括三个部分:IoU损失、距离损失、高宽损失(重叠面积、中心点举例、高宽比)
高宽损失直接最小化了预测目标边界框和真实边界框的高度和宽度的差异,使其有更快的收敛速度和更好的定位结果
E
I
o
U
=
I
o
U
−
ρ
2
(
b
,
b
g
t
)
c
2
−
ρ
2
(
w
,
w
g
t
)
C
w
2
−
ρ
2
(
h
,
h
g
t
)
C
h
2
EIoU=IoU-\frac{\rho^2(b,b^{gt})}{c^2}-\frac{\rho^2(w,w^{gt})}{C_{w}^2}-\frac{\rho^2(h,h^{gt})}{C_{h}^2}
EIoU=IoU−c2ρ2(b,bgt)−Cw2ρ2(w,wgt)−Ch2ρ2(h,hgt)
损失函数为
E
I
o
U
=
1
−
I
o
U
+
ρ
2
(
b
,
b
g
t
)
c
2
+
ρ
2
(
w
,
w
g
t
)
C
w
2
+
ρ
2
(
h
,
h
g
t
)
C
h
2
EIoU=1-IoU+\frac{\rho^2(b,b^{gt})}{c^2}+\frac{\rho^2(w,w^{gt})}{C_{w}^2}+\frac{\rho^2(h,h^{gt})}{C_{h}^2}
EIoU=1−IoU+c2ρ2(b,bgt)+Cw2ρ2(w,wgt)+Ch2ρ2(h,hgt)
其中,
ρ
2
(
b
,
b
g
t
)
\rho^2(b,b^{gt})
ρ2(b,bgt)为预测框和真实框之间中心坐标的欧氏距离,
c
2
c^2
c2为闭包C的对角线。
C
w
{C_{w}}
Cw和
C
h
C_{h}
Ch为闭包C的宽和高,
ρ
2
(
w
,
w
g
t
)
\rho^2(w,w^{gt})
ρ2(w,wgt)和
ρ
2
(
h
,
h
g
t
)
\rho^2(h,h^{gt})
ρ2(h,hgt)分别为真实框宽、高-预测框的宽、高
的平方。
# box1和box2的坐标为左上和右下的坐标,box1为预测框 box2为真实框(gt)
def EIoU(box1, box2):
b1_x1, b1_y1, b1_x2, b1_y2 = box1
b2_x1, b2_y1, b2_x2, b2_y2 = box2
# 用于求∩集
# 左上角坐标选大的
xx1 = np.maximum(b1_x1, b2_x1)
yy1 = np.maximum(b1_y1, b2_y1)
# 右下角坐标选小的,但是右下角坐标比左下角大
xx2 = np.minimum(b1_x2, b2_x2)
yy2 = np.minimum(b1_y2, b2_y2)
inter_w = np.maximum(0.0, xx2 - xx1)
inter_h = np.maximum(0.0, yy2 - yy1)
# 交集的面积
inter = inter_h * inter_w
# 并集为A+B面积
union = (b1_x2 - b1_x1) * (b1_y2 - b1_y1) + (b2_x2 - b2_x1) * (b2_y2 - b2_y1) - inter
iou = inter / union
# 求闭包C
C_xx1 = np.minimum(b1_x1, b2_x1)
C_yy1 = np.minimum(b1_y1, b2_y1)
C_xx2 = np.maximum(b1_x2, b2_x2)
C_yy2 = np.maximum(b1_y2, b2_y2)
C_distance = (C_xx2 - C_xx1) ** 2 + (C_yy2 - C_yy1) ** 2
# 求A B的Distance
center_b_x = (b1_x1 + b1_x2) / 2
center_b_y = (b1_y1 + b1_y2) / 2
center_gtb_x = (b2_x1 + b2_x2) / 2
center_gtb_y = (b2_y1 + b2_y2) / 2
center_d = (center_gtb_x - center_b_x) ** 2 + (center_gtb_y - center_b_y) ** 2
# 长宽比,计算a和v
pred_w = b1_x2 - b1_x1
pred_h = b1_y2 - b1_y1
gt_w = b2_x2 - b2_x1
gt_h = b2_y2 - b2_y1
roh_w = (gt_w - pred_w) ** 2
roh_h = (gt_h - pred_h) ** 2
C_w = (C_xx2 - C_xx1) ** 2
C_h = (C_yy2 - C_yy1) ** 2
eiou = iou - center_d / C_distance - roh_w / C_w - roh_h / C_h
return eiou
box1 = np.array([100, 100, 210, 210])
box2 = np.array([150, 150, 230, 220])
print(EIoU(box1, box2)) # 0.02306222615331968
统一实现
import numpy as np
import torch
import math
def bbox_iou(box1, box2, x1y1x2y2=True, GIoU=False, DIoU=False, CIoU=False, EIoU=False, eps=1e-7):
# Returns the IoU of box1 to box2. box1 is 4, box2 is nx4
# box2 = box2.T
# Get the coordinates of bounding boxes if x1y1x2y2: # x1, y1, x2, y2 = box1
b1_x1, b1_y1, b1_x2, b1_y2 = box1[0], box1[1], box1[2], box1[3]
b2_x1, b2_y1, b2_x2, b2_y2 = box2[0], box2[1], box2[2], box2[3]
else: # transform from xywh to xyxy
b1_x1, b1_x2 = box1[0] - box1[2] / 2, box1[0] + box1[2] / 2
b1_y1, b1_y2 = box1[1] - box1[3] / 2, box1[1] + box1[3] / 2
b2_x1, b2_x2 = box2[0] - box2[2] / 2, box2[0] + box2[2] / 2
b2_y1, b2_y2 = box2[1] - box2[3] / 2, box2[1] + box2[3] / 2
# Intersection area
inter = (torch.min(b1_x2, b2_x2) - torch.max(b1_x1, b2_x1)).clamp(0) * \
(torch.min(b1_y2, b2_y2) - torch.max(b1_y1, b2_y1)).clamp(0)
# Union Area
w1, h1 = b1_x2 - b1_x1, b1_y2 - b1_y1 + eps
w2, h2 = b2_x2 - b2_x1, b2_y2 - b2_y1 + eps
union = w1 * h1 + w2 * h2 - inter + eps
iou = inter / union
if GIoU or DIoU or CIoU or EIoU:
cw = torch.max(b1_x2, b2_x2) - torch.min(b1_x1, b2_x1) # convex (smallest enclosing box) width
ch = torch.max(b1_y2, b2_y2) - torch.min(b1_y1, 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 distance squared
if DIoU:
return iou - rho2 / c2 # DIoU
elif CIoU: # https://github.com/Zzh-tju/DIoU-SSD-pytorch/blob/master/utils/box/box_utils.py#L47
v = (4 / math.pi ** 2) * torch.pow(torch.atan(w2 / h2) - torch.atan(w1 / h1), 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)
else: # GIoU https://arxiv.org/pdf/1902.09630.pdf
c_area = cw * ch + eps # convex area
return iou - (c_area - union) / c_area # GIoU
else:
return iou # IoU
box1 = torch.tensor(np.array([100, 100, 210, 210]))
box2 = torch.tensor(np.array([150, 150, 230, 220]))
print(bbox_iou(box1, box2, EIoU=True)) # 0.02306222615331968