PPYOLOE详解第二弹:你真的知道什么是Neck嘛?

P粉084495128
发布: 2025-07-31 13:45:24
原创
324人浏览过
本文先介绍双阶段检测模型Neck,以FPN论文为例讲4种结构。再讲单阶段检测模型,回顾YOLOv1到v3中Neck的发展,最后重点介绍PANet及PPYOLOE的Neck结构,还给出了相关代码,展现了目标检测网络Neck的演变与特点。

☞☞☞AI 智能聊天, 问答助手, AI 智能搜索, 免费无限量使用 DeepSeek R1 模型☜☜☜

ppyoloe详解第二弹:你真的知道什么是neck嘛? - php中文网

上次介绍完了PPYOLOE的Backbone 那么这次就到目标检测网络的第二部分--Neck了

相比较Backbone被称为主干(骨干)网络比较明确,但是neck的提出就不太明确了,我第一次认识到这个词还是在看YOLOv4网络的时候,YOLOv3中我甚至都没注意到这里,因为YOLOv3中Neck就只是使用来的一个上采样的方式来将主干网络的不同输出进行融合,但是到了YOLOv4中 Neck的占比变得更丰富了,它的作用甚至逐渐的和Head与Backbone相提并论了起来,由于PPYOLOE中的neck创新点也不是很多,单独说又显得有点水,那么这里我们就向大家介绍几种不同的Neck以及YOLO系列中Neck的发展历史,最后再讲YOLO Neck!

So just do it

相信如果对目标检测有一些了解的人应该会知道目标检测网络分为点阶段与双阶段检测模型,那么我们先简单讲一下与本文关系不太大的双阶段检测模型都有哪些Neck吧!

零:双阶段检测模型Neck

这里我们以《Feature Pyramid Networks for Object Detection》这篇论文为例子来介绍几个Neck,FPN这篇论文中一共提出了4个结构

PPYOLOE详解第二弹:你真的知道什么是Neck嘛? - php中文网        
  1. 使用图像金字塔构建特征金字塔。在每个图像比例上独立计算特征,速度较慢。
  2. 最近的检测系统选择只使用单尺度特征,以实现更快的检测。
  3. 另一种方法是重用Conventas计算的金字塔特征层次(如果它是特征化的图像金字塔)。
  4. 特征金字塔网络(FPN)与(b)和(c)一样快速,但更精确。

0.1:第一种

PPYOLOE详解第二弹:你真的知道什么是Neck嘛? - php中文网        

第一种是对每一种尺度的图像都进行特征提取,因此能够产生多尺度的特征表示,由于所有登记的特征图都具有不同的语义信息因此检测精度较高,但是他的推理时间相较于其他网络会大幅增加,同时占用大量的资源,不好实现。

0.2: 第二种

PPYOLOE详解第二弹:你真的知道什么是Neck嘛? - php中文网        

第二种就是只利用最高层的特征图进行预测,像是经典的FasterRCNN网络就是使用的这种简单的方式,也就是几乎没有Neck什么事情,这种方法速度虽然比较快,但是他的检测精度相比较其他几种方法来说还是有待提高

0.3: 第三种

PPYOLOE详解第二弹:你真的知道什么是Neck嘛? - php中文网        

第三种网络与第一种网络类似,但是他放弃了使用底层特征图,而是在较高层次搭建金字塔结构,这就导致一些比较低级的语义信息被错过,而这对于小目标检测来说是极其重要的

0.4: 第四种

PPYOLOE详解第二弹:你真的知道什么是Neck嘛? - php中文网        

插曲

特征金字塔结构,我有一个项目是在详细的介绍,这里不过多赘述,如想详细了解可以看我的之前的项目ResNet+FPN详解

一:单阶段检测模型

1.1: YOLOv1

原论文 You Can Only look once 

YOLOv1是与SSD和Faster RCNN同时代的产物,但是YOLOv1在刚刚出来的时候真的不是很惊艳,因为在检测精度上它与Faster RCNN相差太远了,在“能耗比”上也比不过同时期的SSD,而且这时的YOLOv1采用的是上文中的b模式,也就是只用最高层的特征图进行预测,所以其实YOLOv1中并没有什么Neck,这里也就不过多赘述了,上面有原论文有兴趣的可以去读一下,这里就放一张YOLOv1的原图供大家参考

PPYOLOE详解第二弹:你真的知道什么是Neck嘛? - php中文网        

这里的图片源自B站up主霹雳吧啦Wz

万知
万知

万知: 你的个人AI工作站

万知38
查看详情 万知

1.2:YOLOv2

原论文 YOLO9000: Better, Faster, Stronger

PPYOLOE详解第二弹:你真的知道什么是Neck嘛? - php中文网        

*** 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

1.3: YOLOv3

YOLOv3: An Incremental Improvement

PPYOLOE详解第二弹:你真的知道什么是Neck嘛? - php中文网        

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:我滴任务完成了

PPYOLOE详解第二弹:你真的知道什么是Neck嘛? - php中文网        

YOLOv3相比较YOLOv2他增加了Backbone输出,增加了预测头分支,但是他的语义融合方式从下采样改成了上采样,所以这里有没有可能就是,他真的留了一手,真的准备在自己的YOLOv4中让Neck称为一个真正的Neck?但是很可惜由于种种原因YOLO的原作者放弃了更新YOLO,留一手司机究竟留没留一手,谁也不知道了。

这里的图片源自B站up主霹雳吧啦Wz

二:正式介绍PANet

比较新的几篇YOLO的Neck都是采用了PAN结构,像是PPYOLOv2,YOLO-X,YOLOv5(待定)以及本文要介绍PPYOLOE都采用了这个结构

PPYOLOE详解第二弹:你真的知道什么是Neck嘛? - php中文网        

首先从Backbone获取C5输出需要经过CSPStage,

PPYOLOE详解第二弹:你真的知道什么是Neck嘛? - php中文网        
# 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
登录后复制
       
PPYOLOE详解第二弹:你真的知道什么是Neck嘛? - php中文网        
 5. 然后将C5-1再经过一个ConvBNLayer层将通道数从768转换为384,然后通过interpolate函数进行上采样将宽高x2,通道数不变。 6. 然后与C4进行Concat拼接,称为C4-0,此时C4-0通道数为896,宽高都为32.
登录后复制
       
PPYOLOE详解第二弹:你真的知道什么是Neck嘛? - php中文网        
# 7. C4-0会先分出两个分支
	# 1.1. 分支一     直接经过一个1x1卷积把通道数从896变成192.
	# 1.2. 分支二     首先经过一个1x1的卷积将通道数从896变为192,然后经过3个BasicBlock结构# 8. 将分支一与分支二进行concat拼接# 9. 再经过一个ConvBNLayer结构。# 10. 分出两支一个为C4-1,另外一个为C4-2
登录后复制
       
PPYOLOE详解第二弹:你真的知道什么是Neck嘛? - php中文网        
 11. 然后将C5-1再经过一个ConvBNLayer层将通道数从384转换为192,然后通过interpolate函数进行上采样将宽高x2,通道数不变。 12. 然后与C3进行Concat拼接,称为C3-0,此时C3-0通道数为448,宽高都为64.
登录后复制
       
PPYOLOE详解第二弹:你真的知道什么是Neck嘛? - php中文网        
# 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直接作用于预测头上
登录后复制
       
PPYOLOE详解第二弹:你真的知道什么是Neck嘛? - php中文网        
 11. 然后将C3-1再经过一个ConvBNLayer层将宽高除以二 12. 然后与C4-2进行Concat拼接,称为P4-0,此时P4-0通道数为576,宽高都为32.
登录后复制
       
PPYOLOE详解第二弹:你真的知道什么是Neck嘛? - php中文网        
# 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直接作用于预测头上
登录后复制
       
PPYOLOE详解第二弹:你真的知道什么是Neck嘛? - php中文网        
 11. 然后将P4-1再经过一个ConvBNLayer层将宽高除以二 12. 然后与C5-2进行Concat拼接,称为P5-0,此时P4-0通道数为1152,宽高都为16.
登录后复制
       
PPYOLOE详解第二弹:你真的知道什么是Neck嘛? - php中文网        
# 13. P5-0会先分出两个分支
	# 1.1. 分支一     直接经过一个1x1卷积把通道数从1152变成384.
	# 1.2. 分支二     首先经过一个1x1的卷积将通道数从1152变成384,然后经过3个BasicBlock结构# 14. 将分支一与分支二进行concat拼接# 15. 再经过一个ConvBNLayer结构。# 17. 然后直接作用于预测头上
登录后复制
   

PPYOLOE Neck 所有代码

代码地址 PaddleDetection/ppdet/modeling/necks/custom_pan.py

In [9]
!git clone -b develop https://gitee.com/paddlepaddle/PaddleDetection.git

%cd PaddleDetection/
!python setup.py install
!pip install -r requirements.txt
登录后复制
   
In [ ]
!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中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习
PHP中文网抖音号
发现有趣的

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号