赞
踩
目录
一 回归损失函数(Bounding Box Regression Loss)
② 使用结合Focaler-IoU的各种损失函数改进YOLOv9
官方论文地址:https://arxiv.org/pdf/2311.02877.pdf
官方代码地址:GitCode - 开发者的代码家园
论文中分析了边界框的回归过程,指出了IoU损失的局限性,它对不同的检测任务没有很强的泛化能力。基于边界框回归问题的固有特点,提出了一种基于辅助边界框的边界框回归损失Inner-IoU。通过比例因子比率(scale factor ratio)控制辅助边界框的生成,计算损失,加速训练的收敛。它可以集成到现有的基于IoU的损失函数中。通过一系列的模拟和烧蚀消融实验验证,该方法优于现有方法。本文提出的方法不仅适用于一般的检测任务,而且对于非常小目标的检测任务也表现良好,证实了该方法的泛化性。
官方的代码给出了2种结合方式,文件如下图:
Inner-IoU的描述见下图:
Inner-IoU的实验效果
CIoU 方法, Inner-CIoU (ratio=0.7), Inner-CIoU (ratio=0.75) and Inner-CIoU (ratio=0.8)的检测效果如下图所示:
SIoU 方法, Inner-SIoU (ratio=0.7), Inner-SIoU (ratio=0.75) and Inner-SIoU (ratio=0.8)的检测效果如下图所示:
官方论文地址:https://arxiv.org/pdf/2401.10525.pdf
官方代码地址:https://github.com/malagoutou/Focaler-IoU
论文中分析了难易样本的分布对目标检测的影响。当困难样品占主导地位时,需要关注困难样品以提高检测性能。当简单样本的比例较大时,则相反。论文中提出了Focaler-IoU方法,通过线性区间映射重建原始IoU损失,达到聚焦难易样本的目的。最后通过对比实验证明,该方法能够有效提高检测性能。
为了在不同的回归样本中关注不同的检测任务,使用线性间隔映射方法重构IoU损失,这有助于提高边缘回归。具体的公式如下所示:
将Focaler-IoU应用于现有的基于IoU的边界框回归损失函数中,如下所示:
实验结果如下:
GIoU、DIoU、CIoU、EIoU和MPDIou等的概述见使用MPDIou回归损失函数帮助YOLOv9模型更优秀 点击此处即可跳转
首先,我们现将后续会使用到的损失函数集成到项目中。在utils/metrics.py文件中,使用下述代码(替换后的部分)替换掉bbox_iou()函数,如下图所示:
原始代码部分和替换后的部分对比如下:
原始代码部分如下:
- def bbox_iou(box1, box2, xywh=True, GIoU=False, DIoU=False, CIoU=False, MDPIoU=False, feat_h=640, feat_w=640, 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 + eps
- w2, h2 = b2_x2 - b2_x1, b2_y2 - b2_y1 + eps
-
- # 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
- union = w1 * h1 + w2 * h2 - inter + eps
-
- # IoU
- iou = inter / union
- if CIoU or DIoU or GIoU:
- 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: # 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 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.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
- return iou - rho2 / c2 # DIoU
- c_area = cw * ch + eps # convex area
- return iou - (c_area - union) / c_area # GIoU https://arxiv.org/pdf/1902.09630.pdf
- 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
- return iou # IoU
替换后的部分如下:
- # after
- def xyxy2xywh(x):
- """
- Convert bounding box coordinates from (x1, y1, x2, y2) format to (x, y, width, height) format where (x1, y1) is the
- top-left corner and (x2, y2) is the bottom-right corner.
- Args:
- x (np.ndarray | torch.Tensor): The input bounding box coordinates in (x1, y1, x2, y2) format.
- Returns:
- y (np.ndarray | torch.Tensor): The bounding box coordinates in (x, y, width, height) format.
- """
- assert x.shape[-1] == 4, f"input shape last dimension expected 4 but input shape is {x.shape}"
- y = torch.empty_like(x) if isinstance(x, torch.Tensor) else np.empty_like(x) # faster than clone/copy
- y[..., 0] = (x[..., 0] + x[..., 2]) / 2 # x center
- y[..., 1] = (x[..., 1] + x[..., 3]) / 2 # y center
- y[..., 2] = x[..., 2] - x[..., 0] # width
- y[..., 3] = x[..., 3] - x[..., 1] # height
- return y
-
-
- def bbox_iou(box1, box2, xywh=True, GIoU=False, DIoU=False, CIoU=False, MDPIoU=False, Inner=False, Focaleriou=False,
- ratio=0.7, feat_h=640,
- feat_w=640, eps=1e-7, d=0.00, u=0.95, ):
- # 计算box1(1,4)与box2(n,4)之间的Intersection over Union(IoU)
- # 获取bounding box的坐标
- if Inner:
- if not xywh:
- box1, box2 = xyxy2xywh(box1), xyxy2xywh(box2)
- (x1, y1, w1, h1), (x2, y2, w2, h2) = box1.chunk(4, -1), box2.chunk(4, -1)
- b1_x1, b1_x2, b1_y1, b1_y2 = x1 - (w1 * ratio) / 2, x1 + (w1 * ratio) / 2, y1 - (h1 * ratio) / 2, y1 + (
- h1 * ratio) / 2
- b2_x1, b2_x2, b2_y1, b2_y2 = x2 - (w2 * ratio) / 2, x2 + (w2 * ratio) / 2, y2 - (h2 * ratio) / 2, y2 + (
- h2 * ratio) / 2
-
- # 计算交集
- 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 = w1 * h1 * ratio * ratio + w2 * h2 * ratio * ratio - inter + eps
- else:
- if xywh: # 从xywh转换为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 + eps
- w2, h2 = b2_x2 - b2_x1, b2_y2 - b2_y1 + eps
-
- # 交集
- 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 = w1 * h1 + w2 * h2 - inter + eps
-
- # IoU
- iou = inter / union
- if Focaleriou:
- iou = ((iou - d) / (u - d)).clamp(0, 1) # default d=0.00,u=0.95
- if CIoU or DIoU or GIoU:
- 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) # 计算最小外接矩形的高度
- if CIoU or DIoU:
- c2 = cw ** 2 + ch ** 2 + eps # 最小外接矩形对角线的平方
- rho2 = ((b2_x1 + b2_x2 - b1_x1 - b1_x2) ** 2 + (b2_y1 + b2_y2 - b1_y1 - b1_y2) ** 2) / 4 # 中心点距离的平方
- if 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))
- return iou - (rho2 / c2 + v * alpha) # CIoU
- return iou - rho2 / c2 # DIoU
- c_area = cw * ch + eps # convex area
- return iou - (c_area - union) / c_area # GIoU
- 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
- return iou # 返回计算的IoU值
需要在utils/loss_tal_dual.py文件中进行修改。因为将MPDIou也集成在项目里了,所这个文件的修改有四处处,如下面的两个图所示。
各种损失函数的开启方式均需要在utils/loss_tal_dual.py文件中进行修改。因为将MPDIou也集成在项目里了,所这个文件的修改有两处。
utils/loss_tal_dual.py文件中进行的修改如下图:
接下来,就可以开始训练了!!!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。