摘要
IoU(Intersection over Union)损失是目标检测中广泛使用的边界框回归(BBR)方法。然而,现有的 IoU 损失在不同检测任务中存在泛化性较弱的问题,难以适应不同检测器和任务。为此,作者分析了 BBR 中的模式,并提出了一种新的损失函数——Inner-IoU。该方法通过引入辅助边界框加快高 IoU 样本的收敛,从而提高了训练的效率和泛化性。实验表明,在各种检测任务中,Inner-IoU 损失可以提高边界框回归的性能,并且在小目标检测任务中表现出色。感谢剁椒狗头作者提供贡献,文章导读在这里 Inner-IoU:基于辅助边框的IoU损失。YOLOv8 自带的边界框损失有 CIoU、DIoU、GIoU,下文将新增 SIoU ,MDPIoU 边界框损失函数,并结合 Inner-IoU 形成 Inner-SIoU,Inner-GIoU,Inner-DIoU,Inner-CIoU,Inner-MDPIoU,也顺便把 SIoU ,MDPIoU 理论介绍一下。
Inner-IoU介绍
Inner-IoU引入辅助边界框,通过缩放因子生成不同大小的辅助边界框计算损失。小比例的辅助边界框适用于高 IoU 样本,有助于加快收敛,而大比例的辅助边界框适用于低 IoU 样本。 Inner-IoU 流程如图所示(图摘自论文):
作者通过将 ratio 值设置 0.7 到 0.8 之间小于 1,产生小于实际边框的辅助边框。实验结果证明其能够对高 IoU 样本产生增益。在实验中 ratio 值大于 1,通过生成较大得辅助边框达到对低 IoU 样本加速收敛的效果,检测效果对比图如下所示(摘自论文):
理论详解可以参考链接:论文地址
代码可在这个链接找到:代码地址
SIoU介绍
对象检测是计算机视觉任务中的核心问题之一,其效果在很大程度上取决于损失函数的定义——衡量机器学习模型预测结果准确性的方法。传统的对象检测损失函数依赖于边界框回归的度量集合,如预测框与真实框之间的距离、重叠区域和纵横比(例如 GIoU、CIoU 等)。然而,迄今为止提出和使用的方法都没有考虑预测框与目标框之间偏差的方向性。这种缺陷导致了训练收敛速度较慢且效果较差,因为在训练过程中,预测框可能会“徘徊”,最终生成较差的模型。作者提出了一种新的损失函数 SIoU,该函数在定义惩罚度量时考虑了回归偏差的角度。SIoU核心思想:
-
角度损失:
SIoU 在传统的 IoU 损失中增加了角度意识组件,以减少训练过程中变量的数量。模型会尝试首先将预测框移至最近的 X 或 Y 轴,然后沿着相关轴继续调整。这使得收敛速度更快,因为减少了自由度(从两个坐标减为一个)。角度损失的目标是优化预测框与目标框的相对方向,减少训练时预测框在空间中的“游走”。 -
距离损失:
结合角度损失,重新定义了距离损失,以确保模型在考虑角度的同时缩短中心距离。当角度接近零时,距离损失的贡献显著减少;当角度增加时,距离损失的贡献增大。 -
形状损失:
控制预测框和真实框之间的形状匹配。形状损失根据不同的数据集定义一个参数,用来决定对形状匹配的关注程度。这个参数通过实验和遗传算法进行调整,通常在 2 到 6 的范围内。 -
IoU损失:
保留了传统 IoU 的计算方式,但与其他损失函数结合,总损失函数更加稳定和高效。SIOU如下图所示:
理论详解可以参考链接:论文地址
代码可在这个链接找到:SIoU相关代码(来源美团yolov6)
MDPIoU介绍
边界框回归 (BBR) 已广泛应用于对象检测和实例分割,是对象定位的重要步骤。但是,当预测框与真实框具有相同的纵横比,但宽度和高度值完全不同时,大多数现有的边界框回归损失函数无法优化。为了解决上述问题,我们充分探索了水平矩形的几何特征,提出了一种新的基于最小点距离的边界框相似性比较度量MPDIoU,它包含了现有损失函数中考虑的所有相关因素,即重叠或不重叠的面积、中心点距离和宽度高度的偏差。 同时简化计算过程。在此基础上,作者提出了一种基于 MPDIoU 的边界框回归损失函数,称为 LMPDIoU 。MPDIoU 通过减少预测框和真实框的左上角和右下角点之间的最小距离来提高边界框回归的精度和效率。其核心思想如下:
- MPDIoU 基于预测框和真实框的左上角和右下角点的欧几里得距离,来惩罚预测框和真实框的偏移。这与传统的 IoU 方法不同,它考虑了框与框之间的几何对齐性。
- MPDIoU 在计算 IoU 的基础上,减去左上角点和右下角点的归一化距离。
MPDIoU 计算流程如下图所示(摘自论文):
理论详解可以参考链接:论文地址
代码可在这个链接找到:MDPIoU相关代码(来源yolov9)
下文都是手把手教程,跟着操作即可添加成功
目录
🎓一、YOLOv8原始版本代码下载
官网的源码下载地址 : YOLOv8官网代码
官网打不开的话,从我的网盘下载就行,网盘下载地址: YOLOv8原始版本源码下载,提取码: rpe7
注意注意注意:如果在我之前的文章下载过 YOLOv8 源码,不用重新下载了,没有特殊说明都是用同一个版本的源码
🍀🍀1.yolov8模型结构图
根据 yolov8n.yaml 画出 yolo 整体结构图,如下图所示
🍀🍀2.环境配置
环境配置参考教程链接:链接: 环境配置链接,如果已经配置好环境可以忽略此步骤
🎓二、Inner-IoU核心代码
def inner_iou(box1, box2, xywh=True, eps=1e-7, ratio=0.7):
if not xywh:
box1, box2 = ops.xyxy2xywh(box1), ops.xyxy2xywh(box2)
(x1, y1, w1, h1), (x2, y2, w2, h2) = box1.chunk(4, -1), box2.chunk(4, -1)
inner_b1_x1, inner_b1_x2, inner_b1_y1, inner_b1_y2 = x1 - (w1 * ratio) / 2, x1 + (w1 * ratio) / 2, y1 - (h1 * ratio) / 2, y1 + (h1 * ratio) / 2
inner_b2_x1, inner_b2_x2, inner_b2_y1, inner_b2_y2 = x2 - (w2 * ratio) / 2, x2 + (w2 * ratio) / 2, y2 - (h2 * ratio) / 2, y2 + (h2 * ratio) / 2
# Inner-IoU
inter = (inner_b1_x2.minimum(inner_b2_x2) - inner_b1_x1.maximum(inner_b2_x1)).clamp_(0) * \
(inner_b1_y2.minimum(inner_b2_y2) - inner_b1_y1.maximum(inner_b2_y1)).clamp_(0)
inner_union = w1 * h1 * ratio * ratio + w2 * h2 * ratio * ratio - inter + eps
return inter / inner_union
🎓三、添加方法
🍀🍀1.在metrics.py文件添加第二章的代码
(1).目录为:ultralytics/utils/metrics.py,操作如下:
ctrl+f 快捷键搜索 bbox_iou 方法,原始的 bbox_iou 代码如下:
def bbox_iou(box1, box2, xywh=True, GIoU=False, DIoU=False, CIoU=False, eps=1e-7):
# 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 = (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
# IoU
iou = inter / union
if CIoU or DIoU or GIoU:
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: # 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.atan(w2 / h2) - torch.atan(w1 / h1)).pow(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
return iou # IoU
之后将第二章的代码添加到 bbox_iou 方法,修改后的代码如下:
def inner_iou(box1, box2, xywh=True, eps=1e-7, ratio=0.7):
if not xywh:
box1, box2 = ops.xyxy2xywh(box1), ops.xyxy2xywh(box2)
(x1, y1, w1, h1), (x2, y2, w2, h2) = box1.chunk(4, -1), box2.chunk(4, -1)
inner_b1_x1, inner_b1_x2, inner_b1_y1, inner_b1_y2 = x1 - (w1 * ratio) / 2, x1 + (w1 * ratio) / 2, y1 - (h1 * ratio) / 2, y1 + (h1 * ratio) / 2
inner_b2_x1, inner_b2_x2, inner_b2_y1, inner_b2_y2 = x2 - (w2 * ratio) / 2, x2 + (w2 * ratio) / 2, y2 - (h2 * ratio) / 2, y2 + (h2 * ratio) / 2
# Inner-IoU
inter = (inner_b1_x2.minimum(inner_b2_x2) - inner_b1_x1.maximum(inner_b2_x1)).clamp_(0) * \
(inner_b1_y2.minimum(inner_b2_y2) - inner_b1_y1.maximum(inner_b2_y1)).clamp_(0)
inner_union = w1 * h1 * ratio * ratio + w2 * h2 * ratio * ratio - inter + eps
return inter / inner_union
def bbox_iou(box1, box2, xywh=True, GIoU=False, DIoU=False, CIoU=False,PIoU=False, PIoU2=False,MDPIoU=False,Inner_iou=False,SIOU=False,
feat_h=640, feat_w=640 ,Lambda=1.3, eps=1e-7,ratio=0.7):
# 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 = (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
# IoU
iou = inter / union
if CIoU or DIoU or GIoU or SIOU:
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 SIOU: # 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:
v = (4 / math.pi ** 2) * (torch.atan(w2 / h2) - torch.atan(w1 / h1)).pow(2)
with torch.no_grad():
alpha = v / (v - iou + (1 + eps))
if Inner_iou:
iou = inner_iou(box1, box2, xywh=xywh, ratio=ratio)
return iou - (rho2 / c2 + v * alpha) # CIoU
elif SIOU:
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 Inner_iou:
iou = inner_iou(box1, box2, xywh=xywh, ratio=ratio)
return iou - 0.5 * (distance_cost + shape_cost) + eps
if Inner_iou:
iou = inner_iou(box1, box2, xywh=xywh, ratio=ratio)
return iou - rho2 / c2 # DIoU
c_area = cw * ch + eps # convex area
if Inner_iou:
iou = inner_iou(box1, box2, xywh=xywh, ratio=ratio)
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
if Inner_iou:
iou = inner_iou(box1, box2, xywh=xywh, ratio=ratio)
return iou - d1 / mpdiou_hw_pow - d2 / mpdiou_hw_pow # MPDIoU
if Inner_iou:
iou = inner_iou(box1, box2, xywh=xywh, ratio=ratio)
return iou # IoU
为了大家看到我修改了哪些代码,下面将介绍添加步骤细节:
1)在 metrics.py 文件添加第二章的代码,圈起来就是新增的,inner_iou这个代码封装可以参考官网提供的inner_ciou 和 inner_siou 的代码
2)在 bbox_iou 方法添加第二章的代码,圈起来就是新增的
(2).之后在 loss.py 调用,文件目录:ultralytics/utils/loss.py
Inner-SIoU,Inner-GIoU,Inner-DIoU,Inner-CIoU,Inner-MDPIoU 使用方法,首先 metrics.py 文件的 bbox_iou 方法把 Inner_iou 参数设置为 True 就行,不需要 Inner-IoU 时候把它设置为 False,Inner_iou 设置为 False时候,就调用默认的 iou。
之后在 loss.py 调用就行,代码大概在73行,原始代码如下:
iou = bbox_iou(pred_bboxes[fg_mask], target_bboxes[fg_mask], xywh=False, CIoU=True)
修改后的代码如下:
iou = bbox_iou(pred_bboxes[fg_mask], target_bboxes[fg_mask], xywh=False, SIOU=True)
修改如图所示:
注意注意注意:修改损失函数就简单很多了,你要使用哪个损失函数就填哪个例如使用 CloU 就填 CloU =True,其他参数不用修改,如果需要 Inner-IoU 就要在 metrics.py 文件的 bbox_iou 方法把 Inner_iou 参数改为 True 就行 。
🎓四、yaml文件修改建议
修改损失函数的话 yaml 文件不需要修改,直接用原始 yolov8.yaml 就行,不过大家也可以结合其他改进方法一起使用,如注意机制+改进的损失,改进的主动网络+改进的损失等,结合方式有很多种,大家可以自己尝试
🎓五、训练文件修改
🍀🍀1.新建训练文件
(1)在根目录新建一个 python 文件,取名为:train.py,如果之前看过我的文章,已经新建过就不用重新新建了
🍀🍀2.修改训练文件
YOLOv8 训练方式跟 YOLOv5 是有区别的,但是训练数据集格式跟 YOLOv5 一样的,你只需把处理好的数据集就行,这里就不在阐述了,废话不多说,我的训练文件如下,根据你训练需求修改指定参数就行,其中圈起来的参数需要你修改的,其他参数根据自己需求选择改或者不改就行。
训练的代码如下,如果之前看过我的文章,已经复制过了就不用重新复制了,只需修改参数就行
# -*- coding: utf-8 -*-
"""
@Auth : 挂科边缘
@File :trian.py
@IDE :PyCharm
@Motto:学习新思想,争做新青年
@Email :179958974@qq.com
@qq :179958974
"""
import warnings
warnings.filterwarnings('ignore')
from ultralytics import YOLO
if __name__ == '__main__':
# model.load('yolov8n.pt') # 是否加载预训练权重,改进或者做对比实验时候不建议打开,因为用预训练模型整体精度没有很明显的提升
model = YOLO(model=r'D:\2-Python\1-YOLO\YOLOv8\new-yolov8\ultralytics-main\ultralytics\cfg\models\v8\yolov8.yaml')
model.train(data=r'data.yaml',
imgsz=640,
epochs=50,
batch=4,
workers=0,
device='',
optimizer='SGD',
close_mosaic=10,
resume=False,
project='runs/train',
name='exp',
single_cls=False,
cache=False,
)
训练代码的参数解释,标蓝色的参数为常用参数:
- model 参数:该参数填入模型配置文件的路径,改进的话建议不需要预训练模型权重来训练
- data 参数:该参数可以填入训练数据集配置文件的路径
- imgsz 参数:该参数代表输入图像的尺寸,指定为 640x640 像素
- epochs 参数:该参数代表训练的轮数
- batch 参数:该参数代表批处理大小,电脑显存越大,就设置越大,根据自己电脑性能设置
- workers 参数:该参数代表数据加载的工作线程数,出现显存爆了的话可以设置为 0,默认是 8
- device 参数:该参数代表用哪个显卡训练,留空表示自动选择可用的 GPU 或 CPU
- optimizer 参数:该参数代表优化器类型
- close_mosaic 参数:该参数代表在多少个 epoch 后关闭 mosaic 数据增强
- resume 参数:该参数代表是否从上一次中断的训练状态继续训练。设置为 False 表示从头开始新的训练。如果设置为 True,则会加载上一次训练的模型权重和优化器状态,继续训练。这在训练被中断或在已有模型的基础上进行进一步训练时非常有用。
- project 参数:该参数代表项目文件夹,用于保存训练结果
- name 参数:该参数代表命名保存的结果文件夹
- single_cls 参数:该参数代表是否将所有类别视为一个类别,设置为 False 表示保留原有类别
- cache 参数:该参数代表是否缓存数据,设置为 False 表示不缓存。
总结
请在我提供的 YOLOv8 代码修改,把环境配置好,数据集处理好,训练基本能成功,创作不易,请帮忙点一个爱心,谢谢观看