IoU 的全称为交并比(Intersection over Union),其计算是 “预测的边框” 和 “真实的边框” 的交集和并集的比值。计算过程可以由下图表示:
由图可知IoU的值域为[0, 1]。
IoU的优点:
1、IOU可以作为损失函数,IoU loss=1-IOU。但是当两个物体不相交时无回传梯度。
2、 IOU对尺度变化具有不变性,即不受两个物体尺度大小的影响。
IoU的缺点:
1、当预测框和真实框不相交时,IoU(A,B)=0时,不能反映A,B距离的远近,此时损失函数不可导,IoU Loss 无法优化两个框不相交的情况。
2、假设预测框和真实框的大小都确定,只要两个框的相交值是确定的,其IoU值是相同时,IoU值不能反映两个框是如何相交的。
GIoU是为克服IoU的缺点同时充分利用优点而提出的,GIoU计算如下:
上图C是包含A与B的最小框。
GIoU的值域为(-1, 1]。
GIoU的优点:
1、GIoU和IoU一样,可以作为损失函数,GIoU Loss = 1 - GIoU。
2、GIoU能够更好地反应相交情况。如下图,虽然两种情况下IOU一致,但是(a)中相交的更为整齐,因此GIOU要比(b)中大。
GIoU缺点
当目标框完全包裹预测框的时候,IoU和GIoU的值都一样,此时GIoU退化为IoU, 无法区分其相对位置关系。
基于GIoU的缺点,DIoU被提出。其论文为:Distance-IoU Loss: Faster and Better Learning for Bounding Box Regression
论文给出DIoU Loss为:
上图中绿色框为目标框,黑色框为预测框,灰色框为两者的最小外界矩形框,d表示目标框和真实框的中心点距离,c表示最小外界矩形框的距离。
DIoU的性质:
1、尺度不变性。
2、DIoU Loss可以直接优化2个框直接的距离,比GIoU Loss收敛速度更快。
3、对于目标框包裹预测框的这种情况,DIoU Loss可以收敛的很快,而GIoU Loss此时退化为IoU Loss收敛速度较慢
一个好的目标框回归损失应该考虑三个重要的几何因素:重叠面积、中心点距离、长宽比。
GIoU:为了归一化坐标尺度,利用IoU,并初步解决IoU为零的情况。
DIoU:DIoU损失同时考虑了边界框的重叠面积和中心点距离。
然而,anchor框和目标框之间的长宽比的一致性也是极其重要的。基于此,论文作者提出了Complete IoU Loss。
CIoU Loss又引入一个box长宽比的惩罚项,该Loss考虑了box的长宽比,定义如下:
上述损失函数中,CIoU比DIoU多出了α和v这两个参数。其中α是用于平衡比例的参数。v用来衡量anchor框和目标框之间的比例一致性。
从α参数的定义可以看出,损失函数会更加倾向于往重叠区域增多方向优化,尤其是IoU为零的时候。
这里是实现IoU、GIoU、DIoU、CIoU并非实现其Loss。代码如下:
定义框BBox类:
import math
def euclidean_distance(p1, p2):
'''
计算两个点的欧式距离
'''
x1, y1 = p1
x2, y2 = p2
return math.sqrt((x2-x1)**2 + (y2-y1)**2)
class BBox:
def __init__(self, x, y, r, b):
'''
定义框,左上角及右下角坐标
'''
self.x, self.y, self.r, self.b = x, y, r, b
def __xor__(self, other):
'''
计算box和other的IoU
'''
cross = self & other
union = self | other
return cross / (union + 1e-6)
def __or__(self, other):
'''
计算box和other的并集
'''
cross = self & other
union = self.area + other.area - cross
return union
def __and__(self, other):
'''
计算box和other的交集
'''
xmax = min(self.r, other.r)
ymax = min(self.b, other.b)
xmin = max(self.x, other.x)
ymin = max(self.y, other.y)
cross_box = BBox(xmin, ymin, xmax, ymax)
if cross_box.width <= 0 or cross_box.height <= 0:
return 0
return cross_box.area
def boundof(self, other):
'''
计算box和other的边缘外包框,使得2个box都在框内的最小矩形
'''
xmin = min(self.x, other.x)
ymin = min(self.y, other.y)
xmax = max(self.r, other.r)
ymax = max(self.b, other.b)
return BBox(xmin, ymin, xmax, ymax)
def center_distance(self, other):
'''
计算两个box的中心点距离
'''
return euclidean_distance(self.center, other.center)
def bound_diagonal_distance(self, other):
'''
计算两个box的bound的对角线距离
'''
bound = self.boundof(other)
return euclidean_distance((bound.x, bound.y), (bound.r, bound.b))
@property
def center(self):
return (self.x + self.r) / 2, (self.y + self.b) / 2
@property
def area(self):
return self.width * self.height
@property
def width(self):
return self.r - self.x #+ 1
@property
def height(self):
return self.b - self.y #+ 1
定义IoU
def IoU(a, b):
return a ^ b
def GIoU(a, b):
bound_area = a.boundof(b).area
union_area = a | b
return IoU(a, b) - (bound_area - union_area) / bound_area
def DIoU(a, b):
d = a.center_distance(b)
c = a.bound_diagonal_distance(b)
return IoU(a, b) - (d ** 2) / (c ** 2)
def CIoU(a, b):
v = 4 / (math.pi ** 2) * (math.atan(a.width / a.height) - math.atan(b.width / b.height)) ** 2
iou = IoU(a, b)
alpha = v / (1 - iou + v)
return DIoU(a, b) - alpha * v
代码测试结果:
a = BBox(5, 5, 10, 10)
b = BBox(100, 100, 105, 105)
print("IoU:", IoU(a, b), "\nGIoU:", GIoU(a, b), "\nDIoU:", DIoU(a, b), "\nCIoU:", CIoU(a, b))
最终结果:
IoU: 0.0
GIoU: -0.995
DIoU: -0.9025
CIoU: -0.9025