1 训练自己的数据集
在github搜索ultralytics并下载。
GitHub - ultralytics/ultralytics: Ultralytics YOLO11 ????
环境配置不再赘述,本地配置自行搜索教程,若使用云服务器配置更为简单。
数据标注
pip install labelimg
启动标注工具
labelimg
标注格式设置为yolo
数据集划分比例 train:val:test 建议8:1:1 or 7:2:1
ultralytics提供了从小到大的五个v11模型,一般默认使用yolo11n
并且没有提供训练脚本,你可以再命令行指定训练参数,或创建train.py,将参数提前设置好。
tips:
- 若想训练yolo11n,yaml文件指定为yolo11n.yaml,若想训练yolo11s,yaml文件指定为yolo11s.yaml,以此类推
- 若不想加载预训练权重 ,model.load('') # loading pretrain weights保持注释状态,加载预训练权重的话,权重与yaml文件对应。
- Windows系统中将workers设置为大于1的值可能会报错
- 需要修改为自己的路径
import warnings
warnings.filterwarnings('ignore')
from ultralytics import YOLO
if __name__ == '__main__':
model = YOLO('./ultralytics/cfg/models/11/yolo11s.yaml')
# model.load('') # loading pretrain weights
model.train(data='./dataset/pest.yaml',
cache=False,
imgsz=640,
epochs=150,
batch=8,
close_mosaic=0,
workers=11,
# device='0',
optimizer='SGD', # using SGD
patience=50, # close earlystop
# resume=True, # 断点续训,YOLO初始化时选择last.pt
# amp=False, # close amp
# fraction=0.2,
project='runs/train',
name='exp',
)
训练完成后,进行验证
若你没有测试集,split需设置为val
import warnings
warnings.filterwarnings('ignore')
from ultralytics import YOLO
if __name__ == '__main__':
model = YOLO('./runs/train/exp/weights/best.pt')
model.val(data='./dataset/pest.yaml',
split='test',
imgsz=640,
batch=16,
# iou=0.7,
# rect=False,
# save_json=True, # if you need to cal coco metrice
project='runs/test',
name='exp',
)
在本人的数据集中,yolo11实现了优于目前大部分主流模型的性能。
2 YOLO11网络解析
yolo11网络的yaml文件
# Ultralytics YOLO ????, AGPL-3.0 license
# YOLO11 object detection model with P3-P5 outputs. For Usage examples see https://docs.ultralytics.com/tasks/detect
# Parameters
nc: 80 # number of classes
scales: # model compound scaling constants, i.e. 'model=yolo11n.yaml' will call yolo11.yaml with scale 'n'
# [depth, width, max_channels]
n: [0.50, 0.25, 1024] # summary: 319 layers, 2624080 parameters, 2624064 gradients, 6.6 GFLOPs
s: [0.50, 0.50, 1024] # summary: 319 layers, 9458752 parameters, 9458736 gradients, 21.7 GFLOPs
m: [0.50, 1.00, 512] # summary: 409 layers, 20114688 parameters, 20114672 gradients, 68.5 GFLOPs
l: [1.00, 1.00, 512] # summary: 631 layers, 25372160 parameters, 25372144 gradients, 87.6 GFLOPs
x: [1.00, 1.50, 512] # summary: 631 layers, 56966176 parameters, 56966160 gradients, 196.0 GFLOPs
# YOLO11n backbone
backbone:
# [from, repeats, module, args]
- [-1, 1, Conv, [64, 3, 2]] # 0-P1/2
- [-1, 1, Conv, [128, 3, 2]] # 1-P2/4
- [-1, 2, C3k2, [256, False, 0.25]]
- [-1, 1, Conv, [256, 3, 2]] # 3-P3/8
- [-1, 2, C3k2, [512, False, 0.25]]
- [-1, 1, Conv, [512, 3, 2]] # 5-P4/16
- [-1, 2, C3k2, [512, True]]
- [-1, 1, Conv, [1024, 3, 2]] # 7-P5/32
- [-1, 2, C3k2, [1024, True]]
- [-1, 1, SPPF, [1024, 5]] # 9
- [-1, 2, C2PSA, [1024]] # 10
# YOLO11n head
head:
- [-1, 1, nn.Upsample, [None, 2, "nearest"]]
- [[-1, 6], 1, Concat, [1]] # cat backbone P4
- [-1, 2, C3k2, [512, False]] # 13
- [-1, 1, nn.Upsample, [None, 2, "nearest"]]
- [[-1, 4], 1, Concat, [1]] # cat backbone P3
- [-1, 2, C3k2, [256, False]] # 16 (P3/8-small)
- [-1, 1, Conv, [256, 3, 2]]
- [[-1, 13], 1, Concat, [1]] # cat head P4
- [-1, 2, C3k2, [512, False]] # 19 (P4/16-medium)
- [-1, 1, Conv, [512, 3, 2]]
- [[-1, 10], 1, Concat, [1]] # cat head P5
- [-1, 2, C3k2, [1024, True]] # 22 (P5/32-large)
- [[16, 19, 22], 1, Detect, [nc]] # Detect(P3, P4, P5)
总体上的网络对比
在网络结构中,未发生变化的模块为白色。
YOLO11网络同样由backbone 、neck和head三部分组成。相较于YOLOv8,backbone部分有10层,C2f替换为C3k2,SPPF后新增了C2PSA模块;neck部分的C2f同样替换为C3k2,其余模块无变动;head部分将原始检测头优化为更为轻量化的检测头。
模块对比
C2f improvement
code:
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 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)
)
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 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 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))
C3k2相较于C2f模块区别较小,当c3k2==False时,中间的模块为bottleneck,否则则为c3。给了操作者一个自定义模型的选择。
C2PSA模块相当于将C2f模块中的bottleneck替换为PSA block,PSA模块由一个attention模块和一个前向回馈网络组成(两层convolution),并采用了残差连接。
code:
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))
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 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
lightweight detect head
code:
class Detect(nn.Module):
"""YOLOv8 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 YOLOv8 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)
def forward_end2end(self, x):
"""
Performs forward pass of the v10Detect module.
Args:
x (tensor): Input tensor.
Returns:
(dict, tensor): If not in training mode, returns a dictionary containing the outputs of both one2many and one2one detections.
If in training mode, returns a dictionary containing the outputs of one2many and one2one detections separately.
"""
x_detach = [xi.detach() for xi in x]
one2one = [
torch.cat((self.one2one_cv2[i](x_detach[i]), self.one2one_cv3[i](x_detach[i])), 1) for i in range(self.nl)
]
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 {"one2many": x, "one2one": one2one}
y = self._inference(one2one)
y = self.postprocess(y.permute(0, 2, 1), self.max_det, self.nc)
return y if self.export else (y, {"one2many": x, "one2one": one2one})
def _inference(self, x):
"""Decode predicted bounding boxes and class probabilities based on multiple-level feature maps."""
# Inference path
shape = x[0].shape # BCHW
x_cat = torch.cat([xi.view(shape[0], self.no, -1) for xi in x], 2)
if self.dynamic or self.shape != shape:
self.anchors, self.strides = (x.transpose(0, 1) for x in make_anchors(x, self.stride, 0.5))
self.shape = shape
if self.export and self.format in {"saved_model", "pb", "tflite", "edgetpu", "tfjs"}: # avoid TF FlexSplitV ops
box = x_cat[:, : self.reg_max * 4]
cls = x_cat[:, self.reg_max * 4 :]
else:
box, cls = x_cat.split((self.reg_max * 4, self.nc), 1)
if self.export and self.format in {"tflite", "edgetpu"}:
# Precompute normalization factor to increase numerical stability
# See https://github.com/ultralytics/ultralytics/issues/7371
grid_h = shape[2]
grid_w = shape[3]
grid_size = torch.tensor([grid_w, grid_h, grid_w, grid_h], device=box.device).reshape(1, 4, 1)
norm = self.strides / (self.stride[0] * grid_size)
dbox = self.decode_bboxes(self.dfl(box) * norm, self.anchors.unsqueeze(0) * norm[:, :2])
else:
dbox = self.decode_bboxes(self.dfl(box), self.anchors.unsqueeze(0)) * self.strides
return torch.cat((dbox, cls.sigmoid()), 1)
def bias_init(self):
"""Initialize Detect() biases, WARNING: requires stride availability."""
m = self # self.model[-1] # Detect() module
# cf = torch.bincount(torch.tensor(np.concatenate(dataset.labels, 0)[:, 0]).long(), minlength=nc) + 1
# ncf = math.log(0.6 / (m.nc - 0.999999)) if cf is None else torch.log(cf / cf.sum()) # nominal class frequency
for a, b, s in zip(m.cv2, m.cv3, m.stride): # from
a[-1].bias.data[:] = 1.0 # box
b[-1].bias.data[: m.nc] = math.log(5 / m.nc / (640 / s) ** 2) # cls (.01 objects, 80 classes, 640 img)
if self.end2end:
for a, b, s in zip(m.one2one_cv2, m.one2one_cv3, m.stride): # from
a[-1].bias.data[:] = 1.0 # box
b[-1].bias.data[: m.nc] = math.log(5 / m.nc / (640 / s) ** 2) # cls (.01 objects, 80 classes, 640 img)
def decode_bboxes(self, bboxes, anchors):
"""Decode bounding boxes."""
return dist2bbox(bboxes, anchors, xywh=not self.end2end, dim=1)
@staticmethod
def postprocess(preds: torch.Tensor, max_det: int, nc: int = 80):
"""
Post-processes YOLO model predictions.
Args:
preds (torch.Tensor): Raw predictions with shape (batch_size, num_anchors, 4 + nc) with last dimension
format [x, y, w, h, class_probs].
max_det (int): Maximum detections per image.
nc (int, optional): Number of classes. Default: 80.
Returns:
(torch.Tensor): Processed predictions with shape (batch_size, min(max_det, num_anchors), 6) and last
dimension format [x, y, w, h, max_class_prob, class_index].
"""
batch_size, anchors, _ = preds.shape # i.e. shape(16,8400,84)
boxes, scores = preds.split([4, nc], dim=-1)
index = scores.amax(dim=-1).topk(min(max_det, anchors))[1].unsqueeze(-1)
boxes = boxes.gather(dim=1, index=index.repeat(1, 1, 4))
scores = scores.gather(dim=1, index=index.repeat(1, 1, nc))
scores, index = scores.flatten(1).topk(min(max_det, anchors))
i = torch.arange(batch_size)[..., None] # batch indices
return torch.cat([boxes[i, index // nc], scores[..., None], (index % nc)[..., None].float()], dim=-1)
yolo11的检测头依旧采用了双分支的设计,在负责分类的分支中,将标准卷积替换为深度可分离卷积(Depthwise Separable Convolution,DSConv),降低了模型的双量。
DSConv其实就是一个深度卷积(Depthwise Convolution,DWConv)加上一个点卷积(1*1卷积)
不懂的可以看下面这个,DSConv是一种轻量卷积。
标准卷积、深度可分离卷积与GSConv,YOLOv8引入SlimNeck-****博客
懒得码字了,直接把代码喂给gpt4o......
有不理解的可以把代码pull下来交给ai分析
Q:分析这个检测头的结构
A:
这个 `Detect` 类是一个基于 YOLOv8 的检测头,它专门为目标检测模型设计,用来生成边界框预测和类别预测。以下是其结构和关键部分的分析:
### 1. **类属性**
- `dynamic`:布尔变量,用于控制是否强制重建网格。默认为 `False`。
- `export`:控制是否处于导出模式(用于模型转换,例如 `tflite`、`tfjs`)。默认为 `False`。
- `end2end`:布尔值,表示是否启用端到端的推理模式。
- `max_det`:最大检测数量,默认为 `300`。
- `anchors` 和 `strides`:用于存储生成的锚点和步幅,初始化为空张量。
- `shape`:保存输入张量的形状,用于推理时检测是否需要重新生成锚点。### 2. **初始化方法 (`__init__`)**
- `nc`:类别数,默认设置为 80 个类别(如 COCO 数据集)。
- `ch`:通道数,表示输入特征图的通道数列表,对应不同检测层。
- `reg_max`:用于计算 DF-Loss(分布式 Focal Loss)的参数,默认值为 16。
- `no`:每个锚点的输出通道数,计算方式为 `nc + reg_max * 4`,即类别数加上四个边界框回归值。
- `stride`:保存各个特征层的步幅,初始化为全零张量。
- `cv2`:卷积层模块列表,专用于回归边界框预测。这些模块包含多个卷积操作。
- `cv3`:卷积层模块列表,专用于类别预测。
- `dfl`:分布式 Focal Loss 层,使用该层对边界框的回归输出进行处理。> 主要特征:
- `cv2` 用于预测边界框,而 `cv3` 用于类别分类。
- 使用 `DFL` 处理边界框回归,以提供更精确的预测。### 3. **前向传播 (`forward`)**
- **普通前向传播**:
- 输入 `x` 是来自多个检测层的特征图。
- 对每个特征图分别通过 `cv2` 和 `cv3` 进行边界框和类别预测,然后将它们串联。
- 如果处于训练模式下,返回处理后的特征图。如果是推理模式,则调用 `_inference` 进行解码。
- **端到端推理 (`forward_end2end`)**:
- 该模式下,在处理特征图时会额外生成 `one2one` 检测结果(单一锚点)。
- 推理时使用 `postprocess` 对 `one2one` 的结果进行后处理。### 4. **推理方法 (`_inference`)**
- 在推理阶段,使用 `_inference` 函数对模型的预测结果进行后处理。
- 将所有特征层的输出通过 `cat` 函数串联在一起,并在必要时生成锚点(`make_anchors`)。
- 根据是否处于导出模式,将预测拆分为边界框预测和类别预测。
- 解码预测边界框,应用 `DFL` 进行回归,最终返回预测的边界框和分类得分。### 5. **偏置初始化 (`bias_init`)**
- 用于初始化类别预测的偏置权重,确保类别预测有一个较为合理的初始状态。比如在类别预测中,将偏置初始化为一个较大的负值,以防止初期阶段产生过多的错误检测。### 6. **边界框解码 (`decode_bboxes`)**
- 该函数使用锚点将模型的输出解码为实际的边界框坐标。
- `dist2bbox` 用于将回归输出(一般为偏移或比例)转换为实际的边界框坐标。### 7. **后处理 (`postprocess`)**
- 后处理阶段,将网络的原始预测进一步筛选、排序,并返回最终的检测结果,包括边界框坐标、得分和类别。
- 主要是通过对类别预测的最大概率值进行 `top-k` 筛选,然后根据其索引来获取对应的边界框。### 总结
- 这个检测头设计用于多层特征图检测,通过卷积和深度可分离卷积分别进行边界框和类别的预测。并且通过 `DFL` 实现更精确的回归预测。
- 它支持端到端的检测模式和多种导出格式(如 `tflite` 等),使其能够适应不同的部署需求。
- 在推理阶段,能够根据不同的模式处理不同数量的检测结果,并且通过偏置初始化和锚点生成来优化模型的性能。
Q:分析这部分的结构
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
)
A:这部分代码片段实现了 `Detect` 类中的 `cv3` 属性,它是一个 `nn.ModuleList`,用于创建一系列卷积模块,每个模块与输入特征图的通道数 `ch` 相关联。`cv3` 主要用于类别预测,并结合了多种卷积操作。以下是该结构的详细分析:
### 1. **代码结构简介**
```python
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
)
```
- 这个 `cv3` 是 `Detect` 类的一个属性,使用 `nn.ModuleList` 包装了多个由 `nn.Sequential` 组合而成的卷积模块。
- 每个 `nn.Sequential` 中包含多个卷积层,用于不同的特征图进行处理。
- `cv3` 的作用是为每个输入特征图生成对应的类别预测(即每个目标的类别得分)。### 2. **具体模块的功能**
#### a. **第一层 `nn.Sequential(DWConv(x, x, 3), Conv(x, c3, 1))`**
- **`DWConv(x, x, 3)`**:
- `DWConv` 是深度可分离卷积(Depthwise Convolution),使用输入通道 `x` 和输出通道 `x`。
- 内核大小为 3 × 3,输入和输出的通道数相同。
- 这种卷积方式比标准卷积操作的计算量要低很多,适合在模型的某些层进行降维或保留特征的处理。
- **`Conv(x, c3, 1)`**:
- 这是一个标准卷积层,用 1 × 1 的卷积核将 `DWConv` 的输出进行进一步处理。
- 它的作用是将 `x` 通道的特征图转换为 `c3` 通道。
- `c3` 是初始化时定义的通道数,它的计算方式依赖于输入特征图的通道数 `ch` 和类别数 `nc`。#### b. **第二层 `nn.Sequential(DWConv(c3, c3, 3), Conv(c3, c3, 1))`**
- **`DWConv(c3, c3, 3)`**:
- 这又是一个深度可分离卷积操作,输入和输出通道数都是 `c3`。
- 该层主要用于进一步提取和细化特征,同时保持较低的计算复杂度。
- **`Conv(c3, c3, 1)`**:
- 使用 1 × 1 的卷积核将特征进行处理,并保持通道数为 `c3`。
- 该层的作用是进行跨通道的信息整合,同时减少参数量和计算量。#### c. **第三层 `nn.Conv2d(c3, self.nc, 1)`**
- 这是一个标准的二维卷积层,使用 1 × 1 的卷积核将 `c3` 通道的特征图转化为 `nc` 通道的特征图。
- 其中,`nc` 是类别数,用于最终的类别预测。
- 这一层的作用是将特征图映射到类别维度上,即输出的每个通道都对应一个目标类别的预测得分。### 3. **cv3 的总体作用**
- `cv3` 的每个模块作用于对应层次的特征图(如高分辨率、小分辨率特征图等),通过卷积操作来生成目标检测中的类别预测。
- 其设计思路是通过多种卷积(包括深度可分离卷积和标准卷积)来在不同尺度特征图上提取类别信息,从而提高分类的精确度。
- 最后的 `nn.Conv2d` 层将特征图映射到类别维度,输出的每个通道都表示某个目标类别的预测值。### 4. **深度可分离卷积的优势**
- 使用 `DWConv`(深度可分离卷积)能够有效减少计算复杂度和参数量。
- 相比于标准卷积,深度可分离卷积将卷积操作分为两个部分:
- **深度卷积**:在每个输入通道上单独进行卷积,保留空间维度上的特征。
- **逐点卷积**:使用 1 × 1 的卷积核在所有通道之间进行信息整合,从而生成新的特征图。
- 这种方式既能保持卷积层的空间特征提取能力,又能通过较少的参数实现跨通道的信息融合,是一种计算量更低的卷积操作。### 5. **总结**
- `cv3` 是 `Detect` 模块中专门用于类别预测的卷积模块,通过不同类型的卷积操作对输入特征进行处理。
- 它采用深度可分离卷积结合标准卷积,以减少计算量和提高效率,同时在保持性能的前提下提供精确的类别预测。
- 该模块在 YOLOv8 检测头中与 `cv2`(用于边界框回归的模块)共同作用,完成目标检测任务中的位置和类别预测。
我的内容可能有误,如发现请指正。
Reference:
YOLO11 沉浸式讲解 YOLOV11网络结构以及代码剖析-****博客