论文 PP-PicoDet: A Better Real-Time Object Detector on Mobile Devices
paddleDetection代码:picodet
PP-PicoDet 是百度提出的移动端友好和高精度Anchor-Free 目标检测算法,实测性能非常优越。关于模型的配置与训练的解读可以参考 飞桨 PP-PicoDet 配置与训练

本文将详解 picodet 的 neck—— CSP-PAN。

注:paddleDetection 已更新至2.4 版本,picodet增强版 将 neck 中的 CSP module 换成了 LCNet module。

CSP-PAN

picodet 的结构图。backbone 输出 C3-C5 feature maps 到 neck。neck 用的是 CSP-PAN 网络,输出四个 feature maps。例如对 PicoDet-s 来说,neck 的输入 channel 是[96, 192, 384], 输出 channel 是 [96, 96, 96, 96].

CSP-PAN neck 使用 PAN 结构用于获取多级 feature maps,用 CSP 网络进行相邻 feature maps 之间的特征连接和融合,picodet 还在CSP-PAN的顶部添加一个特征图 scale 以检测更多的目标。此外细节还包含了压缩通道数的 1x1 Conv 、上采样 Upsample、深度可分离卷积 DP、Elementwise Add 和 Channelwise Concat 等操作。下面会进行详细讲解。

1. 从 FPN 到 PAN

1.1 FPN Feature pyramid network,特征金字塔网络 (CVPR 2017)

高层的特征虽然包含了丰富的语义信息,但是由于低分辨率,很难准确地保存物体的位置信息。与之相反,低层的特征虽然语义信息较少,但是由于分辨率高,就可以准确地包含物体位置信息。所以如果可以将低层的特征和高层的特征融合起来,就能得到一个识别和定位都准确的目标检测系统。在 FPN 提出之前也有算法采用了多尺度融合的方式,但是一般都是在特征融合之后再做预测,而 FPN 则是在不同的特征层都单独进行预测。
FPN (Feature Pyramid Network)特征金字塔的结构主要包括两个部分: top-down 和 lateral connection。

Backbone 不同层输出的 feature map 的尺寸有的是不变的,有的是成2倍的减小的。将输出尺寸相同的层归为一个 stage,将每个 stage 的最后一层输出的特征抽取出来,就得到了不同下采样倍数的特征金字塔。以 picodet 的 backbone ESNet 为例,每个 ES block 中最后一个残差块的输出 {C3, C4, C5},这些输出的尺寸分别是原图的{ 1/8, 1/16, 1/32 }倍。

top-down 上采样

高层特征包含丰富的语义信息,但是分辨率低。top-down 的过程就是将高层特征进行上采样 (upsample),然后与低层特征进行融合,使得低层特征也能包含丰富的语义信息。

上采样

picodet 使用的上采样方法是最近邻插值方法。最近邻插值就是将距离待求像素最近的像素赋值给待求像素。假设原图的宽高为 {Wsrc, Hsrc}, 上采样的目标图像宽高为 {Wdst, Hdst}, 则目标图像上某个像素位置 (Xdst, Ydst) 与原始图像上的像素位置(Xsrc, Ysrc)之间的对应关系为
Xsrc = Xdst × (Wsrc / Wdst)
Ysrc = Ydst × (Hsrc / Wdst)
将(Xsrc, Ysrc)位置的值填入(Xdst, Ydst) 即可。如果上式计算出的像素坐标 (Xdst, Ydst) 有小数,一般采用四舍五入取整。pytorch 实现2倍上采样方法:

import torch.nn as nn
upsample = nn.Upsample(scale_factor=2, mode='nearest')

最近邻插值方法计算量小,但可能会产生混叠效应, 即插值生成的图像灰度不连续,在灰度变化大的地方可能会呈现出明显的锯齿状。

lateral connection 横向连接

横向连接就是把 top-down 上采样的特征与 backbone 低层的特征融合。一般有相加堆叠两种融合方式。picodet 采用的是堆叠方式 torch.cat()。

1 x 1 卷积 压缩通道数

大通道数计算成本较高,因此 picodet 在进行特征堆叠之前,采用 1x1 卷积对每个输入 neck 的特征层的通道数做了压缩。在 paddleDetection 源码中用 class Channel_T 实现。


1x1 卷积除了可以压缩通道数,降低网络计算量外,还可以增加网络非线性特征,实现跨通道的信息融合。

1.2 PAN Path Aggregation network,路径聚合网络

FPN是自顶向下,将高层的强语义特征传递下来,对整个金字塔进行增强,不过只增强了语义信息,对定位信息没有传递,而 PAN在FPN的后面添加一个自底向上的金字塔,这样的操作是对FPN的补充,将低层的强定位特征传递上去. 如下图中的绿色虚线的 bottom-top 过程,直接将backbone 低层特征通过卷积操作下采样后与 FPN 输出的特征相融合。PA(Path Aggregation)的策略使得不同层次的特征在传递时需要“穿越”的网络层次数量大大减少。

bottom-up 下采样

下采样过程通常采用 stride=2 的卷积来实现。

卷积网络 消除混叠效应

常规的 FPN 网络在特征堆叠或相加后要做一次卷积操作,以消除上采样产生的混叠效应。而 picodet 在此处使用了更为复杂的 csp 网络代替了常规的卷积操作。

2. CSP 网络

CSP 网络 (Cross Stage Partial Layer)并不是完整的网络结构,而是一个模块,或者说是一种思想,它可以被很方便的应用于很多不同的网络结构中。其思想是把特征分为 Part 1,Part 2 两部分,Part 2 进行卷积操作等得到 C,然后把 Part 1 和 C 进行 concat。即将feature map拆成 Part1 和 Part2 两个部分,Part2 部分进行卷积操作,然后将卷积操作的结果与 Part1 进行 concate。
下图为原始 CSP 论文中在 resnet 上进行 csp 改造的结构图。其中的 Partial Transition 代表过渡层,主要包含瓶颈层( 1 × 1 卷 积 ) 和池化层(可选)。其中(a)图是原始的ResNet的特征融合方式。(b)图是CSPResNet的特征融合方式。


CSP作者认为在神经网络推理过程中计算量大的一个原因是网络优化过程中的梯度信息重复。为了解决这个问题,CSP 网络采用了更加丰富的梯度组合,在保证准确率的前提下减小计算量(减小 20%)。
CSPNet通过将梯度的变化从头到尾地集成到特征图中,在减少了计算量的同时可以保证准确率。

1.增强CNN的学习能力
通常轻量化后的网络,效果会下降。如果轻量化的模型要有大模型效果,就必须要有更强的学习能力。CSPNet 通过将梯度的变化从头到尾地集成到特征图中,这样实现了更加丰富的梯度组合,从而增强了网络的学习能力。

2. 减少计算量
去掉计算量较高的计算瓶颈结构。比如对于 DenseNet 来说,其计算量较高的显然是在 Dense Block 里面,CSPNet 通过 Partial Dense Block 的改进,仅仅一部分 feature map 进行计算,自然可以减少计算量。

3. 降低内存占用
在 FPN 的时候采用 cross-channel pooling 压缩 feature map。Partial Dense Block 应该也有助于降低内存占用。

总的来说,CSPNet 是基于 DenseNet 的一种改进网络。CSPNet 保留了DenseNet特性重用特性的优点,但同时通过截断梯度流防止了过多的重复梯度信息。

PicoDet 采用的 CSPLayer pytorch 实现如下:

 class CSPLayer(nn.Module):
    """Cross Stage Partial Layer.

    Args:
        in_channels (int): The input channels of the CSP layer.
        out_channels (int): The output channels of the CSP layer.
        expand_ratio (float): Ratio to adjust the number of channels of the
            hidden layer. Default: 0.5
        num_blocks (int): Number of blocks. Default: 1
        add_identity (bool): Whether to add identity in blocks.
            Default: True
        use_depthwise (bool): Whether to depthwise separable convolution in
            blocks. Default: False
    """

    def __init__(self,
                 in_channels,
                 out_channels,
                 kernel_size=3,
                 expand_ratio=0.5,
                 num_blocks=1,
                 add_identity=True,
                 use_depthwise=False,
                 act="LeakyReLU"):
        super().__init__()
        mid_channels = int(out_channels * expand_ratio)
        self.main_conv = ConvBNLayer(in_channels, mid_channels, 1, act=act)
        self.short_conv = ConvBNLayer(in_channels, mid_channels, 1, act=act)
        self.final_conv = ConvBNLayer(
            2 * mid_channels, out_channels, 1, act=act)

        self.blocks = nn.Sequential(*[
            DarknetBottleneck(
                mid_channels,
                mid_channels,
                kernel_size,
                1.0,
                add_identity,
                use_depthwise,
                act=act) for _ in range(num_blocks)
        ])

    def forward(self, x):
        x_short = self.short_conv(x)

        x_main = self.main_conv(x)
        x_main = self.blocks(x_main)

        x_final = torch.cat((x_main, x_short), dim=1)
        return self.final_conv(x_final)

picodet 的 CSPLayer 包含了 DarknetBottleneck 层。关于 bottleneck 结构原理可以参考:https://zhuanlan.zhihu.com/p/98692254。

参考

https://zhuanlan.zhihu.com/p/362129030
https://zhuanlan.zhihu.com/p/397020975
https://zhuanlan.zhihu.com/p/62604038
cv中的特征金字塔