改进yolo11-HWD等200+全套创新点大全:高尔夫球检测系统源码&数据集全套
1.图片效果展示
项目来源 人工智能促进会 2024.10.30
注意:由于项目一直在更新迭代,上面“1.图片效果展示”和“2.视频效果展示”展示的系统图片或者视频可能为老版本,新版本在老版本的基础上升级如下:(实际效果以升级的新版本为准)
(1)适配了YOLOV11的“目标检测”模型和“实例分割”模型,通过加载相应的权重(.pt)文件即可自适应加载模型。
(2)支持“图片识别”、“视频识别”、“摄像头实时识别”三种识别模式。
(3)支持“图片识别”、“视频识别”、“摄像头实时识别”三种识别结果保存导出,解决手动导出(容易卡顿出现爆内存)存在的问题,识别完自动保存结果并导出到tempDir中。
(4)支持Web前端系统中的标题、背景图等自定义修改。
另外本项目提供训练的数据集和训练教程,暂不提供权重文件(best.pt),需要您按照教程进行训练后实现图片演示和Web前端界面演示的效果。
2.视频效果展示
2.1 视频效果展示
3.背景
研究背景与意义
随着科技的不断进步,计算机视觉技术在各个领域的应用日益广泛,尤其是在体育运动中的应用潜力逐渐显现。高尔夫作为一项全球广受欢迎的运动,其比赛和训练过程中对高尔夫球的精确检测与跟踪具有重要意义。传统的高尔夫球检测方法往往依赖于人工观察和经验判断,效率低下且容易受到环境因素的影响。因此,基于深度学习的自动化检测系统应运而生,成为提升高尔夫运动管理和训练效果的重要工具。
本研究旨在基于改进的YOLOv11模型,构建一个高效的高尔夫球检测系统。YOLO(You Only Look Once)系列模型以其快速的检测速度和高准确率在物体检测领域取得了显著的成果。通过对YOLOv11的改进,我们期望在保证检测精度的同时,进一步提升其在复杂环境下的鲁棒性。为此,本项目将使用包含137张高尔夫球图像的数据集,专注于高尔夫球这一单一类别的检测任务。这一数据集虽然规模较小,但其针对性强,能够有效支持模型的训练与验证。
高尔夫球检测系统的成功实现,不仅能够为高尔夫运动员提供实时的训练反馈,帮助他们更好地掌握击球技巧,还能够为赛事组织者提供数据支持,提升赛事的管理效率。此外,该系统的开发也为其他运动项目的物体检测提供了借鉴,推动了计算机视觉技术在体育领域的进一步应用。因此,本研究不仅具有重要的学术价值,也具备广泛的实际应用前景。
4.数据集信息展示
4.1 本项目数据集详细数据(类别数&类别名)
nc: 1
names: [‘golf ball’]
该项目为【目标检测】数据集,请在【训练教程和Web端加载模型教程(第三步)】这一步的时候按照【目标检测】部分的教程来训练
4.2 本项目数据集信息介绍
本项目数据集信息介绍
本项目所使用的数据集名为“Golf Ball Detection”,旨在为改进YOLOv11的高尔夫球检测系统提供必要的训练基础。该数据集专注于高尔夫球的识别与定位,包含了丰富的高尔夫球图像数据,确保了模型在实际应用中的准确性和鲁棒性。数据集中仅包含一个类别,即“golf ball”,这使得模型的训练过程更加专注和高效。通过聚焦于单一类别,研究人员能够深入分析高尔夫球在不同环境、光照和背景下的特征,从而提升检测系统的性能。
数据集的构建过程遵循严格的标准,确保了图像的多样性和代表性。收集的图像涵盖了各种场景,包括高尔夫球场、练习场以及不同的自然环境。这些图像不仅展示了高尔夫球的不同颜色和品牌,还考虑了高尔夫球在不同位置和姿态下的表现,增强了模型的泛化能力。此外,数据集中还包含了不同的拍摄角度和距离,以模拟真实世界中可能遇到的各种情况。
为了确保数据集的高质量,所有图像均经过精心标注,确保每个高尔夫球的边界框准确无误。这种高质量的标注不仅为YOLOv11的训练提供了坚实的基础,也为后续的模型评估和优化提供了可靠的数据支持。通过使用“Golf Ball Detection”数据集,研究团队期望能够显著提升高尔夫球检测系统的准确性和实时性,从而推动高尔夫运动相关技术的发展与应用。
5.全套项目环境部署视频教程(零基础手把手教学)
5.1 所需软件PyCharm和Anaconda安装教程(第一步)
5.2 安装Python虚拟环境创建和依赖库安装视频教程(第二步)
6.改进YOLOv11训练教程和Web_UI前端加载模型教程(零基础手把手教学)
6.1 改进YOLOv11训练教程和Web_UI前端加载模型教程(第三步)
按照上面的训练视频教程链接加载项目提供的数据集,运行train.py即可开始训练
Epoch gpu_mem box obj cls labels img_size
1/200 20.8G 0.01576 0.01955 0.007536 22 1280: 100%|██████████| 849/849 [14:42<00:00, 1.04s/it]
Class Images Labels P R mAP@.5 mAP@.5:.95: 100%|██████████| 213/213 [01:14<00:00, 2.87it/s]
all 3395 17314 0.994 0.957 0.0957 0.0843
Epoch gpu_mem box obj cls labels img_size
2/200 20.8G 0.01578 0.01923 0.007006 22 1280: 100%|██████████| 849/849 [14:44<00:00, 1.04s/it]
Class Images Labels P R mAP@.5 mAP@.5:.95: 100%|██████████| 213/213 [01:12<00:00, 2.95it/s]
all 3395 17314 0.996 0.956 0.0957 0.0845
Epoch gpu_mem box obj cls labels img_size
3/200 20.8G 0.01561 0.0191 0.006895 27 1280: 100%|██████████| 849/849 [10:56<00:00, 1.29it/s]
Class Images Labels P R mAP@.5 mAP@.5:.95: 100%|███████ | 187/213 [00:52<00:00, 4.04it/s]
all 3395 17314 0.996 0.957 0.0957 0.0845
项目数据集下载链接
7.原始YOLOv11算法讲解
其实到了YOLOV11 基本创新点就不太多了,主要就是大家互相排列组合复用不同的网络模块、损失函数和样本匹配策略,需要注意YOLO V5、V8 V11
都是1个公司的,其余的个人建议看看V8的,剩下的了解就好。
V11支持多种视觉任务:物体检测、实例分割、图像分类、姿态估计和定向物体检测(OBB)。
YOLOv11
基本和YOLOV8同源,甚至git目前都是1个,部分代码注释还是YOLOV8的,所以建议先看我写的YOLOV8相关博客,对比YOLOV8主要涉及到:
*backbone 中的使用C2f模块 变为 c3k2 模块。
*backbone 中的最后一层(sppf层)后增加了C2PSA模块。
*head 解耦头中的分类检测头两个Conv 变为 DWConv。
整体技术而言:
*backbone 使用了C2K2模块+最后SPPF模块级联C2PSA模块;
*neck 使用PAN结构,并且里面也使用C3K2模块;
*head使用了anchor-free + Decoupled-head,其中回归头使用正常的卷积,分类头使用DWConv;
*损失函数使用了分类BCE、回归CIOU + VFL的组合;
*框匹配策略由静态匹配改为了Task-Aligned Assigner匹配方式;
*训练策略没有提及,其中YOLOV8可以参考如下最后 10 个 epoch 关闭 Mosaic 的操作、训练总 epoch 数从 300 提升到了 500。
主要思路
配置文件:ultralytics/ultralytics/cfg/models/11/yolo11.yaml at main ·
ultralytics/ultralytics ·
GitHub
解析函数:ultralytics/ultralytics/nn/tasks.py at main · ultralytics/ultralytics ·
GitHub
具体细节
input
输入要求以及预处理,可选项比较多,可以参考这个配置文件:ultralytics/ultralytics/cfg/default.yaml at main
· ultralytics/ultralytics ·
GitHub 的Hyperparameters 部分。
基础输入仍然为640*640。预处理就是熟悉的letterbox(根据参数配置可以为不同的缩放填充模式,主要用于resize到640)+
转换rgb、chw、int8(0-255)->float(0-1),注意没有归一化操作。需要注意的是作者实现的mosaic和网上看到的不同,对比如下图(左边网上版本,右边是YOLO的实现)。并且作者添加了在最后10轮关闭mosaic增强(YOLOV8开始支持,具体原因个人的经验如我的这篇文章:yolov5
mosaic相关,关闭参数在 Train settings 部分的close_mosaic 选项)
backbone
主干网络以及改进
这里不去特意强调对比YOLOv5、V8等等的改进,因为各个系列都在疯狂演进,个人认为没必要花费时间看差异,着重看看一些比较重要的模块即可。源代码:
大多数模块:ultralytics/ultralytics/nn/modules/block.py at main ·
ultralytics/ultralytics ·
GitHub
head 部分:ultralytics/ultralytics/nn/modules/head.py at main ·
ultralytics/ultralytics ·
GitHub
串联模块构造网络:ultralytics/ultralytics/nn/tasks.py at main ·
ultralytics/ultralytics ·
GitHub
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作者使用的默认的33卷积核也就等同于使用c3(c3是33卷积核)。
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的特征图的hw 看做VIT 的path数(也可以理解为NLP中token 个数),特征图的channel
数看做VIT特征维度(CNN的宽度,或者理解为NLP中token
编码后的特征维度),然后计算出QKV(这里需要注意第四幅图的QKV是值,不是操作,所以标注成了圆角矩形,这里是为了大家好理解),这里的Attention其实是在hw维度计算空间Attention,个人感觉是强制给了全局感受野,并且并联了一个33的深度可分离卷积的单空间部分,就是仅在每一个特征图上进行33卷积,具体实现是通过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 x
class 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 x
class 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函数转换为需要的格式:
https://github.com/ultralytics/ultralytics/blob/cc3c774bde86ffce694d202b7383da6cc1721c1b/ultralytics/nn/modules.py#L378
https://github.com/ultralytics/ultralytics/blob/cc3c774bde86ffce694d202b7383da6cc1721c1b/ultralytics/yolo/utils/tal.py#L196。
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
默认参数如下(当前版本这些超参没有提供修改的接口,如需修改需要在源码上进行修改):
4、loss function
损失函数设计
Loss 计算包括 2 个分支: 分类和回归分支,没有了之前的 objectness 分支。
分类分支依然采用 BCE Loss。回归分支使用了 Distribution Focal Loss(DFL Reg_max默认为16)+ CIoU
Loss。3 个 Loss
采用一定权重比例加权即可(默认如下:https://github.com/ultralytics/ultralytics/blob/main/ultralytics/yolo/configs/default.yaml#L83)。
这里重点介绍一下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的对应坐标)
DFL的实现方式其实就是一个卷积:ultralytics/ultralytics/nn/modules.py at
cc3c774bde86ffce694d202b7383da6cc1721c1b · ultralytics/ultralytics ·
GitHub
NOTE:作者代码中的超参数Reg_max是写死的——16,并且代码内部做了强制截断到16,如果要修改需要修改源码,如果你的输入是640,最大下采样到2020,那么16是够用的,如果输入没有resize或者超过了640一定要自己设置这个Reg_max参数,否则如果目标尺寸还大,将无法拟合到这个偏移量。
比如12801280的图片,目标1280*960,最大下采样32倍,1280/32/2=20 > 16(除以2是因为是一半的偏移量),超过了dfl
滑板右侧那个图的范围。至于为什么叫focal
loss的变体,有兴趣看一下这个https://zhuanlan.zhihu.com/p/357415257和https://zhuanlan.zhihu.com/p/147691786就可以,这里不再赘述是因为,如果先看这些,很容易犯晕,反而抓不住DFL
我认为的重点(离散的分布形式)
class DFL(nn.Module):
# Integral module of Distribution Focal Loss (DFL) proposed in Generalized Focal Loss https://ieeexplore.ieee.org/document/9792391
def __init__(self, c1=16):
super().__init__()
self.conv = nn.Conv2d(c1, 1, 1, bias=False).requires_grad_(False)
x = torch.arange(c1, dtype=torch.float)
self.conv.weight.data[:] = nn.Parameter(x.view(1, c1, 1, 1))
self.c1 = c1
def forward(self, x):
b, c, a = x.shape # batch, channels, anchors
return self.conv(x.view(b, 4, self.c1, a).transpose(2, 1).softmax(1)).view(b, 4, a)
# return self.conv(x.view(b, self.c1, 4, a).softmax(1)).view(b, 4, a)
8.200+种全套改进YOLOV11创新点原理讲解
8.1 200+种全套改进YOLOV11创新点原理讲解大全
由于篇幅限制,每个创新点的具体原理讲解就不全部展开,具体见下列网址中的改进模块对应项目的技术原理博客网址【Blog】(创新点均为模块化搭建,原理适配YOLOv5~YOLOv11等各种版本)
改进模块技术原理博客【Blog】网址链接
8.2 精选部分改进YOLOV11创新点原理讲解
这里节选部分改进创新点展开原理讲解(完整的改进原理见上图和改进模块技术原理博客链接【如果此小节的图加载失败可以通过CSDN或者Github搜索该博客的标题访问原始博客,原始博客图片显示正常】
### 深度学习基础
卷积神经网络通过使用具有共享参数的卷积运算显著降低了模型的计算开销和复杂性。在LeNet、AlexNet和VGG等经典网络的驱动下,卷积神经网络现在已经建立了一个完整的系统,并在深度学习领域形成了先进的卷积神经网络模型。
感受野注意力卷积RFCBAMConv的作者在仔细研究了卷积运算之后获得了灵感。对于分类、目标检测和语义分割任务,一方面,图像中不同位置的对象的形状、大小、颜色和分布是可变的。在卷积操作期间,卷积核在每个感受野中使用相同的参数来提取信息,而不考虑来自不同位置的差分信息。这限制了网络的性能,这已经在最近的许多工作中得到了证实。
另一方面,卷积运算没有考虑每个特征的重要性,这进一步影响了提取特征的有效性,并最终限制了模型的性能。此外,注意力机制允许模型专注于重要特征,这可以增强特征提取的优势和卷积神经网络捕获详细特征信息的能力。因此,注意力机制在深度学习中得到了广泛的应用,并成功地应用于各个领域。
通过研究卷积运算的内在缺陷和注意力机制的特点,作者认为现有的空间注意力机制从本质上解决了卷积运算的参数共享问题,但仍局限于对空间特征的认知。对于较大的卷积核,现有的空间注意力机制并没有完全解决共享参数的问题。此外,他们无法强调感受野中每个特征的重要性,例如现有的卷积块注意力模块(CBAM)和 Coordinate注意力(CA)。
因此,参考该博客提出了一种新的感受野注意力机制(RFA),它完全解决了卷积核共享参数的问题,并充分考虑了感受野中每个特征的重要性。通过RFA设计的卷积运算(RFAConv)是一种新的卷积运算,可以取代现有神经网络中的标准卷积运算。RFAConv通过添加一些参数和计算开销来提高网络性能。
大量关于Imagnet-1k、MS COCO和VOC的实验已经证明了RFAConv的有效性。作为一种由注意力构建的新型卷积运算,它超过了由CAM、CBAM和CA构建的卷积运算(CAMConv、CBAMConv、CAConv)以及标准卷积运算。
此外,为了解决现有方法提取感受野特征速度慢的问题,提出了一种轻量级操作。在构建RFAConv的过程中,再次设计了CA和CBAM的升级版本,并进行了相关实验。作者认为当前的空间注意力机制应该将注意力放在感受野空间特征上,以促进当前空间注意力机制的发展,并再次增强卷积神经网络架构的优势。
卷积神经网络架构
出色的神经网络架构可以提高不同任务的性能。卷积运算作为卷积神经网络的一种基本运算,推动了人工智能的发展,并为车辆检测、无人机图像、医学等先进的网络模型做出了贡献。He等人认为随着网络深度的增加,该模型将变得难以训练并产生退化现象,因此他们提出了残差连接来创新卷积神经网络架构的设计。Huang等人通过重用特征来解决网络梯度消失问题,增强了特征信息,他们再次创新了卷积神经网络架构。
通过对卷积运算的详细研究,Dai等人认为,具有固定采样位置的卷积运算在一定程度上限制了网络的性能,因此提出了Deformable Conv,通过学习偏移来改变卷积核的采样位置。在Deformable Conv的基础上,再次提出了Deformable Conv V2和Deformable Conv V3,以提高卷积网络的性能。
Zhang等人注意到,组卷积可以减少模型的参数数量和计算开销。然而,少于组内信息的交互将影响最终的网络性能。1×1的卷积可以与信息相互作用。然而,这将带来更多的参数和计算开销,因此他们提出了无参数的“通道Shuffle”操作来与组之间的信息交互。
Ma等人通过实验得出结论,对于参数较少的模型,推理速度不一定更快,对于计算量较小的模型,推理也不一定更快。经过仔细研究提出了Shufflenet V2。
YOLO将输入图像划分为网格,以预测对象的位置和类别。经过不断的研究,已经提出了8个版本的基于YOLO的目标检测器,如YOLOv11、YOLOv11、YOLOv11等。上述卷积神经网络架构已经取得了巨大的成功。然而,它们并没有解决提取特征过程中的参数共享问题。本文的工作从注意力机制开始,从一个新的角度解决卷积参数共享问题。
注意力机制
注意力机制被用作一种提高网络模型性能的技术,使其能够专注于关键特性。注意力机制理论已经在深度学习中建立了一个完整而成熟的体系。Hu等人提出了一种Squeeze-and-Excitation(SE)块,通过压缩特征来聚合全局通道信息,从而获得与每个通道对应的权重。Wang等人认为,当SE与信息交互时,单个通道和权重之间的对应关系是间接的,因此设计了高效通道注Efficient Channel Attention力(ECA),并用自适应kernel大小的一维卷积取代了SE中的全连接(FC)层。Woo等人提出了卷积块注意力模块(CBAM),它结合了通道注意力和空间注意力。作为一个即插即用模块,它可以嵌入卷积神经网络中,以提高网络性能。
尽管SE和CBAM已经提高了网络的性能。Hou等人仍然发现压缩特征在SE和CBAM中丢失了太多信息。因此,他们提出了轻量级Coordinate注意力(CA)来解决SE和CBAM中的问题。Fu等人计了一个空间注意力模块和通道注意力模块,用于扩展全卷积网络(FCN),分别对空间维度和通道维度的语义相关性进行建模。Zhang等人在通道上生成不同尺度的特征图,以建立更有效的通道注意力机制。
本文从一个新的角度解决了标准卷积运算的参数共享问题。这就是将注意力机制结合起来构造卷积运算。尽管目前的注意力机制已经获得了良好的性能,但它们仍然没有关注感受野的空间特征。因此,设计了具有非共享参数的RFA卷积运算,以提高网络的性能。
回顾标准卷积
以标准卷积运算为基础构建卷积神经网络,通过共享参数的滑动窗口提取特征信息,解决了全连接层构建的神经网络的固有问题(即参数数量大、计算开销高)。
设表示输入特征图,其中、和分别表示特征图的通道数、高度和宽度。为了能够清楚地展示卷积核提取特征信息的过程,以为例。提取每个感受野slider的特征信息的卷积运算可以表示如下:
这里,表示在每次卷积slider操作之后获得的值,表示在每个slider内的相应位置处的像素值。表示卷积核,表示卷积核中的参数数量,表示感受野slider的总数。
可以看出,每个slider内相同位置的特征共享相同的参数。因此,标准的卷积运算无法感知不同位置带来的差异信息,这在一定程度上限制了卷积神经网络的性能。
回顾空间注意力
目前,空间注意力机制使用通过学习获得的注意力图来突出每个特征的重要性。与上一节类似,以为例。突出关键特征的空间注意力机制可以简单地表达如下:
这里,表示在加权运算之后获得的值。和分别表示输入特征图和学习注意力图在不同位置的值,是输入特征图的高度和宽度的乘积,表示像素值的总数。一般来说,整个过程可以简单地表示在图1中。
空间注意力与标准卷积
众所周知,将注意力机制引入卷积神经网络可以提高网络的性能。通过标准的卷积运算和对现有空间注意力机制的仔细分析。作者认为空间注意力机制本质上解决了卷积神经网络的固有缺点,即共享参数的问题。
目前,该模型最常见的卷积核大小为1×1和3×3。引入空间注意力机制后用于提取特征的卷积操作是1×1或3×3卷积操作。这个过程可以直观地显示出来。空间注意力机制被插入到1×1卷积运算的前面。通过注意力图对输入特征图进行加权运算(Re-weight“×”),最后通过1×1卷积运算提取感受野的slider特征信息。
整个过程可以简单地表示如下:
这里,卷积核仅表示一个参数值。如果将的值作为一个新的卷积核参数,那么有趣的是,通过1×1卷积运算提取特征时的参数共享问题得到了解决。然而,空间注意力机制的传说到此结束。当空间注意力机制被插入到3×3卷积运算的前面时。具体情况如下:
如上所述,如果取的值。作为一种新的卷积核参数,上述方程完全解决了大规模卷积核的参数共享问题。然而,最重要的一点是,卷积核在每个感受野slider中提取将共享部分特征的特征。换句话说,在每个感受野slider内都会有重叠。
经过仔细分析发现,,…,空间注意力图的权重在每个slider内共享。因此,空间注意机制不能解决大规模卷积核共享参数的问题,因为它们不注意感受野的空间特征。在这种情况下,空间注意力机制是有限的。
创新空间注意力与标准卷积
RFA是为了解决空间注意力机制问题而提出的,创新了空间注意力。使用与RFA相同的思想,一系列空间注意力机制可以再次提高性能。RFA设计的卷积运算可以被视为一种轻量级的即插即用模块,以取代标准卷积,从而提高卷积神经网络的性能。因此,作者认为空间注意力机制和标准卷积在未来将有一个新的春天。
感受野的空间特征:
现在给出感受野空间特征的定义。它是专门为卷积核设计的,并根据kernel大小动态生成,如图2所示,以3×3卷积核为例。
在图2中,“空间特征”表示原始特征图,等于空间特征。“感受野空间特征”表示变换后的特征,该特征由每个感受野slider滑块组成,并且不重叠。也就是说,“感受野空间特征”中的每个3×3大小的slider表示提取原始3×3卷积特征时所有感觉野slider的特征。
感受野注意力卷积(RFA):
关于感受野空间特征,该博客的作者提出了感受野注意(RFA),它不仅强调了感受野slider内各种特征的重要性,而且还关注感受野空间特性,以彻底解决卷积核参数共享的问题。感受野空间特征是根据卷积核的大小动态生成的,因此,RFA是卷积的固定组合,不能脱离卷积运算的帮助,卷积运算同时依赖RFA来提高性能。
因此,作者提出了感受野注意力卷积(RFAConv)。具有3×3大小卷积核的RFAConv的总体结构如图3所示。
目前,提取感受野特征最常用的方法速度较慢,因此经过不断探索提出了一种快速的方法,通过分组卷积来取代原来的方法。
具体来说,根据感受野大小,使用相应的组卷积大小来动态生成展开特征。尽管与原始的无参数方法(如Pytorch提供的nn.Unfld())相比,该方法添加了一些参数,但速度要快得多。
注意:正如在上一节中提到的,当原始的3×3卷积核提取特征时,感受野空间特征中的每个3×3大小的窗口表示所有感受野滑块的特征。但在快速分组卷积提取感受野特征后,由于原始方法太慢,它们会被映射到新的特征中。
最近的一些工作已经证明信息交互可以提高网络性能。类似地,对于RFAConv,与感受野特征信息交互以学习注意力图可以提高网络性能,但与每个感受野特征交互将带来额外的计算开销。为了确保少量的计算开销和参数数量,通过探索使用AvgPool池化每个感受野特征的全局信息,然后通过1×1组卷积运算与信息交互。最后,softmax用于强调感受野特征中每个特征的重要性。通常,RFA的计算可以表示为:
表示分组卷积,表示卷积核的大小,代表规范化,表示输入特征图,是通过将注意力图与变换的感受野空间特征相乘而获得的。
与CBAM和CA不同,RFA可以为每个感受野特征生成注意力图。标准卷积受到卷积神经网络性能的限制,因为共享参数的卷积运算对位置带来的差异信息不敏感。RFA完全可以解决这个问题,具体细节如下:
由于RFA获得的特征图是“调整形状”后不重叠的感受野空间特征,因此通过池化每个感受野滑块的特征信息来学习学习的注意力图。换句话说,RFA学习的注意力图不再在每个感受野slider内共享,并且是有效的。这完全解决了现有的CA和CBAM对大尺寸kernel的注意力机制中的参数共享问题。
同时,RFA给标准卷积核带来了相当大的好处,但调整形状后,特征的高度和宽度是k倍,需要进行k×k的stride卷积运算,才能提取特征信息。RFAConv创新了标准卷积运算。
此外,空间注意力机制将得到升级,因为作者认为现有的空间注意力机制应该专注于感受野空间特征,以提高网络的性能。众所周知,基于自注意力机制的网络模型取得了巨大的成功,因为它解决了卷积参数共享的问题,并对远程信息进行了建模,但基于自注意力机理的方法给模型带来了巨大的计算开销和复杂性。作者认为通过将一些现有空间注意力机制的注意力放在感受野空间特征中,它以轻量级的方式解决了自注意力机制的问题。
答案如下:
将关注感受野空间特征的空间注意力与卷积相匹配,完全解决了卷积参数共享的问题;
当前的空间注意力机制本身具有考虑远距离信息的特点,它们通过全局平均池化或全局最大池化来获得全局信息,这在一定程度上考虑了远距离信息。
为此,作者设计了一种新的CBAM和CA,称为RFACBAM和RFACA,它专注于感受野的空间特征。与RFA类似,使用stride为k的k×k的最终卷积运算来提取特征信息,具体结构如图4和图5所示,将这2种新的卷积方法称为RFCBAMConv和RFCAConv。比较原始的CBAM,使用SE注意力来代替RFCBAM中的CAM。因为这样可以减少计算开销。
此外,在RFCBAM中,通道和空间注意力不是在单独的步骤中执行的,因为通道和空间注意力是同时加权的,从而允许在每个通道上获得的注意力图是不同的。
9.系统功能展示
图9.1.系统支持检测结果表格显示
图9.2.系统支持置信度和IOU阈值手动调节
图9.3.系统支持自定义加载权重文件best.pt(需要你通过步骤5中训练获得)
图9.4.系统支持摄像头实时识别
图9.5.系统支持图片识别
图9.6.系统支持视频识别
图9.7.系统支持识别结果文件自动保存
图9.8.系统支持Excel导出检测结果数据
10. YOLOv11核心改进源码讲解
10.1 SMPConv.py
以下是经过精简和注释的核心代码部分,保留了主要的功能和结构,同时添加了详细的中文注释。
import torch
import torch.nn as nn
import torch.nn.functional as F
from timm.layers import trunc_normal_
# 定义相对位置的函数
def rel_pos(kernel_size):
# 生成一个 [-1, 1] 的线性空间,作为卷积核的坐标
tensors = [torch.linspace(-1, 1, steps=kernel_size) for _ in range(2)]
kernel_coord = torch.stack(torch.meshgrid(*tensors), dim=-0)
kernel_coord = kernel_coord.unsqueeze(0) # 增加一个维度
return kernel_coord
# 定义自定义卷积层 SMPConv
class SMPConv(nn.Module):
def __init__(self, planes, kernel_size, n_points, stride, padding, groups):
super().__init__()
self.planes = planes
self.kernel_size = kernel_size
self.n_points = n_points
self.init_radius = 2 * (2/kernel_size)
# 初始化卷积核坐标
kernel_coord = rel_pos(kernel_size)
self.register_buffer('kernel_coord', kernel_coord)
# 初始化权重坐标
weight_coord = torch.empty(1, n_points, 2)
nn.init.trunc_normal_(weight_coord, std=0.2, a=-1., b=1.)
self.weight_coord = nn.Parameter(weight_coord)
# 初始化半径
self.radius = nn.Parameter(torch.empty(1, n_points).unsqueeze(-1).unsqueeze(-1))
self.radius.data.fill_(value=self.init_radius)
# 初始化权重
weights = torch.empty(1, planes, n_points)
trunc_normal_(weights, std=.02)
self.weights = nn.Parameter(weights)
def forward(self, x):
# 生成卷积核并进行卷积操作
kernels = self.make_kernels().unsqueeze(1)
x = x.contiguous()
kernels = kernels.contiguous()
# 根据输入数据类型选择合适的卷积实现
if x.dtype == torch.float32:
x = _DepthWiseConv2dImplicitGEMMFP32.apply(x, kernels)
elif x.dtype == torch.float16:
x = _DepthWiseConv2dImplicitGEMMFP16.apply(x, kernels)
else:
raise TypeError("Only support fp32 and fp16, get {}".format(x.dtype))
return x
def make_kernels(self):
# 计算卷积核
diff = self.weight_coord.unsqueeze(-2) - self.kernel_coord.reshape(1, 2, -1).transpose(1, 2) # [1, n_points, kernel_size^2, 2]
diff = diff.transpose(2, 3).reshape(1, self.n_points, 2, self.kernel_size, self.kernel_size)
diff = F.relu(1 - torch.sum(torch.abs(diff), dim=2) / self.radius) # [1, n_points, kernel_size, kernel_size]
# 计算最终的卷积核
kernels = torch.matmul(self.weights, diff.reshape(1, self.n_points, -1)) # [1, planes, kernel_size*kernel_size]
kernels = kernels.reshape(1, self.planes, *self.kernel_coord.shape[2:]) # [1, planes, kernel_size, kernel_size]
kernels = kernels.squeeze(0)
kernels = torch.flip(kernels.permute(0, 2, 1), dims=(1,)) # 调整卷积核的维度
return kernels
# 定义 SMPCNN 类
class SMPCNN(nn.Module):
def __init__(self, in_channels, out_channels, kernel_size, stride, groups, n_points=None):
super().__init__()
padding = kernel_size // 2
self.smp = conv_bn(in_channels=in_channels, out_channels=out_channels, kernel_size=kernel_size,
stride=stride, padding=padding, groups=groups, n_points=n_points)
self.small_kernel = 5
self.small_conv = nn.Conv2d(in_channels, out_channels, self.small_kernel, stride, self.small_kernel // 2, groups, bias=False)
def forward(self, inputs):
# 前向传播,结合两种卷积结果
out = self.smp(inputs)
out += self.small_conv(inputs)
return out
# 定义 SMPBlock 类
class SMPBlock(nn.Module):
def __init__(self, in_channels, dw_channels, lk_size, drop_path):
super().__init__()
self.pw1 = nn.Sequential(
nn.Conv2d(in_channels, dw_channels, kernel_size=1, stride=1, padding=0, bias=False),
nn.BatchNorm2d(dw_channels),
nn.ReLU()
)
self.large_kernel = SMPCNN(in_channels=dw_channels, out_channels=dw_channels, kernel_size=lk_size,
stride=1, groups=dw_channels)
self.pw2 = nn.Conv2d(dw_channels, in_channels, kernel_size=1, stride=1, padding=0, bias=False)
self.drop_path = nn.Identity() # 可以根据需要替换为 DropPath
def forward(self, x):
out = self.pw1(x)
out = self.large_kernel(out)
out = self.pw2(out)
return x + self.drop_path(out) # 残差连接
代码分析
-
SMPConv: 自定义卷积层,使用相对位置编码和动态生成卷积核。通过
make_kernels
方法生成卷积核,并根据输入数据类型选择不同的卷积实现。 -
SMPCNN: 结合了自定义卷积层和小卷积层的网络结构,能够同时利用大卷积核和小卷积核的特性。
-
SMPBlock: 主要模块,包含两个逐点卷积和一个大卷积层,使用残差连接来增强特征传递。
这些核心部分构成了整个模型的基础,能够实现复杂的卷积操作和特征提取。
该文件定义了一个名为 SMPConv
的卷积模块以及其他相关的神经网络组件,主要用于深度学习中的卷积操作。首先,文件导入了必要的 PyTorch 库和一些自定义模块,接着定义了一些辅助函数和类。
在 SMPConv
类中,构造函数初始化了一些参数,包括输出通道数、卷积核大小、点数、步幅、填充和分组数。rel_pos
函数生成了一个相对位置的张量,用于卷积核的坐标。接着,SMPConv
类注册了卷积核坐标和权重坐标,并使用截断正态分布初始化权重。
forward
方法中,输入数据通过 make_kernels
方法生成卷积核,并根据输入数据的类型选择不同的深度可分离卷积实现。make_kernels
方法计算了权重坐标与卷积核坐标之间的差异,并使用 ReLU 激活函数处理这些差异,最终生成卷积核。
此外,radius_clip
方法用于限制半径的范围,确保其在合理的范围内。
文件中还定义了一些辅助函数,例如 get_conv2d
用于根据条件选择使用 SMPConv
或标准的 Conv2d
,get_bn
用于选择批归一化的实现(同步或普通),以及 conv_bn
和 conv_bn_relu
用于构建包含卷积、批归一化和激活函数的序列模块。
SMPCNN
类则结合了 SMPConv
和一个小卷积核的卷积层,形成一个更复杂的卷积网络结构。SMPCNN_ConvFFN
类实现了一个前馈网络,包含了两层卷积和一个激活函数,最后通过 DropPath 进行随机丢弃以增强模型的鲁棒性。
最后,SMPBlock
类实现了一个包含多个卷积层和激活函数的模块,利用了之前定义的组件,形成了一个更高层次的网络结构。整体来看,该文件实现了一个灵活且可扩展的卷积神经网络模块,适用于各种深度学习任务。
10.2 fadc.py
以下是代码中最核心的部分,并附上详细的中文注释:
import torch
import torch.nn as nn
import torch.nn.functional as F
class OmniAttention(nn.Module):
def __init__(self, in_planes, out_planes, kernel_size, groups=1, reduction=0.0625, kernel_num=4, min_channel=16):
super(OmniAttention, self).__init__()
# 计算注意力通道数
attention_channel = max(int(in_planes * reduction), min_channel)
self.kernel_size = kernel_size
self.kernel_num = kernel_num
self.temperature = 1.0 # 温度参数,用于调整注意力分布
# 自适应平均池化
self.avgpool = nn.AdaptiveAvgPool2d(1)
# 全连接层,用于生成通道注意力
self.fc = nn.Conv2d(in_planes, attention_channel, 1, bias=False)
self.bn = nn.BatchNorm2d(attention_channel) # 批归一化
self.relu = nn.ReLU(inplace=True) # ReLU激活函数
# 生成通道注意力的全连接层
self.channel_fc = nn.Conv2d(attention_channel, in_planes, 1, bias=True)
# 根据输入和输出通道数决定过滤器注意力的计算方式
if in_planes == groups and in_planes == out_planes: # 深度卷积
self.func_filter = self.skip
else:
self.filter_fc = nn.Conv2d(attention_channel, out_planes, 1, bias=True)
self.func_filter = self.get_filter_attention
# 根据卷积核大小决定空间注意力的计算方式
if kernel_size == 1: # 点卷积
self.func_spatial = self.skip
else:
self.spatial_fc = nn.Conv2d(attention_channel, kernel_size * kernel_size, 1, bias=True)
self.func_spatial = self.get_spatial_attention
# 根据卷积核数量决定核注意力的计算方式
if kernel_num == 1:
self.func_kernel = self.skip
else:
self.kernel_fc = nn.Conv2d(attention_channel, kernel_num, 1, bias=True)
self.func_kernel = self.get_kernel_attention
self._initialize_weights() # 初始化权重
def _initialize_weights(self):
# 初始化卷积层和批归一化层的权重
for m in self.modules():
if isinstance(m, nn.Conv2d):
nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
if m.bias is not None:
nn.init.constant_(m.bias, 0)
if isinstance(m, nn.BatchNorm2d):
nn.init.constant_(m.weight, 1)
nn.init.constant_(m.bias, 0)
@staticmethod
def skip(_):
return 1.0 # 跳过操作,返回1.0
def get_channel_attention(self, x):
# 计算通道注意力
channel_attention = torch.sigmoid(self.channel_fc(x).view(x.size(0), -1, 1, 1) / self.temperature)
return channel_attention
def get_filter_attention(self, x):
# 计算过滤器注意力
filter_attention = torch.sigmoid(self.filter_fc(x).view(x.size(0), -1, 1, 1) / self.temperature)
return filter_attention
def get_spatial_attention(self, x):
# 计算空间注意力
spatial_attention = self.spatial_fc(x).view(x.size(0), 1, 1, 1, self.kernel_size, self.kernel_size)
spatial_attention = torch.sigmoid(spatial_attention / self.temperature)
return spatial_attention
def get_kernel_attention(self, x):
# 计算核注意力
kernel_attention = self.kernel_fc(x).view(x.size(0), -1, 1, 1, 1, 1)
kernel_attention = F.softmax(kernel_attention / self.temperature, dim=1)
return kernel_attention
def forward(self, x):
# 前向传播
x = self.avgpool(x) # 自适应平均池化
x = self.fc(x) # 全连接层
x = self.bn(x) # 批归一化
x = self.relu(x) # ReLU激活
return self.func_channel(x), self.func_filter(x), self.func_spatial(x), self.func_kernel(x) # 返回四种注意力
class AdaptiveDilatedConv(nn.Module):
"""自适应膨胀卷积层的封装"""
def __init__(self, in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True):
super(AdaptiveDilatedConv, self).__init__()
# 初始化卷积层参数
self.conv = nn.Conv2d(in_channels, out_channels, kernel_size, stride=stride, padding=padding, dilation=dilation, groups=groups, bias=bias)
self.omni_attention = OmniAttention(in_planes=in_channels, out_planes=out_channels, kernel_size=kernel_size)
def forward(self, x):
# 前向传播
attention_weights = self.omni_attention(x) # 计算注意力权重
x = self.conv(x) # 卷积操作
return x * attention_weights # 加权输出
代码核心部分解释:
-
OmniAttention 类:实现了多种注意力机制,包括通道注意力、过滤器注意力、空间注意力和核注意力。通过自适应平均池化和全连接层来生成注意力权重,并在前向传播中返回这些权重。
-
AdaptiveDilatedConv 类:封装了自适应膨胀卷积的实现,结合了卷积操作和注意力机制。通过使用
OmniAttention
类来计算输入特征的注意力权重,并将其应用于卷积输出,以增强特征表达能力。
这些部分是实现自适应卷积和注意力机制的核心,能够有效提升模型的性能。
这个程序文件 fadc.py
是一个用于深度学习的模块,主要实现了一些复杂的卷积操作和注意力机制,适用于图像处理任务。以下是对文件中主要部分的详细说明。
首先,文件导入了必要的库,包括 PyTorch 和一些深度学习相关的模块。程序的开头包含版权信息,表明代码的来源和使用条款。
接下来,定义了一个名为 OmniAttention
的类,它是一个注意力机制模块。这个模块的构造函数接收多个参数,包括输入和输出通道数、卷积核大小、分组数、缩减比例等。它的主要功能是计算通道注意力、过滤器注意力、空间注意力和卷积核注意力。通过使用自适应平均池化、卷积层和批归一化等操作,OmniAttention
可以有效地提取特征并增强模型的表达能力。
OmniAttention
类中有几个静态方法和实例方法,分别用于计算不同类型的注意力。forward
方法是这个模块的前向传播函数,输入特征经过处理后输出多个注意力权重。
接下来,定义了一个名为 generate_laplacian_pyramid
的函数,用于生成拉普拉斯金字塔。这个函数通过逐层下采样输入张量,并计算每层的拉普拉斯差分,返回一个包含不同分辨率特征的金字塔结构。这在图像处理和特征提取中非常有用。
然后,定义了一个名为 FrequencySelection
的类,它用于选择特定频率的特征。这个类的构造函数接收多个参数,包括输入通道数、频率列表、空间卷积的参数等。它的主要功能是通过不同的池化和卷积操作提取频率特征,并根据指定的激活函数对特征进行处理。
FrequencySelection
类的 forward
方法实现了前向传播,输入特征经过处理后返回选择的频率特征。
接下来,定义了一个名为 AdaptiveDilatedConv
的类,它是一个可调节的膨胀卷积层,继承自 ModulatedDeformConv2d
。这个类的构造函数接收多个参数,包括输入输出通道、卷积核大小、步幅、填充方式等。它的主要功能是通过动态调整卷积核的偏移量和权重来实现自适应卷积。
在 AdaptiveDilatedConv
中,使用了 OmniAttention
模块来增强卷积操作的特征提取能力。它的 forward
方法实现了卷积操作,并结合了注意力机制,以提高模型的性能。
最后,定义了一个名为 AdaptiveDilatedDWConv
的类,类似于 AdaptiveDilatedConv
,但它是一个深度可分离卷积层。这个类的构造函数和 forward
方法与前面的类类似,主要区别在于它处理的卷积类型和参数设置。
整体来看,这个程序文件实现了多种复杂的卷积操作和注意力机制,适用于需要高效特征提取的深度学习任务,尤其是在图像处理领域。通过使用自适应卷积和频率选择技术,模型能够更好地捕捉输入数据中的重要特征。
10.3 rep_block.py
以下是保留的核心代码部分,并附上详细的中文注释:
import torch
import torch.nn as nn
import torch.nn.functional as F
# 定义一个卷积层和批归一化层的组合
def conv_bn(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1):
# 创建卷积层
conv_layer = nn.Conv2d(in_channels=in_channels, out_channels=out_channels, kernel_size=kernel_size,
stride=stride, padding=padding, dilation=dilation, groups=groups,
bias=False) # 不使用偏置,因为后面会加批归一化
# 创建批归一化层
bn_layer = nn.BatchNorm2d(num_features=out_channels, affine=True)
# 将卷积层和批归一化层组合成一个顺序容器
return nn.Sequential(conv_layer, bn_layer)
# 定义一个多分支块
class DiverseBranchBlock(nn.Module):
def __init__(self, in_channels, out_channels, kernel_size, stride=1, padding=None, dilation=1, groups=1):
super(DiverseBranchBlock, self).__init__()
# 计算填充,如果未提供则自动计算
if padding is None:
padding = kernel_size // 2 # 确保填充为核大小的一半
assert padding == kernel_size // 2 # 确保填充正确
# 定义主分支的卷积和批归一化
self.dbb_origin = conv_bn(in_channels=in_channels, out_channels=out_channels, kernel_size=kernel_size,
stride=stride, padding=padding, dilation=dilation, groups=groups)
# 定义平均池化分支
self.dbb_avg = nn.Sequential(
nn.Conv2d(in_channels=in_channels, out_channels=out_channels, kernel_size=1, stride=1, padding=0, groups=groups, bias=False),
nn.BatchNorm2d(out_channels),
nn.AvgPool2d(kernel_size=kernel_size, stride=stride, padding=0)
)
# 定义1x1卷积分支
self.dbb_1x1_kxk = nn.Sequential(
nn.Conv2d(in_channels=in_channels, out_channels=out_channels, kernel_size=1, stride=stride, padding=0, groups=groups, bias=False),
nn.BatchNorm2d(out_channels),
nn.Conv2d(in_channels=out_channels, out_channels=out_channels, kernel_size=kernel_size, stride=stride, padding=0, groups=groups, bias=False),
nn.BatchNorm2d(out_channels)
)
def forward(self, inputs):
# 计算主分支的输出
out = self.dbb_origin(inputs)
# 计算平均池化分支的输出
out += self.dbb_avg(inputs)
# 计算1x1卷积分支的输出
out += self.dbb_1x1_kxk(inputs)
return out # 返回最终的输出
# 示例:创建一个DiverseBranchBlock实例
# block = DiverseBranchBlock(in_channels=64, out_channels=128, kernel_size=3)
# output = block(torch.randn(1, 64, 32, 32)) # 输入一个随机张量
代码说明:
-
conv_bn函数:这个函数创建一个卷积层和一个批归一化层的组合,通常用于卷积神经网络中,以便在卷积后进行归一化处理。
-
DiverseBranchBlock类:这是一个多分支卷积块,包含多个分支(主分支、平均池化分支和1x1卷积分支),用于提取特征。每个分支都使用卷积和批归一化来处理输入。
-
forward方法:该方法定义了前向传播的过程,计算每个分支的输出并将它们相加,最终返回结果。
注意事项:
- 代码中省略了许多细节和辅助函数,这些函数用于处理卷积核的融合和其他操作。保留的部分是实现多分支卷积块的核心逻辑。
这个文件 rep_block.py
定义了一些用于深度学习的模块,主要是不同类型的卷积块(DiverseBranchBlock
、WideDiverseBranchBlock
和 DeepDiverseBranchBlock
),这些模块可以在卷积神经网络中使用,特别是在处理图像时。以下是对文件中主要部分的详细说明。
首先,文件导入了必要的库,包括 PyTorch 的核心库和一些功能模块。然后定义了一些用于卷积和批归一化的转换函数,这些函数用于在不同的卷积层之间进行权重和偏置的融合与转换。
transI_fusebn
函数用于将卷积层的权重与批归一化层的参数融合,返回融合后的卷积核和偏置。其他转换函数(如 transII_addbranch
、transIII_1x1_kxk
等)则实现了不同的卷积操作和参数合并,帮助构建复杂的卷积结构。
接下来,定义了几个类,首先是 IdentityBasedConv1x1
,这是一个自定义的 1x1 卷积层,能够保持输入的身份特征。它通过在卷积核中添加单位矩阵来实现这一点。BNAndPadLayer
类则结合了批归一化和填充操作,允许在卷积操作后进行批归一化,并在必要时对输出进行填充。
DiverseBranchBlock
是一个复杂的卷积块,包含多个分支,允许在同一层中使用不同的卷积核和操作。它的构造函数根据输入参数设置不同的卷积层,包括常规卷积、1x1 卷积和平均池化等。该类还提供了获取等效卷积核和偏置的功能,以便在部署时使用。
DiverseBranchBlockNOAct
和 DeepDiverseBranchBlock
是对 DiverseBranchBlock
的扩展,分别实现了不带激活函数的卷积块和更深层次的分支结构。它们都遵循类似的设计模式,允许在构造时选择不同的卷积和归一化层。
WideDiverseBranchBlock
类则进一步扩展了功能,支持在卷积操作中使用非对称卷积核,能够同时进行水平和垂直卷积,增强了模型的表达能力。
这些模块的设计旨在提高卷积神经网络的灵活性和性能,尤其是在处理复杂图像任务时。通过将多个卷积分支组合在一起,这些模块能够提取更丰富的特征,从而提高模型的准确性和鲁棒性。
10.4 wtconv2d.py
以下是代码中最核心的部分,并附上详细的中文注释:
import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.autograd import Function
import pywt
# 创建小波滤波器
def create_wavelet_filter(wave, in_size, out_size, type=torch.float):
# 使用PyWavelets库创建小波对象
w = pywt.Wavelet(wave)
# 获取小波的分解滤波器(高通和低通)
dec_hi = torch.tensor(w.dec_hi[::-1], dtype=type) # 高通滤波器
dec_lo = torch.tensor(w.dec_lo[::-1], dtype=type) # 低通滤波器
# 创建分解滤波器的组合
dec_filters = torch.stack([
dec_lo.unsqueeze(0) * dec_lo.unsqueeze(1), # LL
dec_lo.unsqueeze(0) * dec_hi.unsqueeze(1), # LH
dec_hi.unsqueeze(0) * dec_lo.unsqueeze(1), # HL
dec_hi.unsqueeze(0) * dec_hi.unsqueeze(1) # HH
], dim=0)
# 重复滤波器以适应输入通道数
dec_filters = dec_filters[:, None].repeat(in_size, 1, 1, 1)
# 获取重构滤波器
rec_hi = torch.tensor(w.rec_hi[::-1], dtype=type).flip(dims=[0])
rec_lo = torch.tensor(w.rec_lo[::-1], dtype=type).flip(dims=[0])
# 创建重构滤波器的组合
rec_filters = torch.stack([
rec_lo.unsqueeze(0) * rec_lo.unsqueeze(1), # LL
rec_lo.unsqueeze(0) * rec_hi.unsqueeze(1), # LH
rec_hi.unsqueeze(0) * rec_lo.unsqueeze(1), # HL
rec_hi.unsqueeze(0) * rec_hi.unsqueeze(1) # HH
], dim=0)
# 重复滤波器以适应输出通道数
rec_filters = rec_filters[:, None].repeat(out_size, 1, 1, 1)
return dec_filters, rec_filters
# 小波变换
def wavelet_transform(x, filters):
b, c, h, w = x.shape # 获取输入的形状
pad = (filters.shape[2] // 2 - 1, filters.shape[3] // 2 - 1) # 计算填充
# 使用小波滤波器进行卷积
x = F.conv2d(x, filters.to(x.dtype).to(x.device), stride=2, groups=c, padding=pad)
x = x.reshape(b, c, 4, h // 2, w // 2) # 重塑输出
return x
# 逆小波变换
def inverse_wavelet_transform(x, filters):
b, c, _, h_half, w_half = x.shape # 获取输入的形状
pad = (filters.shape[2] // 2 - 1, filters.shape[3] // 2 - 1) # 计算填充
x = x.reshape(b, c * 4, h_half, w_half) # 重塑输入
# 使用小波滤波器进行转置卷积
x = F.conv_transpose2d(x, filters.to(x.dtype).to(x.device), stride=2, groups=c, padding=pad)
return x
# 定义小波变换类
class WaveletTransform(Function):
@staticmethod
def forward(ctx, input, filters):
ctx.filters = filters # 保存滤波器
with torch.no_grad():
x = wavelet_transform(input, filters) # 执行小波变换
return x
@staticmethod
def backward(ctx, grad_output):
grad = inverse_wavelet_transform(grad_output, ctx.filters) # 计算梯度
return grad, None
# 定义逆小波变换类
class InverseWaveletTransform(Function):
@staticmethod
def forward(ctx, input, filters):
ctx.filters = filters # 保存滤波器
with torch.no_grad():
x = inverse_wavelet_transform(input, filters) # 执行逆小波变换
return x
@staticmethod
def backward(ctx, grad_output):
grad = wavelet_transform(grad_output, ctx.filters) # 计算梯度
return grad, None
# 定义小波卷积层
class WTConv2d(nn.Module):
def __init__(self, in_channels, out_channels, kernel_size=5, stride=1, bias=True, wt_levels=1, wt_type='db1'):
super(WTConv2d, self).__init__()
assert in_channels == out_channels # 输入和输出通道数必须相同
self.in_channels = in_channels
self.wt_levels = wt_levels
self.stride = stride
# 创建小波滤波器
self.wt_filter, self.iwt_filter = create_wavelet_filter(wt_type, in_channels, in_channels, torch.float)
self.wt_filter = nn.Parameter(self.wt_filter, requires_grad=False) # 小波滤波器
self.iwt_filter = nn.Parameter(self.iwt_filter, requires_grad=False) # 逆小波滤波器
# 初始化小波变换和逆小波变换函数
self.wt_function = wavelet_transform_init(self.wt_filter)
self.iwt_function = inverse_wavelet_transform_init(self.iwt_filter)
# 基础卷积层
self.base_conv = nn.Conv2d(in_channels, in_channels, kernel_size, padding='same', stride=1, groups=in_channels, bias=bias)
self.base_scale = _ScaleModule([1, in_channels, 1, 1]) # 缩放模块
# 小波卷积层
self.wavelet_convs = nn.ModuleList(
[nn.Conv2d(in_channels * 4, in_channels * 4, kernel_size, padding='same', stride=1, groups=in_channels * 4, bias=False) for _ in range(self.wt_levels)]
)
self.wavelet_scale = nn.ModuleList(
[_ScaleModule([1, in_channels * 4, 1, 1], init_scale=0.1) for _ in range(self.wt_levels)]
)
def forward(self, x):
# 前向传播过程
x_ll_in_levels = [] # 存储低频分量
x_h_in_levels = [] # 存储高频分量
shapes_in_levels = [] # 存储形状信息
curr_x_ll = x # 当前低频分量
# 小波变换
for i in range(self.wt_levels):
curr_shape = curr_x_ll.shape
shapes_in_levels.append(curr_shape)
if (curr_shape[2] % 2 > 0) or (curr_shape[3] % 2 > 0):
curr_pads = (0, curr_shape[3] % 2, 0, curr_shape[2] % 2) # 处理奇数维度
curr_x_ll = F.pad(curr_x_ll, curr_pads)
curr_x = self.wt_function(curr_x_ll) # 执行小波变换
curr_x_ll = curr_x[:, :, 0, :, :] # 取低频分量
shape_x = curr_x.shape
curr_x_tag = curr_x.reshape(shape_x[0], shape_x[1] * 4, shape_x[3], shape_x[4]) # 重塑
curr_x_tag = self.wavelet_scale[i](self.wavelet_convs[i](curr_x_tag)) # 小波卷积
curr_x_tag = curr_x_tag.reshape(shape_x) # 重塑回原形状
x_ll_in_levels.append(curr_x_tag[:, :, 0, :, :]) # 存储低频分量
x_h_in_levels.append(curr_x_tag[:, :, 1:4, :, :]) # 存储高频分量
next_x_ll = 0 # 初始化下一个低频分量
# 逆小波变换
for i in range(self.wt_levels - 1, -1, -1):
curr_x_ll = x_ll_in_levels.pop() # 取出低频分量
curr_x_h = x_h_in_levels.pop() # 取出高频分量
curr_shape = shapes_in_levels.pop() # 取出形状信息
curr_x_ll = curr_x_ll + next_x_ll # 叠加低频分量
curr_x = torch.cat([curr_x_ll.unsqueeze(2), curr_x_h], dim=2) # 合并低频和高频分量
next_x_ll = self.iwt_function(curr_x) # 执行逆小波变换
next_x_ll = next_x_ll[:, :, :curr_shape[2], :curr_shape[3]] # 修剪到原始形状
x_tag = next_x_ll # 最终输出的低频分量
assert len(x_ll_in_levels) == 0 # 确保所有低频分量已处理
x = self.base_scale(self.base_conv(x)) # 基础卷积和缩放
x = x + x_tag # 加上小波变换的结果
return x # 返回最终输出
# 定义缩放模块
class _ScaleModule(nn.Module):
def __init__(self, dims, init_scale=1.0, init_bias=0):
super(_ScaleModule, self).__init__()
self.dims = dims
self.weight = nn.Parameter(torch.ones(*dims) * init_scale) # 初始化权重
self.bias = None # 偏置未使用
def forward(self, x):
return torch.mul(self.weight, x) # 进行缩放
代码核心部分解释:
- 小波滤波器创建:
create_wavelet_filter
函数使用 PyWavelets 库生成小波的分解和重构滤波器。 - 小波变换与逆变换:
wavelet_transform
和inverse_wavelet_transform
函数分别实现小波变换和逆变换的过程,利用卷积和转置卷积。 - 自定义的函数类:
WaveletTransform
和InverseWaveletTransform
类实现了小波变换的前向和反向传播,便于在训练过程中使用。 - 小波卷积层:
WTConv2d
类定义了一个卷积层,结合小波变换和基础卷积,能够处理输入数据并输出经过小波变换的结果。 - 缩放模块:
_ScaleModule
类用于对卷积结果进行缩放,提供了灵活的参数调整。
这些核心部分构成了一个小波卷积神经网络的基础,能够有效地处理图像数据并提取特征。
这个程序文件 wtconv2d.py
实现了一个基于小波变换的二维卷积层,主要用于图像处理和特征提取。程序使用了 PyTorch 框架,并结合了小波变换的概念来增强卷积操作的能力。
首先,程序导入了必要的库,包括 PyTorch 的核心库、神经网络模块、功能模块,以及用于小波变换的 pywt
库。接着,定义了一个函数 create_wavelet_filter
,用于生成小波变换的滤波器。这些滤波器包括了小波的分解滤波器和重构滤波器,分别用于将输入信号分解为低频和高频成分,以及将这些成分重构回原始信号。
接下来,定义了两个函数 wavelet_transform
和 inverse_wavelet_transform
,分别用于执行小波变换和逆小波变换。小波变换通过对输入张量进行卷积操作,将其分解为多个子带,而逆小波变换则是将这些子带合并回原始信号。
程序中还定义了两个类 WaveletTransform
和 InverseWaveletTransform
,这两个类继承自 Function
,实现了小波变换和逆变换的前向和反向传播逻辑。它们的 forward
方法调用之前定义的变换函数,而 backward
方法则实现了梯度的计算。
wavelet_transform_init
和 inverse_wavelet_transform_init
函数用于初始化小波变换和逆变换的应用函数。
核心的 WTConv2d
类继承自 nn.Module
,实现了自定义的卷积层。构造函数中,首先检查输入和输出通道数是否相等,然后创建小波滤波器和逆滤波器,并将其设置为不可训练的参数。接着,定义了基础卷积层和多个小波卷积层,并根据需要设置步幅。
在 forward
方法中,首先进行小波变换,提取低频和高频成分。然后,依次通过小波卷积层处理这些成分,并在每一层中进行必要的形状调整。最后,进行逆小波变换,将处理后的信号合并回原始的空间维度。最终,输出结果经过基础卷积层和缩放模块的处理。
最后,定义了一个 _ScaleModule
类,用于实现简单的缩放操作,通过可训练的权重对输入进行缩放。
整体来看,这个程序文件实现了一个复杂的卷积操作,结合了小波变换的优势,可以在图像处理任务中提取多尺度特征,适用于各种深度学习应用。
注意:由于此博客编辑较早,上面“10.YOLOv11核心改进源码讲解”中部分代码可能会优化升级,仅供参考学习,以“11.完整训练+Web前端界面+200+种全套创新点源码、数据集获取(由于版权原因,本博客仅提供【原始博客的链接】,原始博客提供下载链接)”的内容为准。
11.完整训练+Web前端界面+200+种全套创新点源码、数据集获取(由于版权原因,本博客仅提供【原始博客的链接】,原始博客提供下载链接)
参考原始博客1: https://gitee.com/Vision-Studios/Golf-Ball-Detection77
参考原始博客2: https://github.com/Qunmasj-Vision-Studio/Golf-Ball-Detection77