目标检测算法-YOLOV11解析

原文首发于微信公众号

微信公众号-人工智能与图像处理:目标检测算法-YOLOV11解析

一,YOLOV11概述

YOLOv11是由Ultralytics公司开发的新一代目标检测算法,它在之前YOLO版本的基础上进行了显著的架构和训练方法改进。整合了改进的模型结构设计增强的特征提取技术优化的训练方法。真正让YOLO11脱颖而出的是它令人印象深刻的速度准确性效率的结合,使其成为Ultralytics迄今为止创造的最强大的型号之一。通过改进设计,YOLO11提供了更好的特征提取,这是从图像中识别重要模式和细节的过程,即使在具有挑战性的场景中,也可以更准确地捕捉复杂的方面。

YOLO11m在COCO数据集上实现了更高的平均精度(mAP)得分,同时使用的参数比YOLOv8m少22%,使其在不牺牲性能的情况下计算更轻。这意味着它提供了更准确的结果,同时运行效率更高。最重要的是,YOLO11带来了更快的处理速度,推理时间比YOLOv10快约2%,使其成为实时应用程序的理想选择。

1、主要创新点

  • 增强的特征提取:YOLOv11采用了改进的骨干网络和颈部架构,增强了特征提取能力,以实现更精确的目标检测和复杂任务的性能。

  • 优化效率和速度:引入了精细的架构设计和优化的训练流程,提供了更快的处理速度,并在准确性和性能之间保持了最佳平衡。

  • 更少参数下的高准确度:YOLOv11在COCO数据集上实现了更高的平均精度均值(mAP),同时比YOLOv8少用了22%的参数,使其在不牺牲准确性的情况下具有计算效率。

  • 跨环境的适应性:YOLOv11可以无缝部署在各种环境中,包括边缘设备、云平台和支持NVIDIA GPU的系统,确保了最大的灵活性。

  • 支持广泛的任务:YOLOv11不仅支持目标检测,还支持实例分割图像分类姿态估计定向目标检测(OBB),满足一系列计算机视觉挑战。

2、YOLOv11的网络结构和关键创新点包括:

  • C3k2机制:这是一种新的卷积机制,它在网络的浅层将c3k参数设置为False,类似于YOLOv8中的C2f结构。

  • C2PSA机制:这是一种在C2机制内部嵌入的多头注意力机制,类似于在C2中嵌入了一个PSA(金字塔空间注意力)机制。

  • 深度可分离卷积(DWConv):在分类检测头中增加了两个DWConv,这种卷积操作减少了计算量和参数量,提高了模型的效率。

  • 自适应锚框机制:自动优化不同数据集上的锚框配置,提高了检测精度。

  • EIoU损失函数:引入了新的EIoU(Extended IoU)损失函数,考虑了预测框与真实框的重叠面积,长宽比和中心点偏移,提高了预测精度。

3、训练:

YOLOv11的训练过程包括数据准备数据增强超参数优化模型训练几个阶段。它使用混合精度训练技术,在不降低模型精度的情况下,加快了训练速度,并减少了显存的占用。

4、部署:

在部署方面,YOLOv11支持导出为不同的格式,如ONNXTensorRTCoreML,以适应不同的部署平台。它还采用了多种加速技术,如半精度浮点数推理(FP16)批量推理硬件加速,以提升推理速度。

YOLOv11的成功标志着目标检测技术又迈出了重要的一步,它为开发者提供了更强大的工具来应对日益复杂的视觉检测任务。

二、各个模块解析

整体的检测算法框架图如下(yolov11n)

1、input

    输入要求以及预处理,可选项比较多,可以参考这个配置文件:ultralytics/ultralytics/nn/tasks.py at main · ultralytics/ultralytics (github.com) · GitHub 的Hyperparameters 部分。

    基础输入仍然为640*640。预处理就是熟悉的letterbox(根据参数配置可以为不同的缩放填充模式,主要用于resize到640)+ 转换rgb、chw、int8(0-255)->float(0-1),注意没有归一化操作。需要注意的是作者实现的mosaic和网上看到的不同,对比如下图(上边网上版本,下边是YOLO的实现)。并且作者添加了在最后10轮关闭mosaic增强。

2、backbone

    主干网络以及改进

    这里不去特意强调对比YOLOv5、V8等等的改进,因为各个系列都在疯狂演进,着重看看一些比较重要的模块即可。源代码:

大多数模块:ultralytics/ultralytics/nn/modules/block.py at main · ultralytics/ultralytics (github.com)

head 部分:ultralytics/ultralytics/nn/modules/head.py at main · ultralytics/ultralytics (github.com)

串联模块构造网络:ultralytics/ultralytics/nn/tasks.py at main · ultralytics/ultralytics (github.com)

1)CBS 模块(后面叫做Conv)

    就是pytorch 自带的conv + BN +SiLU,这里对应上面的配置文件的Conv 的 args 比如[64, 3, 2] 就是 conv2d 的c2=64、k=3、 s =2、c1 自动为上一层参数、p 为自动计算,真实需要计算scales 里面的with 和 max_channels 缩放系数。

    这里连续使用两个3*3卷积stride为2的CBS模块直接横竖各降低了4倍分辨率(整体变为原来1/16)。这个还是比较猛的,敢在如此小的感受野下连续两次仅仅用一层卷积就下采样,当然作为代价它的特征图还是比较厚的分别为16、32。    

    class Conv(nn.Module):    """Standard convolution with args(ch_in, ch_out, kernel, stride, padding, groups, dilation, activation)."""    default_act = nn.SiLU()  # default activation    def __init__(self, c1, c2, k=1, s=1, p=None, g=1, d=1, act=True):        """Initialize Conv layer with given arguments including activation."""        super().__init__()        self.conv = nn.Conv2d(c1, c2, k, s, autopad(k, p, d), groups=g, dilation=d, bias=False)        self.bn = nn.BatchNorm2d(c2)        self.act = self.default_act if act is True else act if isinstance(act, nn.Module) else nn.Identity()    def forward(self, x):        """Apply convolution, batch normalization and activation to input tensor."""        return self.act(self.bn(self.conv(x)))    def forward_fuse(self, x):        """Perform transposed convolution of 2D data."""        return self.act(self.conv(x))
 2)c3k2 模块

    Bottleneck

有两种结构,需要参数shortcut和两个conv的宽度是否相同来控制。

    C3 & C3K

    都是CSP bottleneck module with 3 convolutions, C3 代表3个卷积层, K代表其中bottleneck中的卷积核为支持自定义,其实这里c3k作者使用的默认的3*3卷积核也就等同于使用c3(c3是3*3卷积核)。

    c2f & c3k2 

    其实也就是仿照YOLOv7 的ELAN 结构,通过更多的分支夸层链接,丰富了模型的梯度流。C3K2模块其实就是C2F模块转变出来的,它代码中有一个设置,就是当c3k这个参数为FALSE的时候,C3K2模块就是C2F模块,也就是说它的Bottleneck是普通的Bottleneck;反之当它为true的时候,将Bottleneck模块替换成C3K模块。模块中存在 Split 等操作对特定硬件部署没有之前那么友好了。需要针对自己的硬件进行测试看对最终推理速度的影响。

    可视化关系如下,这里需要注意配置文件中的参数,比如21行[-1, 2, C3k2, [512, False, 0.25]] 512代表宽度、false代表是否使用shortcut、0.25代表c2f的宽度缩放。也就是第一个Conv的输出宽度。 

源代码如下: 

class Bottleneck(nn.Module):    """Standard bottleneck."""    def __init__(self, c1, c2, shortcut=True, g=1, k=(3, 3), e=0.5):        """Initializes a standard bottleneck module with optional shortcut connection and configurable parameters."""        super().__init__()        c_ = int(c2 * e)  # hidden channels        self.cv1 = Conv(c1, c_, k[0], 1)        self.cv2 = Conv(c_, c2, k[1], 1, g=g)        self.add = shortcut and c1 == c2    def forward(self, x):        """Applies the YOLO FPN to input data."""        return x + self.cv2(self.cv1(x)) if self.add else self.cv2(self.cv1(x))class C3(nn.Module):    """CSP Bottleneck with 3 convolutions."""    def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5):        """Initialize the CSP Bottleneck with given channels, number, shortcut, groups, and expansion values."""        super().__init__()        c_ = int(c2 * e)  # hidden channels        self.cv1 = Conv(c1, c_, 1, 1)        self.cv2 = Conv(c1, c_, 1, 1)        self.cv3 = Conv(2 * c_, c2, 1)  # optional act=FReLU(c2)        self.m = nn.Sequential(*(Bottleneck(c_, c_, shortcut, g, k=((1, 1), (3, 3)), e=1.0) for _ in range(n)))    def forward(self, x):        """Forward pass through the CSP bottleneck with 2 convolutions."""        return self.cv3(torch.cat((self.m(self.cv1(x)), self.cv2(x)), 1))class C3k(C3):    """C3k is a CSP bottleneck module with customizable kernel sizes for feature extraction in neural networks."""    def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5, k=3):        """Initializes the C3k module with specified channels, number of layers, and configurations."""        super().__init__(c1, c2, n, shortcut, g, e)        c_ = int(c2 * e)  # hidden channels        # self.m = nn.Sequential(*(RepBottleneck(c_, c_, shortcut, g, k=(k, k), e=1.0) for _ in range(n)))        self.m = nn.Sequential(*(Bottleneck(c_, c_, shortcut, g, k=(k, k), e=1.0) for _ in range(n)))class C2f(nn.Module):    """Faster Implementation of CSP Bottleneck with 2 convolutions."""    def __init__(self, c1, c2, n=1, shortcut=False, g=1, e=0.5):        """Initializes a CSP bottleneck with 2 convolutions and n Bottleneck blocks for faster processing."""        super().__init__()        self.c = int(c2 * e)  # hidden channels        self.cv1 = Conv(c1, 2 * self.c, 1, 1)        self.cv2 = Conv((2 + n) * self.c, c2, 1)  # optional act=FReLU(c2)        self.m = nn.ModuleList(Bottleneck(self.c, self.c, shortcut, g, k=((3, 3), (3, 3)), e=1.0) for _ in range(n))    def forward(self, x):        """Forward pass through C2f layer."""        y = list(self.cv1(x).chunk(2, 1))        y.extend(m(y[-1]) for m in self.m)        return self.cv2(torch.cat(y, 1))    def forward_split(self, x):        """Forward pass using split() instead of chunk()."""        y = list(self.cv1(x).split((self.c, self.c), 1))        y.extend(m(y[-1]) for m in self.m)        return self.cv2(torch.cat(y, 1))class C3k2(C2f):    """Faster Implementation of CSP Bottleneck with 2 convolutions."""    def __init__(self, c1, c2, n=1, c3k=False, e=0.5, g=1, shortcut=True):        """Initializes the C3k2 module, a faster CSP Bottleneck with 2 convolutions and optional C3k blocks."""        super().__init__(c1, c2, n, shortcut, g, e)        self.m = nn.ModuleList(            C3k(self.c, self.c, 2, shortcut, g) if c3k else Bottleneck(self.c, self.c, shortcut, g) for _ in range(n)        )
3)sppf 模块

    对比spp,将简单的并行max pooling 改为串行+并行的方式。对比如下(左边是SPP,右边是SPPF):    

    class SPPF(nn.Module):    # Spatial Pyramid Pooling - Fast (SPPF) layer for YOLOv5 by Glenn Jocher    def __init__(self, c1, c2, k=5):  # equivalent to SPP(k=(5, 9, 13))        super().__init__()        c_ = c1 // 2  # hidden channels        self.cv1 = Conv(c1, c_, 1, 1)        self.cv2 = Conv(c_ * 4, c2, 1, 1)        self.m = nn.MaxPool2d(kernel_size=k, stride=1, padding=k // 2)    def forward(self, x):        x = self.cv1(x)        with warnings.catch_warnings():            warnings.simplefilter('ignore')  # suppress torch 1.9.0 max_pool2d() warning            y1 = self.m(x)            y2 = self.m(y1)            return self.cv2(torch.cat((x, y1, y2, self.m(y2)), 1))
 4)C2PSA 模块

C2PSA它结合了PSA(Pointwise Spatial Attention)块,用于增强特征提取和注意力机制。下面的图建议从左到右看,这样才能更有条理的理解,其实PSA个人感觉就是仿着VIT 的Attention来做的,是把输入C2PSA的特征图的h*w 看做VIT 的path数(也可以理解为NLP中token 个数),特征图的channel 数看做VIT特征维度(CNN的宽度,或者理解为NLP中token 编码后的特征维度),然后计算出QKV(这里需要注意第四幅图的QKV是值,不是操作,所以标注成了圆角矩形,这里是为了大家好理解),这里的Attention其实是在h*w维度计算空间Attention,个人感觉是强制给了全局感受野,并且并联了一个3*3的深度可分离卷积的单空间部分,就是仅在每一个特征图上进行3*3卷积,具体实现是通过pytorch conv2d 的 group参数设置为特征图的通道数。特别的关于Conv的参数分别为:输入通道数、输出通道数、卷积核尺寸、pad尺寸、group数、是否有激活函数(默认silu)。图中的最后一幅省略了一些细节,可以参考源码。

注意区别C2fPSA,C2fPSA才是对 C2f 模块的扩展,通过在标准 C2f 模块中引入 PSA 块,C2fPSA实现了更强大的注意力机制,从而提高了模型对重要特征的捕捉能力。作者实现了该模块但最终没有使用。

涉及的源码:

class Attention(nn.Module):    """    Attention module that performs self-attention on the input tensor.    Args:        dim (int): The input tensor dimension.        num_heads (int): The number of attention heads.        attn_ratio (float): The ratio of the attention key dimension to the head dimension.    Attributes:        num_heads (int): The number of attention heads.        head_dim (int): The dimension of each attention head.        key_dim (int): The dimension of the attention key.        scale (float): The scaling factor for the attention scores.        qkv (Conv): Convolutional layer for computing the query, key, and value.        proj (Conv): Convolutional layer for projecting the attended values.        pe (Conv): Convolutional layer for positional encoding.    """    def __init__(self, dim, num_heads=8, attn_ratio=0.5):        """Initializes multi-head attention module with query, key, and value convolutions and positional encoding."""        super().__init__()        self.num_heads = num_heads        self.head_dim = dim // num_heads        self.key_dim = int(self.head_dim * attn_ratio)        self.scale = self.key_dim**-0.5        nh_kd = self.key_dim * num_heads        h = dim + nh_kd * 2        self.qkv = Conv(dim, h, 1, act=False)        self.proj = Conv(dim, dim, 1, act=False)        self.pe = Conv(dim, dim, 3, 1, g=dim, act=False)    def forward(self, x):        """        Forward pass of the Attention module.        Args:            x (torch.Tensor): The input tensor.        Returns:            (torch.Tensor): The output tensor after self-attention.        """        B, C, H, W = x.shape        N = H * W        qkv = self.qkv(x)        q, k, v = qkv.view(B, self.num_heads, self.key_dim * 2 + self.head_dim, N).split(            [self.key_dim, self.key_dim, self.head_dim], dim=2        )        attn = (q.transpose(-2, -1) @ k) * self.scale        attn = attn.softmax(dim=-1)        x = (v @ attn.transpose(-2, -1)).view(B, C, H, W) + self.pe(v.reshape(B, C, H, W))        x = self.proj(x)        return xclass PSABlock(nn.Module):    """    PSABlock class implementing a Position-Sensitive Attention block for neural networks.    This class encapsulates the functionality for applying multi-head attention and feed-forward neural network layers    with optional shortcut connections.    Attributes:        attn (Attention): Multi-head attention module.        ffn (nn.Sequential): Feed-forward neural network module.        add (bool): Flag indicating whether to add shortcut connections.    Methods:        forward: Performs a forward pass through the PSABlock, applying attention and feed-forward layers.    Examples:        Create a PSABlock and perform a forward pass        >>> psablock = PSABlock(c=128, attn_ratio=0.5, num_heads=4, shortcut=True)        >>> input_tensor = torch.randn(1, 128, 32, 32)        >>> output_tensor = psablock(input_tensor)    """    def __init__(self, c, attn_ratio=0.5, num_heads=4, shortcut=True) -> None:        """Initializes the PSABlock with attention and feed-forward layers for enhanced feature extraction."""        super().__init__()        self.attn = Attention(c, attn_ratio=attn_ratio, num_heads=num_heads)        self.ffn = nn.Sequential(Conv(c, c * 2, 1), Conv(c * 2, c, 1, act=False))        self.add = shortcut    def forward(self, x):        """Executes a forward pass through PSABlock, applying attention and feed-forward layers to the input tensor."""        x = x + self.attn(x) if self.add else self.attn(x)        x = x + self.ffn(x) if self.add else self.ffn(x)        return xclass C2PSA(nn.Module):    """    C2PSA module with attention mechanism for enhanced feature extraction and processing.    This module implements a convolutional block with attention mechanisms to enhance feature extraction and processing    capabilities. It includes a series of PSABlock modules for self-attention and feed-forward operations.    Attributes:        c (int): Number of hidden channels.        cv1 (Conv): 1x1 convolution layer to reduce the number of input channels to 2*c.        cv2 (Conv): 1x1 convolution layer to reduce the number of output channels to c.        m (nn.Sequential): Sequential container of PSABlock modules for attention and feed-forward operations.    Methods:        forward: Performs a forward pass through the C2PSA module, applying attention and feed-forward operations.    Notes:        This module essentially is the same as PSA module, but refactored to allow stacking more PSABlock modules.    Examples:        >>> c2psa = C2PSA(c1=256, c2=256, n=3, e=0.5)        >>> input_tensor = torch.randn(1, 256, 64, 64)        >>> output_tensor = c2psa(input_tensor)    """    def __init__(self, c1, c2, n=1, e=0.5):        """Initializes the C2PSA module with specified input/output channels, number of layers, and expansion ratio."""        super().__init__()        assert c1 == c2        self.c = int(c1 * e)        self.cv1 = Conv(c1, 2 * self.c, 1, 1)        self.cv2 = Conv(2 * self.c, c1, 1)        self.m = nn.Sequential(*(PSABlock(self.c, attn_ratio=0.5, num_heads=self.c // 64) for _ in range(n)))    def forward(self, x):        """Processes the input tensor 'x' through a series of PSA blocks and returns the transformed tensor."""        a, b = self.cv1(x).split((self.c, self.c), dim=1)        b = self.m(b)        return self.cv2(torch.cat((a, b), 1))

3、neck & head

    1)检测头

    YOLOV11 Head 部分和YOLOV8是近似的,所以简单对比YOLOV5、V8、V11。

如上面图,上边是YOLOV5 的结构,中是YOLOv8 的结构,下面是YOLOV11 结构

    Yolov5: 检测和分类共用一个卷积(coupled head)并且是anchor based ,其 卷积输出为(5+N class)*3,其中 5为bbox 四个值(具体代表什么不同版本略有不同,官方git有说明,历史版本见 目标检测算法——YOLOV5 )+ 一个obj 值 (是否有目标,这个是从YOLO V1 传承下来的,个人感觉有点绕和不合理,并且后面取消),N class 为类别数,3为anchor 的数量,默认是3个。

    YOLOv8:检测和分类的卷积是解耦的(decoupled),如中图,上面一条卷积支路是回归框,框的特征图channel为4*regmax,关于这个regmax 后面我们详细的解释,并不是anchor;分类的channel 为类别数。

    YOLOV11:检测和分类的卷积是解耦的(decoupled),如右图,上面一条卷积支路是回归框,框的特征图channel为4*regmax,关于这个regmax 后面我们详细的解释,并不是anchor;分类的channel 为类别数,分类使用深度可分离卷积替代常规卷积降低计算量。

    源码部分如下:

class Detect(nn.Module):    """YOLO Detect head for detection models."""    dynamic = False  # force grid reconstruction    export = False  # export mode    end2end = False  # end2end    max_det = 300  # max_det    shape = None    anchors = torch.empty(0)  # init    strides = torch.empty(0)  # init    def __init__(self, nc=80, ch=()):        """Initializes the YOLO detection layer with specified number of classes and channels."""        super().__init__()        self.nc = nc  # number of classes        self.nl = len(ch)  # number of detection layers        self.reg_max = 16  # DFL channels (ch[0] // 16 to scale 4/8/12/16/20 for n/s/m/l/x)        self.no = nc + self.reg_max * 4  # number of outputs per anchor        self.stride = torch.zeros(self.nl)  # strides computed during build        c2, c3 = max((16, ch[0] // 4, self.reg_max * 4)), max(ch[0], min(self.nc, 100))  # channels        self.cv2 = nn.ModuleList(            nn.Sequential(Conv(x, c2, 3), Conv(c2, c2, 3), nn.Conv2d(c2, 4 * self.reg_max, 1)) for x in ch        )        self.cv3 = nn.ModuleList(            nn.Sequential(                nn.Sequential(DWConv(x, x, 3), Conv(x, c3, 1)),                nn.Sequential(DWConv(c3, c3, 3), Conv(c3, c3, 1)),                nn.Conv2d(c3, self.nc, 1),            )            for x in ch        )        self.dfl = DFL(self.reg_max) if self.reg_max > 1 else nn.Identity()        if self.end2end:            self.one2one_cv2 = copy.deepcopy(self.cv2)            self.one2one_cv3 = copy.deepcopy(self.cv3)    def forward(self, x):        """Concatenates and returns predicted bounding boxes and class probabilities."""        if self.end2end:            return self.forward_end2end(x)        for i in range(self.nl):            x[i] = torch.cat((self.cv2[i](x[i]), self.cv3[i](x[i])), 1)        if self.training:  # Training path            return x        y = self._inference(x)        return y if self.export else (y, x)

因此主要的变化可以认为有三个:(1)coupled head -> decoupled head ;(2)obj 分支消失;(3)anchor based——> anchor free ; 4) 深度可分离卷积

    (1)coupled head -> decoupled head 

    这个解耦操作,看YOLO x 的论文,约有1% 的提升。逻辑和实现都比较直观易懂,不再赘述。

    (2)obj 分支消失;

        这个其实我自己再看YOLO V1 的时候就有疑问,它存在的意义。后来人们发现,其实obj 的在训练和推理过程中存在逻辑不一致性。具体而言(摘自“https://zhuanlan.zhihu.com/p/147691786”)

    A。用法不一致。训练的时候,分类和质量估计各自训练自个儿的,但测试的时候却又是乘在一起作为NMS score排序的依据,这个操作显然没有end-to-end,必然存在一定的gap。(个人认为还好,就是两个监督信号)

    B。对象不一致。借助Focal Loss的力量,分类分支能够使得少量的正样本和大量的负样本一起成功训练,但是质量估计通常就只针对正样本训练。那么,对于one-stage的检测器而言,在做NMS score排序的时候,所有的样本都会将分类score和质量预测score相乘用于排序,那么必然会存在一部分分数较低的“负样本”的质量预测是没有在训练过程中有监督信号的,对于大量可能的负样本,他们的质量预测是一个未定义行为。这就很有可能引发这么一个情况:一个分类score相对低的真正的负样本,由于预测了一个不可信的极高的质量score,而导致它可能排到一个真正的正样本(分类score不够高且质量score相对低)的前面。问题一如图所示: 

(3)anchor based——> anchor free

    这里主要涉及怎么定义回归内容以及如何匹配GT框的问题。

 2)匹配策略

    A。回归的内容当前版本就是回归的lftp四个值(这四个值是距离匹配到的anchor 点的距离值!不是图片的绝对位置)。后面推理阶段通过 dist2bbox函数转换为需要的格式:

ultralytics/ultralytics/nn/modules.py at cc3c774bde86ffce694d202b7383da6cc1721c1b · ultralytics/ultralytics (github.com)

ultralytics/ultralytics/yolo/utils/tal.py at cc3c774bde86ffce694d202b7383da6cc1721c1b · ultralytics/ultralytics (github.com)

   def dist2bbox(distance, anchor_points, xywh=True, dim=-1):    """Transform distance(ltrb) to box(xywh or xyxy)."""    lt, rb = torch.split(distance, 2, dim)    x1y1 = anchor_points - lt    x2y2 = anchor_points + rb    if xywh:        c_xy = (x1y1 + x2y2) / 2        wh = x2y2 - x1y1        return torch.cat((c_xy, wh), dim)  # xywh bbox    return torch.cat((x1y1, x2y2), dim)  # xyxy bbox

 B.匹配策略

YOLOv5 采用静态的匹配策略,V8采用了动态的TaskAlignedAssigner,其余常见的动态匹配还有:YOLOX 的 simOTA、TOOD 的 TaskAlignedAssigner 和 RTMDet 的 DynamicSoftLabelAssigner。

TaskAligned使用分类得分和IoU的高阶组合来衡量Task-Alignment的程度。使用上面公式来对每个实例计算Anchor-level 的对齐程度:s 和 u 分别为分类得分和 IoU 值,α 和 β 为权重超参。t 可以同时控制分类得分和IoU 的优化来实现 Task-Alignment,可以引导网络动态的关注于高质量的Anchor。采用一种简单的分配规则选择训练样本:对每个实例,选择m个具有最大t值的Anchor作为正样本,选择其余的Anchor作为负样本。然后,通过损失函数(针对分类与定位的对齐而设计的损失函数)进行训练。

代码地址:ultralytics/ultralytics/yolo/utils/tal.py at c0c0c138c12699807ff9446f942cb3bd325d670b · ultralytics/ultralytics (github.com)

4、loss function

    损失函数设计

    Loss 计算包括 2 个分支:分类和回归分支,没有了之前的 objectness 分支。

    分类分支依然采用 BCE Loss。回归分支使用了 Distribution Focal Loss(DFL Reg_max默认为16)+ CIoU Loss。3 个 Loss 采用一定权重比例加权即可

这里重点介绍一下DFL损失。目前被广泛使用的bbox表示可以看作是对bbox方框坐标建模了单一的狄拉克分布。但是在复杂场景中,一些检测对象的边界并非十分明确。如下图左面所示,对于滑板左侧被水花模糊,引起对左边界的预测分布是任意而扁平的,对右边界的预测分布是明确而尖锐的。对于这个问题,有学者提出直接回归一个任意分布来建模边界框,使用softmax实现离散的回归,将狄拉克分布的积分形式推导到一般形式的积分形式来表示边界框。

狄拉克分布可以认为在一个点概率密度为无穷大,其他点概率密度为0,这是一种极端地认为离散的标签时绝对正确的。

因为标签是一个离散的点,如果把标签认为是绝对正确的目标,那么学习出的就是狄拉克分布,概率密度是一条尖锐的竖线。然而真实场景,物体边界并非是十分明确的,因此学习一个宽范围的分布更为合理。我们需要获得的分布虽然不再像狄拉克分布那么极端(只存在标签值),但也应该在标签值附近。因此学者提出Distribution Focal Loss损失函数,目的让网络快速聚焦到标签附近的数值,是标签处的概率密度尽量大。思想是使用交叉熵函数,来优化标签y附近左右两个位置的概率,是网络分布聚焦到标签值附近。如下公式。Si 是网络的sigmod 输出(因为真是是多分类,所以是softmax),yi 和 yi+1 是上图的区间顺序,y是label 值。

    具体而言,针对我们将DFL的超参数Reg_max 设置为16的情况下:

    A、训练阶段:我们以回归left为例:目标的label 转换为ltrb后,y = ( left - 匹配到的anchor 中心点 x 坐标)/ 当前的下采样倍数,假设求得3.2。那么i 就应该为3,yi = 3 ,yi+1 = 4。

    B、推理阶段:因为没有label,直接将16个格子进行积分(离散变量为求和,也就是期望)结果就是最终的坐标偏移量(再乘以下采样倍数+ 匹配到的anchor的对应坐标)

三,相关地址:

论文地址:

代码地址:https://github.com/ultralytics/ultralytics

四,参考文章:

YOLOv11全网最新创新点改进系列:一文读懂YOLOv11算法!!!-CSDN博客

YOLO11问世!重新定义AI的可能性!|速度|ai|大模型|计算机视觉_网易订阅 (163.com)

目标检测算法——YOLOV11——算法详解-CSDN博客

YOLOv11训练自己的数据集,YOLOv11网络解析_yolov11 github-CSDN博客

YOLO11 沉浸式讲解 YOLOV11网络结构以及代码剖析-CSDN博客

YOLOv11 | 一文带你深入理解ultralytics最新作品yolov11的创新 | 训练、推理、验证、导出 (附网络结构图)-CSDN博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/56499.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Redis Search系列 - 第四讲 支持中文

目录 一、支持中文二、自定义中文词典2.1 Redis Search设置FRISOINI参数2.2 friso.ini文件相关配置1)自定义friso UTF-8字典2)修改friso.ini配置文件 三、实测中文分词效果 一、支持中文 Redis Stack 从版本 0.99.0 开始支持中文文档的添加和分词。中文…

Django+Vue智慧分析居家养老系统统的设计与实现

目录 1 项目介绍2 项目截图3 核心代码3.1 需要的环境3.2 Django接口层3.3 实体类3.4 config.ini3.5 启动类3.5 Vue 4 数据库表设计5 文档参考6 计算机毕设选题推荐7 源码获取 1 项目介绍 博主个人介绍:CSDN认证博客专家,CSDN平台Java领域优质创作者&…

【学习笔记】强化学习

李宏毅深度强化学习 笔记 课程主页:NTU-MLDS18 视频:youtube B站 参考资料: 作业代码参考 纯numpy实现非Deep的RL算法 OpenAI tutorial 文章目录 李宏毅深度强化学习 笔记1. Introduction2. Policy Gradient2.1 Origin Policy Gradient2.2…

基于大型语言模型的智能网页抓取

Google Gemini 是 Google AI 创建的大型语言模型 (LLM) 系列,可提供最先进的 AI 功能。Gemini 模型包括: Gemini Ultra — 最大、最强大的模型,擅长处理编码、逻辑推理和创意协作等复杂任务。可通过 Gemini Advanced(原名 Bard&a…

【Linux】基础IO-上

1、共识原理 1、文件 内容 属性 2、文件分为打开的文件和没打开的文件 3、打开的文件是谁打开的? 答案是:进程!---本质是研究进程和文件的关系 文件被打开必须先被加载到内存,一个进程可以打开多个文件。因此,在OS内…

NVR小程序接入平台/设备EasyNVR多个NVR同时管理的高效解决方案

在当今的数字化安防时代,视频监控系统的需求日益复杂和多样化。为了满足不同场景下的监控需求,一种高效、灵活且兼容性强的安防视频监控平台——NVR批量管理软件/平台EasyNVR应运而生。本篇探讨这一融合所带来的创新与发展。 一、NVR监测软件/设备EasyNV…

mysql 13 MySQL基于规则的优化

01.条件化简 我们编写的查询语句的搜索条件本质上是一个表达式,这些表达式可能比较繁杂,或者不能高效的执行, MySQL的查询优化器会为我们简化这些表达式。为了方便大家理解,我们后边举例子的时候都使用诸如 a 、 b 、 c 之类的简…

shell——正则表达式入门

目录 一、常规匹配 二、特殊字符 ^ $ . * 字符区间 \ 三、示例 shell中总是会需要对文本字符串做各种各样的剪切拼接等操作,除了 basename 和 dirname 这种简单的函数外,还可以用正则表达式,定义模糊匹配的筛选规则 一、常规匹配 管…

Pyqt5设计打开电脑摄像头+可选择哪个摄像头(如有多个)

目录 专栏导读库的安装代码介绍完整代码总结 专栏导读 🌸 欢迎来到Python办公自动化专栏—Python处理办公问题,解放您的双手 🏳️‍🌈 博客主页:请点击——> 一晌小贪欢的博客主页求关注 👍 该系列文…

【C++】——list 容器的解析与极致实现

人的一切痛苦,本质上都是对自己的无能的愤怒。 —— 王小波 目录 1、list 介绍 2、list的使用 2.1 list 的构造 2.2 iterator 的使用 2.3 list 的修改 2.4一些特殊接口 2.5 迭代器失效问题 3、实现list 3.1底层结构 结点类 list类 迭代器类 3.2功能接…

【优选算法篇】在分割中追寻秩序:二分查找的智慧轨迹

文章目录 C 二分查找详解:基础题解与思维分析前言第一章:热身练习1.1 二分查找基本实现解题思路图解分析C代码实现易错点提示代码解读 1.2 在排序数组中查找元素的第一个和最后一个位置解题思路1.2.1 查找左边界算法步骤:图解分析C代码实现 1…

git clone报错fatal: pack has bad object at offset 186137397: inflate returned 1

git clone报错fatal: pack has bad object at offset 186137397: inflate returned 1 逐步拷贝 https://stackoverflow.com/questions/27653116/git-fatal-pack-has-bad-object-at-offset-x-inflate-returned-5 https://www.cnblogs.com/Lenbrother/p/17726195.html https://…

在UE引擎中使用spine动画(1)

注意事项,spine的版本必须和UE插件的版本相同。 1.最重要的是“修改骨架名称。(影响在UE引擎中的资产名称) 2.导出操作(把非必要的数据取消掉,可能会影响UE导入)。 3.纹理打包(一般默认&#…

程序员:数字时代的先锋

随着科技的不断进步,程序员这一职业群体逐渐成为社会中不可或缺的一部分。他们以智慧和汗水为世界带来更多的便捷与创新。今天,我们将庆祝1024程序员节,这是一个向全球程序员们表达敬意和感激的节日。让我们一同走进程序员的内心世界&#xf…

数字分组求偶数和

问题描述 小M面对一组从 1 到 9 的数字,这些数字被分成多个小组,并从每个小组中选择一个数字组成一个新的数。目标是使得这个新数的各位数字之和为偶数。任务是计算出有多少种不同的分组和选择方法可以达到这一目标。 numbers: 一个由多个整数字符串组…

[ 钓鱼实战系列-基础篇-7 ] 一篇文章教会你搭建邮件钓鱼服务器-1

🍬 博主介绍 👨‍🎓 博主介绍:大家好,我是 _PowerShell ,很高兴认识大家~ ✨主攻领域:【渗透领域】【数据通信】 【通讯安全】 【web安全】【面试分析】 🎉点赞➕评论➕收藏 养成习…

js.杨辉三角和分发饼干

1,链接:118. 杨辉三角 - 力扣(LeetCode) 题目: 给定一个非负整数 numRows,生成「杨辉三角」的前 numRows 行。 在「杨辉三角」中,每个数是它左上方和右上方的数的和。 示例 1: 输入: numRows …

动态规划-子序列问题——300.最长递增子序列

1.题目解析 题目来源:300.最长递增子序列——力扣 测试用例 2.算法原理 1.状态表示 首先创建一个与数组大小相同的dp表,此时dp[i]表示的是:以第i个位置为结尾的所有子序列中最长递增子序列的长度 2.状态转移方程 此时第i个位置一定是最长递…

国家能源集团携手海康威视研发攻克融合光谱煤质快检技术

10月24日,在国家能源集团准能集团黑岱沟露天煤矿,安装于准能选煤厂785商品煤胶带机中部的煤质快检核心设备,正在对当天装车外运的商品煤煤质进行实时检测。仅两分钟后,涵盖发热量、水分、灰分、硫分等多项指标的数据信息已传输到到…

六.python面向对象

学过C或者Java的同学一定了解过面向对象的相关内容,编程语言一般分为两种设计方式:面向对象、面向过程,早期的编程语言多是面向过程的,由多个过程组合在一起,而Python在设计的时候就是一种面向对象的语言,因…