IOU 的计算
1. box_iou的计算
仅是计算两组box的交并比。
def box_iou(box1, box2, eps=1e-7):
# https://github.com/pytorch/vision/blob/master/torchvision/ops/boxes.py
"""
Return intersection-over-union (Jaccard index) of boxes.
Both sets of boxes are expected to be in (x1, y1, x2, y2) format.
Arguments:
box1 (Tensor[N, 4])
box2 (Tensor[M, 4])
Returns:
iou (Tensor[N, M]): the NxM matrix containing the pairwise
IoU values for every element in boxes1 and boxes2
"""
# inter(N,M) = (rb(N,M,2) - lt(N,M,2)).clamp(0).prod(2)
#box1.unsqueeze(1).chunk(2, 2) 将box1的维度由[N,4]转成[N,1,4],然后分成2个tensor,尺寸分别为[N,1,2]
#box2.unsqueeze(0).chunk(2, 2) 将box2的维度由[N,4]转成[1,N,4],然后分成2个tensor,尺寸分别为[1,M,2]
# 其是就是将x1,y1分给a1和b1, x2,y2分给a2和b2
(a1, a2), (b1, b2) = box1.unsqueeze(1).chunk(2, 2), box2.unsqueeze(0).chunk(2, 2)
#通过torch.min实现维度扩充,并在对应位置取最小值,再做差,变成[N,M,2]
#通过clamp(0)对tensor中的元素进行filter,小于0者取0。
#prod(2)是 将dim=3的维度的两个元素代表对应的长和宽,再对应相乘,得到交叉面积。
inter = (torch.min(a2, b2) - torch.max(a1, b1)).clamp(0).prod(2)
# IoU = inter / (area1 + area2 - inter)
return inter / ((a2 - a1).prod(2) + (b2 - b1).prod(2) - inter + eps)
2. bbox_iou的计算
支持目前有的GIoU、DIoU、CIoU 等
理论知识:
常见的IOU分为以下几种:
(1) 最初的IOU:
计算方式如上述代码,
IOU值越高说明A框和B框的重合度也越高,代表预测的越准确,反之预测效果不好。
但是这种计算方式存在一定的缺陷:
如果两个目标没有重叠,IoU将会为0,并且不会反应两个目标之间的距离,在这种无重叠目标的情况下,如果IoU用作于损失函数,梯度为0,无法优 化。
IoU无法精确的反映两者的重合度大小。但看得出来他们的重合度是不一样的。有时候IOU值相等,但是重合度不一样。
(2) GIOU(Generalized IoU)
为了解决IoU作为损失函数时的两个缺点,有大神提出了GIoU,在IoU后面增加了一项,计算两个框的最小外接矩形,用于表征两个框的距离,从而解 决了两个目标没有交集时梯度为零的问题,公式为:
$$
GIOU = IOU - \frac{C-A \bigcup B}{C}
$$
其中C是两个框的最小外接矩形的面积。
当IOU=0时:
$$
GIOU = -1 + \frac {A \bigcup B}{C}
$$
当IOU为0时,意味着A与B没有交集,这个时候两个框离得越远,GIOU越接近-1;两框重合,GIOU=1,所以GIOU的取值为(-1, 1]。
所以GIOU作为loss函数时,可以表达为:
$$
L_G = 1- GIOU
$$
当A、B两框不相交时,
$$
A \bigcup B
$$
不变,最大化GIoU就是最小化C,这样就会促使两个框不断靠近。
优点:
当IoU=0时,仍然可以很好的表示两个框的距离。 GIoU不仅关注重叠区域,还关注其他的非重合区域,能更好的反映两者的重合度。
缺点:
当两个框属于包含关系时,GIoU会退化成IoU,无法区分其相对位置关系。
由于GIoU仍然严重依赖IoU,因此在两个垂直方向,误差很大,很难收敛。两个框在相同距离的情况下,水平垂直方向时,此部分面积最小,对loss的贡献也就越小,从而导致在垂直水平方向上回归效果较差。 没太理解
(3) DIoU(Distance-IoU)
针对上述GIoU的两个问题,有大神将GIoU中最小外接框来最大化重叠面积的惩罚项修改成最小化两个BBox中心点的标准化距离从而加速损失的收敛过程,这就诞生了DIoU。
DIoU要比GIou更加符合目标框回归的机制,将目标与预测之间的距离,重叠率以及尺度都考虑进去,使得目标框回归变得更加稳定,不会像IoU和GIoU一样出现训练过程中发散等问题。
$$
DIOU = IOU - \frac {\rho^2(b,b^{gt})}{c^2}
$$
其中b和bgt分别代表预测框和真实框的中心点,且p代表两个中心点间的欧式距离,c 代表的是能够同时包含预测框和真实框的最小外接矩形的对角线长度。
优点:
(1)DIoU loss可以直接最小化两个目标框的距离,因此比GIoU loss收敛快得多。
(2)对于包含两个框在水平方向和垂直方向上这种情况,DIoU损失可以使回归非常快。
(3)DIoU还可以替换普通的IoU评价策略,应用于NMS中,使得NMS得到的结果更加合理和有效。
缺点:
虽然DIOU能够直接最小化预测框和真实框的中心点距离加速收敛,但是Bounding box的回归还有一个重要的因素纵横比暂未考虑。如下图,三个红框的面积相同,但是长宽比不一样,红框与绿框中心点重合,这时三种情况的DIoU相同,证明DIoU不能很好的区分这种情况。
(4) CIoU(Complete-IoU)
CIoU与DIoU出自同一篇论文,CIoU大多数用于训练。DIoU的作者考虑到,在两个框中心点重合时,c与d的值都不变。所以此时需要引入框的宽高比:
$$
CIoU = IoU - (\frac{\rho^2(b,b^{gt})}{c^2} + \alpha\upsilon )
$$
其中α是权重函数,v用来度量宽高比的一致性:
$$
\alpha = \frac{\upsilon}{(1-IoU) + \upsilon}
$$
$$
\upsilon = \frac{4}{\pi^2}(\arctan \frac{\omega_{gt}}{h_{gt}} - \arctan (\frac{\omega}{h}))^2
$$
最终CIoU Loss定义为:
$$
L_{CIoU} = 1 - IoU + \frac {\rho^2(b, b^{gt})}{c^2} + \alpha\upsilon
$$
优点:
考虑了框的纵横比,可以解决DIoU的问题。
缺点:
通过CIoU公式中的v反映的纵横比的差异,而不是宽高分别与其置信度的真实差异,所以有时会阻碍模型有效的优化相似性。
(5) EIoU(Efficient-IoU)
论文地址:https://arxiv.org/pdf/2101.08158.pdf
为了解决CIoU的问题,有学者在CIOU的基础上将纵横比拆开,提出了EIOU Loss,并且加入Focal聚焦优质的预测框,与CIoU相似的,EIoU是损失函数的解决方案,只用于训练。
EIOU的惩罚项是在CIOU的惩罚项基础上将纵横比的影响因子拆开分别计算目标框和预测框的长和宽,该损失函数包含三个部分:重叠损失,中心距离损失,宽高损失,前两部分延续CIoU中的方法,但是宽高损失直接使目标框与预测框的宽度和高度之差最小,使得收敛速度更快。惩罚项公式如下:
$$
L_{EIoU} = L_{IoU} + L_{dic} + L_{asp} = 1 - IoU + \frac{\rho^2(b, b^{gt})}{(c_w)^2 + (c_h)^2} + \frac {\rho^2(w, w^{gt})}{(c_w)^2} + \frac{ \rho^2(h, h^{gt})}{(c_h)^2}
$$
其中
$$
c_w 和c_h
$$
是覆盖两个Box的最小外接框的宽度和高度。
优点:
1)将纵横比的损失项拆分成预测的宽高分别与最小外接框宽高的差值,加速了收敛提高了回归精度。
代码实现:
import torch
import math
import numpy as np
def bbox_iou(box1, box2, xywh=False, giou=False, diou=False, ciou=False,eiou=False,MDPIoU=False, eps=1e-7):
"""
实现各种IoU
Parameters
----------
box1 shape(b, c, h, w,4)
box2 shape(b, c, h, w,4)
xywh 是否使用中心点和wh,如果是False,输入就是左上右下四个坐标
GIoU 是否GIoU
DIoU 是否DIoU
CIoU 是否CIoU
EIoU 是否EIoU
eps 防止除零的小量
Returns
-------
"""
# 获取边界框的坐标
# 判断输入的box数据格式,如果为xywh,则将其转换为xyxy格式
if xywh:
# xywh转为xyxy,对于box1和box2分别进行操作
# box1和box2是包含四个元素的张量,这四个元素分别是x, y, w, h
# 这里的xy1和xy2是将原始的box1和box2的坐标中心点计算出来,然后根据新的宽度和高度(原宽度和高度的算术平均值)计算出新的坐标范围
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
else:
# 如果输入的box数据格式不是xywh,则直接使用输入的box数据(x1, y1, x2, y2)
# box1和box2是包含四个元素的张量,这四个元素分别是x1, y1, x2, y2
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]
# 计算两个box的交集面积
# 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)
inter = torch.min(b1_x2, b2_x2) - torch.max(b1_x1, b2_x1) # 行交集
inter = torch.min(inter, torch.zeros_like(inter)) # 对可能出现的负值使用零进行替换
inter = inter.clamp(0) # 对可能出现的负值使用零进行替换
inter = inter * torch.min(b1_y2, b2_y2) - torch.max(b1_y1, b2_y1) # 列交集
inter = torch.min(inter, torch.zeros_like(inter)) # 对可能出现的负值使用零进行替换
inter = inter.clamp(0) # 对可能出现的负值使用零进行替换
# 计算两个box的并集面积(不考虑重叠部分)
w1, h1 = b1_x2 - b1_x1, b1_y2 - b1_y1 + eps # box1的宽和高
w2, h2 = b2_x2 - b2_x1, b2_y2 - b2_y1 + eps # box2的宽和高
union = w1 * h1 + w2 * h2 - inter + eps # 并集面积,需要减去交集部分以避免重复计算
# 计算IOU(Intersection over Union)值,即两个box的交集面积除以两个box的并集面积
iou = inter / union # IOU值
# 判断是否需要计算GIoU、DIoU、CIoU、EIoU中的至少一种
if giou or diou or ciou or eiou:
# 计算两个box的最小外接矩形的宽度和高度
# 矩形的左上和右下坐标分别为(b1_x1, b1_y1)和(b1_x2, b1_y2),(b2_x1, b2_y1)和(b2_x2, b2_y2)
cw = torch.max(b1_x2, b2_x2) - torch.min(b1_x1, b2_x1)
ch = torch.max(b1_y2, b2_y2) - torch.min(b1_y1, b2_y1)
# 如果需要计算CIoU、DIoU、EIoU中的至少一种
if ciou or diou or eiou:
# 计算最小外接矩形角线的平方
c2 = cw ** 2 + ch ** 2 + eps # eps是防止除数为0的微小值
# 计算最小外接矩形中点距离的平方
rho2 = ((b2_x1 + b2_x2 - b1_x1 - b1_x2) ** 2 +
(b2_y1 + b2_y2 - b1_y1 - b1_y2) ** 2) / 4
# 如果需要计算DIoU
if diou:
# 输出DIoU
return iou - rho2 / c2
# 如果需要计算CIoU
elif ciou:
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)) # eps是防止除数为0的微小值
# 输出CIoU
return iou - (rho2 / c2 + v * alpha)
# 如果需要计算EIoU
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 # eps是防止除数为0的微小值
ch2 = ch ** 2 + eps # eps是防止除数为0的微小值
# 输出EIoU
return iou - (rho2 / c2 + rho_w2 / cw2 + rho_h2 / ch2)
else:
# 计算凸包面积(最小外接矩形的面积)
c_area = cw * ch + eps # eps是防止除数为0的微小值
# 输出GIoU,GIoU是GIoU、CIoU、DIoU、EIoU之外的IoU变种,它的计算方式和IoU相同,只是最后用凸包面积代替了交集面积
return iou - (c_area - union) / c_area
elif MDPIoU:
d1 = (b2_x1 - b1_x1) ** 2 + (b2_y1 - b1_y1) ** 2
d2 = (b2_x2 - b1_x2) ** 2 + (b2_y2 - b1_y2) ** 2
mpdiou_hw_pow = feat_h ** 2 + feat_w ** 2
return iou - d1 / mpdiou_hw_pow - d2 / mpdiou_hw_pow # MPDIoU
else:
# 如果不需要计算任何IoU的变种,则直接输出原始的IoU值
# 这里的iou应该是在函数外部定义的变量,表示两个box的交集面积除以两个box的并集面积
return iou
参考: