本文先介绍双阶段检测模型Neck,以FPN论文为例讲4种结构。再讲单阶段检测模型,回顾YOLOv1到v3中Neck的发展,最后重点介绍PANet及PPYOLOE的Neck结构,还给出了相关代码,展现了目标检测网络Neck的演变与特点。
☞☞☞AI 智能聊天, 问答助手, AI 智能搜索, 免费无限量使用 DeepSeek R1 模型☜☜☜

相比较Backbone被称为主干(骨干)网络比较明确,但是neck的提出就不太明确了,我第一次认识到这个词还是在看YOLOv4网络的时候,YOLOv3中我甚至都没注意到这里,因为YOLOv3中Neck就只是使用来的一个上采样的方式来将主干网络的不同输出进行融合,但是到了YOLOv4中 Neck的占比变得更丰富了,它的作用甚至逐渐的和Head与Backbone相提并论了起来,由于PPYOLOE中的neck创新点也不是很多,单独说又显得有点水,那么这里我们就向大家介绍几种不同的Neck以及YOLO系列中Neck的发展历史,最后再讲YOLO Neck!
So just do it
相信如果对目标检测有一些了解的人应该会知道目标检测网络分为点阶段与双阶段检测模型,那么我们先简单讲一下与本文关系不太大的双阶段检测模型都有哪些Neck吧!
这里我们以《Feature Pyramid Networks for Object Detection》这篇论文为例子来介绍几个Neck,FPN这篇论文中一共提出了4个结构
第一种是对每一种尺度的图像都进行特征提取,因此能够产生多尺度的特征表示,由于所有登记的特征图都具有不同的语义信息因此检测精度较高,但是他的推理时间相较于其他网络会大幅增加,同时占用大量的资源,不好实现。
第二种就是只利用最高层的特征图进行预测,像是经典的FasterRCNN网络就是使用的这种简单的方式,也就是几乎没有Neck什么事情,这种方法速度虽然比较快,但是他的检测精度相比较其他几种方法来说还是有待提高
第三种网络与第一种网络类似,但是他放弃了使用底层特征图,而是在较高层次搭建金字塔结构,这就导致一些比较低级的语义信息被错过,而这对于小目标检测来说是极其重要的
特征金字塔结构,我有一个项目是在详细的介绍,这里不过多赘述,如想详细了解可以看我的之前的项目ResNet+FPN详解
原论文 YOLO9000: Better, Faster, Stronger
*** YOLOv2的论文原名叫YOLO9000,这是因为作者通过引入anchor等操作让YOLO能够检测超过9000种,正如YOLO9000的名字一样 Better,Faster,Stronger,作者在网络中也开始使用Backbone中间层次的特征图,从而利用到了中间层的语义信息,通过上图可以这里YOLOv2是将中间的分支先通过一个基础卷积块把通道数从512变为64,后通过PassThrough Layer将通道数改为256,长宽各减一半,然后与最高层的特征图在深度维度进行concat拼接,这里YOLOv2已经开始使用下采样的的方式来进行语义融合,这里我认为可以算是YOLO的Neck初具雏形,但在YOLOv2中它仍然只有一个检测头,所以它应该是类似于FPN***
这里的图片源自B站up主霹雳吧啦Wz
YOLOv3: An Incremental Improvement
YOLOv3的论文名称很有意思,渐进式改进,所以可能作者只是想把YOLO当做一个日常升级?自己还藏了一手?所以也有的同学会戏称原YOLO系列的作者为藏一手司机。在YOLOv3中Backbone已经被替换成了DarkNet53,这次的Backbone输出分支也从2变成了3个,同时预测头也增加到了3个。这里我们就将Backbone的三个分支从低到高以此称为C0 [B,256,64,64],C1[B,512,32,32],C2[B,1024,16,16]。首先网络将C2的输出经过一系列卷积之后,传出两个分支C2-1,C2-2,C2-1分支用于预测头,C2-2经过上采样技术用于与C1的输出进行Concat拼接用于语义融合,并在经过一系列卷积之后再次传出两个分支C1-1、C1-2,C1-1分支作用在第二个预测头上,C1-2分支再次经过一个上采样技术然后与C0的释出进行Concat拼接用于语义融合,并在经过一系列卷积之后,诶~这回到头了,不分支了,直接接一个检测头,哈哈哈哈
Neck:我滴任务完成了
YOLOv3相比较YOLOv2他增加了Backbone输出,增加了预测头分支,但是他的语义融合方式从下采样改成了上采样,所以这里有没有可能就是,他真的留了一手,真的准备在自己的YOLOv4中让Neck称为一个真正的Neck?但是很可惜由于种种原因YOLO的原作者放弃了更新YOLO,留一手司机究竟留没留一手,谁也不知道了。
这里的图片源自B站up主霹雳吧啦Wz
比较新的几篇YOLO的Neck都是采用了PAN结构,像是PPYOLOv2,YOLO-X,YOLOv5(待定)以及本文要介绍PPYOLOE都采用了这个结构
首先从Backbone获取C5输出需要经过CSPStage,
# 1. C5会先分出两个分支 # 1.1. 分支一 直接经过一个1x1卷积把通道数从1024变成384. # 1.2. 分支二 首先经过一个1x1的卷积将通道数从1024变为384,然后经过3个BasicBlock结构其中在第二个BasicBlock结构后加入一个SPP结构# 2. 将分支一与分支二进行concat拼接# 3. 再经过一个ConvBNLayer结构,说句实话我也不知道这是在干吗因为这个ConvBN是1x1卷积,而且他的输出通道数与输入通道数都是768,所以我也有点疑惑。# 4. 分出两支一个为C5-1,另外一个为C5-2
5. 然后将C5-1再经过一个ConvBNLayer层将通道数从768转换为384,然后通过interpolate函数进行上采样将宽高x2,通道数不变。 6. 然后与C4进行Concat拼接,称为C4-0,此时C4-0通道数为896,宽高都为32.
# 7. C4-0会先分出两个分支 # 1.1. 分支一 直接经过一个1x1卷积把通道数从896变成192. # 1.2. 分支二 首先经过一个1x1的卷积将通道数从896变为192,然后经过3个BasicBlock结构# 8. 将分支一与分支二进行concat拼接# 9. 再经过一个ConvBNLayer结构。# 10. 分出两支一个为C4-1,另外一个为C4-2
11. 然后将C5-1再经过一个ConvBNLayer层将通道数从384转换为192,然后通过interpolate函数进行上采样将宽高x2,通道数不变。 12. 然后与C3进行Concat拼接,称为C3-0,此时C3-0通道数为448,宽高都为64.
# 13. C3-0会先分出两个分支 # 1.1. 分支一 直接经过一个1x1卷积把通道数从448变成96. # 1.2. 分支二 首先经过一个1x1的卷积将通道数从448变为96,然后经过3个BasicBlock结构# 14. 将分支一与分支二进行concat拼接# 15. 再经过一个ConvBNLayer结构。# 16. 分出两支一个为C3-1,另外一个为C3-2# 17. C3-2直接作用于预测头上
11. 然后将C3-1再经过一个ConvBNLayer层将宽高除以二 12. 然后与C4-2进行Concat拼接,称为P4-0,此时P4-0通道数为576,宽高都为32.
# 13. P4-0会先分出两个分支 # 1.1. 分支一 直接经过一个1x1卷积把通道数从576变成192. # 1.2. 分支二 首先经过一个1x1的卷积将通道数从576变为192,然后经过3个BasicBlock结构# 14. 将分支一与分支二进行concat拼接# 15. 再经过一个ConvBNLayer结构。# 16. 分出两支一个为P4-1,P4-2# 17. P4-2直接作用于预测头上
11. 然后将P4-1再经过一个ConvBNLayer层将宽高除以二 12. 然后与C5-2进行Concat拼接,称为P5-0,此时P4-0通道数为1152,宽高都为16.
# 13. P5-0会先分出两个分支 # 1.1. 分支一 直接经过一个1x1卷积把通道数从1152变成384. # 1.2. 分支二 首先经过一个1x1的卷积将通道数从1152变成384,然后经过3个BasicBlock结构# 14. 将分支一与分支二进行concat拼接# 15. 再经过一个ConvBNLayer结构。# 17. 然后直接作用于预测头上
!git clone -b develop https://gitee.com/paddlepaddle/PaddleDetection.git %cd PaddleDetection/ !python setup.py install !pip install -r requirements.txt
!python ./ppdet/modeling/necks/custom_pan.py
# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.## Licensed under the Apache License, Version 2.0 (the "License");# you may not use this file except in compliance with the License.# You may obtain a copy of the License at## http://www.apache.org/licenses/LICENSE-2.0## Unless required by applicable law or agreed to in writing, software# distributed under the License is distributed on an "AS IS" BASIS,# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.# See the License for the specific language governing permissions and# limitations under the License.import paddleimport paddle.nn as nnimport paddle.nn.functional as Ffrom ppdet.core.workspace import register, serializablefrom ppdet.modeling.layers import DropBlockfrom ppdet.modeling.ops import get_act_fnfrom ppdet.modeling.backbones.cspresnet import ConvBNLayer, BasicBlockfrom ppdet.modeling.shape_spec import ShapeSpecimport numpy as np
__all__ = ['CustomCSPPAN']class SPP(nn.Layer):
def __init__(self,
ch_in,
ch_out,
k,
pool_size,
act='swish',
data_format='NCHW'):
super(SPP, self).__init__()
self.pool = []
self.data_format = data_format for i, size in enumerate(pool_size):
pool = self.add_sublayer( 'pool{}'.format(i),
nn.MaxPool2D(
kernel_size=size,
stride=1,
padding=size // 2,
data_format=data_format,
ceil_mode=False))
self.pool.append(pool)
self.conv = ConvBNLayer(ch_in, ch_out, k, padding=k // 2, act=act) def forward(self, x):
outs = [x] for pool in self.pool:
outs.append(pool(x)) if self.data_format == 'NCHW':
y = paddle.concat(outs, axis=1) else:
y = paddle.concat(outs, axis=-1)
y = self.conv(y) return yclass CSPStage(nn.Layer):
def __init__(self, block_fn, ch_in, ch_out, n, act='swish', spp=False):
super(CSPStage, self).__init__()
ch_mid = int(ch_out // 2)
self.conv1 = ConvBNLayer(ch_in, ch_mid, 1, act=act)
self.conv2 = ConvBNLayer(ch_in, ch_mid, 1, act=act)
self.convs = nn.Sequential()
next_ch_in = ch_mid for i in range(n):
self.convs.add_sublayer( str(i), eval(block_fn)(next_ch_in, ch_mid, act=act, shortcut=False)) if i == (n - 1) // 2 and spp:
self.convs.add_sublayer( 'spp', SPP(ch_mid * 4, ch_mid, 1, [5, 9, 13], act=act))
next_ch_in = ch_mid
self.conv3 = ConvBNLayer(ch_mid * 2, ch_out, 1, act=act) def forward(self, x):
y1 = self.conv1(x)
y2 = self.conv2(x)
y2 = self.convs(y2)
y = paddle.concat([y1, y2], axis=1)
y = self.conv3(y) return yclass CustomCSPPAN(nn.Layer):
__shared__ = ['norm_type', 'data_format', 'width_mult', 'depth_mult', 'trt'] def __init__(self,
in_channels=[256, 512, 1024],
out_channels=[768,768/2,768/4],
norm_type='bn',
act='relu',
stage_fn='CSPStage',
block_fn='BasicBlock',
stage_num=1,
block_num=3,
drop_block=False,
block_size=3,
keep_prob=0.9,
spp=True,
data_format='NCHW',
width_mult=1.0,
depth_mult=1.0,
trt=False):
super(CustomCSPPAN, self).__init__()
out_channels = [max(round(c * width_mult), 1) for c in out_channels]
block_num = max(round(block_num * depth_mult), 1)
act = get_act_fn(
act, trt=trt) if act is None or isinstance(act,
(str, dict)) else act
self.num_blocks = len(in_channels)
self.data_format = data_format
self._out_channels = out_channels
in_channels = in_channels[::-1]
fpn_stages = []
fpn_routes = [] for i, (ch_in, ch_out) in enumerate(zip(in_channels, out_channels)): if i > 0:
ch_in += ch_pre // 2
stage = nn.Sequential() for j in range(stage_num):
stage.add_sublayer( str(j), eval(stage_fn)(block_fn,
ch_in if j == 0 else ch_out,
ch_out,
block_num,
act=act,
spp=(spp and i == 0))) if drop_block:
stage.add_sublayer('drop', DropBlock(block_size, keep_prob))
fpn_stages.append(stage) if i < self.num_blocks - 1:
fpn_routes.append(
ConvBNLayer(
ch_in=ch_out,
ch_out=ch_out // 2,
filter_size=1,
stride=1,
padding=0,
act=act))
ch_pre = ch_out
self.fpn_stages = nn.LayerList(fpn_stages)
self.fpn_routes = nn.LayerList(fpn_routes)
pan_stages = []
pan_routes = [] for i in reversed(range(self.num_blocks - 1)):
pan_routes.append(
ConvBNLayer(
ch_in=out_channels[i + 1],
ch_out=out_channels[i + 1],
filter_size=3,
stride=2,
padding=1,
act=act))
ch_in = out_channels[i] + out_channels[i + 1]
ch_out = out_channels[i]
stage = nn.Sequential() for j in range(stage_num):
stage.add_sublayer( str(j), eval(stage_fn)(block_fn,
ch_in if j == 0 else ch_out,
ch_out,
block_num,
act=act,
spp=False)) if drop_block:
stage.add_sublayer('drop', DropBlock(block_size, keep_prob))
pan_stages.append(stage)
self.pan_stages = nn.LayerList(pan_stages[::-1])
self.pan_routes = nn.LayerList(pan_routes[::-1]) def forward(self, blocks, for_mot=False):
blocks = blocks[::-1]
fpn_feats = [] for i, block in enumerate(blocks): if i > 0:
block = paddle.concat([route, block], axis=1) print(self.fpn_stages[i])
route = self.fpn_stages[i](block)
fpn_feats.append(route) if i < self.num_blocks - 1:
route = self.fpn_routes[i](route)
route = F.interpolate(
route, scale_factor=2., data_format=self.data_format)
pan_feats = [fpn_feats[-1], ]
route = fpn_feats[-1] for i in reversed(range(self.num_blocks - 1)):
block = fpn_feats[i]
route = self.pan_routes[i](route)
block = paddle.concat([route, block], axis=1)
route = self.pan_stages[i](block)
pan_feats.append(route) return pan_feats[::-1] @classmethod
def from_config(cls, cfg, input_shape):
return {'in_channels': [i.channels for i in input_shape], } @property
def out_shape(self):
return [ShapeSpec(channels=c) for c in self._out_channels]if __name__=='__main__':
model = CustomCSPPAN()
path = './infer/neck/CustomCSPPAN_SPP_True'
# weight_path = '123.pdparams'
# static = paddle.load(weight_path)
x2 = paddle.to_tensor(np.random.rand(1,1024,16,16),dtype='float32')
x1 = paddle.to_tensor(np.random.rand(1,512,32,32),dtype='float32')
x0 = paddle.to_tensor(np.random.rand(1,256,64,64),dtype='float32')
out = [x0,x1,x2]
out = model(out) # model.eval()
# paddle.jit.save(model,path,(out,False))以上就是PPYOLOE详解第二弹:你真的知道什么是Neck嘛?的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号