FPN:feature pyramid networks for object detection

论文:feature pyramid networks for object detection
论文链接

论文概述:

作者提出的多尺度的object detection算法:FPN(feature pyramid networks)。原来多数的object detection算法都是只采用顶层特征做预测,但我们知道低层的特征语义信息比较少,但是目标位置准确;高层的特征语义信息比较丰富,但是目标位置比较粗略。另外虽然也有些算法采用多尺度特征融合的方式,但是一般是采用融合后的特征做预测,而本文不一样的地方在于预测是在不同特征层独立进行的。

特征金字塔

下图展示了4种利用特征的形式:
(a)图像金字塔,即将图像做成不同的scale,然后不同scale的图像生成对应的不同scale的特征。这种方法的缺点在于增加了时间成本。有些算法会在测试时候采用图像金字塔。
(b)像SPP net,Fast RCNN,Faster RCNN是采用这种方式,即仅采用网络最后一层的特征。
(c)像SSD(Single Shot Detector)采用这种多尺度特征融合的方式,没有上采样过程,即从网络不同层抽取不同尺度的特征做预测,这种方式不会增加额外的计算量。作者认为SSD算法中没有用到足够低层的特征(在SSD中,最低层的特征是VGG网络的conv4_3),而在作者看来足够低层的特征对于检测小物体是很有帮助的。
(d)本文作者是采用这种方式,顶层特征通过上采样和低层特征做融合,而且每层都是独立预测的。
在这里插入图片描述

如下图所示,我们可以看到图像中存在不同尺寸的目标,而不同的目标具有不同的特征,利用浅层的特征就可以将简单的目标的区分开来;利用深层的特征可以将复杂的目标区分开来;这样我们就需要这样的一个特征金字塔来完成这件事。图中我们在第1层(请看绿色标注)输出较大目标的实例分割结果,在第2层输出次大目标的实例检测结果,在第3层输出较小目标的实例分割结果。检测也是一样,我们会在第1层输出简单的目标,第2层输出较复杂的目标,第3层输出复杂的目标。
在这里插入图片描述

作者的主网络采用ResNet。
作者的算法大致结构如下图:一个自底向上的线路,一个自顶向下的线路,横向连接(lateral connection)。图中放大的区域就是横向连接,这里1*1的卷积核的主要作用是减少卷积核的个数,也就是减少了feature map的个数,并不改变feature map的尺寸大小。
在这里插入图片描述

自底向上其实就是网络的前向过程。在前向过程中,feature map的大小在经过某些层后会改变,而在经过其他一些层的时候不会改变,作者将不改变feature map大小的层归为一个stage,因此每次抽取的特征都是每个stage的最后一个层输出,这样就能构成特征金字塔。
自顶向下的过程采用上采样(upsampling)进行,而横向连接则是将上采样的结果和自底向上生成的相同大小的feature map进行融合(merge)。在融合之后还会再采用3*3的卷积核对每个融合结果进行卷积,目的是消除上采样的混叠效应(aliasing effect)。并假设生成的feature map结果是P2,P3,P4,P5,和原来自底向上的卷积结果C2,C3,C4,C5一一对应。
在这里插入图片描述

利用FPN构建Faster R-CNN检测器

步骤:

  • 首先,选择一张需要处理的图片,然后对该图片进行预处理操作;
  • 然后,将处理过的图片送入预训练的特征网络中(如ResNet等),即构建所谓的bottom-up网络;
  • 接着,如下图所示,构建对应的top-down网络(即对层4进行上采样操作,先用1x1的卷积对层2进行降维处理,然后将两者相加(对应元素相加),最后进行3x3的卷积操作,最后);
  • 接着,在图中的4、5、6层上面分别进行RPN操作,即一个3x3的卷积后面分两路,分别连接一个1x1的卷积用来进行分类和回归操作;
  • 接着,将上一步获得的候选ROI分别输入到4、5、6层上面分别进行ROI Pool操作(固定为7x7的特征);
  • 最后,在上一步的基础上面连接两个1024层的全连接网络层,然后分两个支路,连接对应的分类层和回归层;
    在这里插入图片描述
    FPN整体架构

注:层1、2、3对应的支路就是bottom-up网络,就是所谓的预训练网络,文中使用了ResNet网络;由于整个流向是自底向上的,所以我们叫它bottom-up;层4、5、6对应的支路就是所谓的top-down网络,是FPN的核心部分,名字的来由也很简单。

FPN构建Faster R-CNN检测器的Faster R-CNN+FPN细节图:
在这里插入图片描述

FPN性能评估

  1. FPN效果定量评估

在这里插入图片描述
表1 Faster R-CNN+FPN结果

如上表所示,我们可以看到当我们使用相同的预训练网络、相同的RPN网络、Fast R-CNN使用不同的方法时,我们的特征层由原来的C4或者C5变化为现在的特征集合Pk,同时FPN方案使用了横向连接(lateral)和top-down模型,算法的性能有了大幅度上升,与a相比提升了2.2个百分点,与b相比提升了4.0个百分点(AP@0.5);对于APs,提升了5.9个百分点;对于APm,提升了5.3个百分点。这也说明了FPN能够提升小目标的检测效果。

在这里插入图片描述

表2 FPN高效训练结果

观察表2,我们可以看到使用FPN的Fast R-CNN+FPN和没有使用FPN的Fast R-CNN方案之间的差别,首先我们的特征维度由1024减少到256维(这样可以大大的减少一些运算量,包括前向和反向运算);我们利用2个MLP层取代了Conv5,作为我们的头分类器;我们的训练时间由原来的44.6小时减少到现在的10.6小时,速度大概提升了4倍;我们的推理时间由原来的0.32s减少到现在的0.15s;最后,我们的准确率提升了2.0个百分点。主要的原因是因为我们通过FPN获得了更加鲁邦的高层语义特征,它使得我们的学习过程更加高效。

在这里插入图片描述
表3 COCO数据集结果展示

如上表所示,我们测试了FPN算法在COCO数据集上面的性能表现,使用了FPN的Faster R-CNN方法获得了很多的最佳指标,尤其是在APs指标上面。总之,获得了最好的单模型准确率。
在这里插入图片描述

表4 使用了FPN的RPN效果

如上表所示,我们比较了比较了FPN+RPN和RPN这两种方案,我们可以看到我们的性能有了大幅度的提升。

在这里插入图片描述

表5 top-down和lateral的重要性

在表5中,我们验证了top-down模型和Lateral连接的重要性,同时使用两者的FPN方案取得了最好的结果。相对来讲,Lateral连接起到了更好的效果。

在这里插入图片描述

表7 FPN在实例分割上面的效果

由于FPN是一个特征金字塔,具有很好地泛华能力,可以利用到很多利用深度学习网络的方法中去,包括目标检测、实例分割等。如上表所示,使用了FPN的DeepMask方法可以不仅可以获得性能的提升,同时可以获得速度的提升。(不同的目标在不同的层上面生成对应的Mask)!

总结
作者提出的FPN(Feature Pyramid Network)算法同时利用低层特征高分辨率和高层特征的高语义信息,通过融合这些不同层的特征达到预测的效果。并且预测是在每个融合后的特征层上单独进行的,这和常规的特征融合方式不同。

自己实现的代码,仅供参考:

import torch.nn as nn
import torch.nn.functional as F
import math

__all__ = ['FPN']

class Bottleneck(nn.Module):
    expansion = 4
    #这里,我们先实现的是Bottleneck的左半边(1*1->3*3->1*1),expansion = 最后一层的输出通道数/第一层的输入通道数
    def __init__(self,in_planes,planes,stride=1,downsample=None):
        super(Bottleneck, self).__init__()
        self.conv1 = nn.Conv2d(in_planes,planes,kernel_size=1,bias=False)
        self.bn1 = nn.BatchNorm2d(planes)

        self.conv2 = nn.Conv2d(planes,planes,kernel_size=3,stride=stride,padding=1,bias=False)
        self.bn2 = nn.BatchNorm2d(planes)

        self.conv3 = nn.Conv2d(planes,self.expansion*planes,kernel_size=1,stride=stride,bias=False)
        self.bn3 = nn.BatchNorm2d(self.expansion*planes)
        self.relu = nn.ReLU(inplace=True)
        self.downsample = downsample
        self.stride = stride

    #这里加上了residual
    def forward(self,x):
        residual = x            #右边的short_cut

        out = self.conv1(x)
        out = self.bn1(out)
        out = self.relu(out)

        out = self.conv2(out)
        out = self.bn2(out)
        out = self.relu(out)

        out = self.con3(out)
        out = self.bn3(out)
        out = self.relu(out)

        if self.downsample is not None:
            residual = self.downsample(x)

        out += residual
        out += self.relu(out)

        return out

class FPN(nn.Module):
    #这里传入的参数block用于传入Bottleneck
    def __init__(self,block,layers):
        super(FPN, self).__init__()
        self.inplanes = 64

        #这部分是FPN最开始的一个Conv1,为了节省内存,没有将这部分纳入金字塔
        self.conv1 = nn.Conv2d(3,64,kernel_size=7,stride=2,padding=3,bias=False)
        self.bn1 = nn.BatchNorm2d(64)
        self.relu = nn.ReLU(inplace=True)
        self.maxpool = nn.MaxPool2d(kernel_size=3,stride=2,padding=1)

        #Bottom-up layers
        self.layer1 = self._make_layer(block,layers[0])
        self.layer2 = self._make_layer(block,layers[1],stride=2)
        self.layer3 = self._make_layer(block,layers[2],stride=2)
        self.layer4 = self._make_layer(block,layers[3],stride=2)

        #Top layer
        self.toplayer = nn.Conv2d(2048,256,kernel_size=1,stride=1,padding=0)

        #FPN朝右边延伸的,Smooth layers
        self.smooth1 = nn.Conv2d(256,256,kernel_size=3,stride=1,padding=1)
        self.smooth2 = nn.Conv2d(256,256,kernel_size=3,stride=1,padding=1)
        self.smooth3 = nn.Conv2d(256,256,kernel_size=3,stride=1,padding=1)

        #FPN的左边的特征金字塔连接到右边
        self.latlayer1 = nn.Conv2d(1024,256,kernel_size=1,stride=1,padding=0)
        self.latlayer2 = nn.Conv2d(512,256,kernel_size=1,stride=1,padding=0)
        self.latlayer3 = nn.Conv2d(256,256,kernel_size=1,stride=1,padding=0)

        #这部分是初始化conv的值,还有BN的值
        for m in self.modules():
            if isinstance(m,nn.Conv2d):
                n = m.kernel_size[0]*m.kernel_size[1]*m.out_channels
                m.weight.data.normal_(0,math.sqrt(2./n))
            elif isinstance(m,nn.BatchNorm2d):
                m.weight.data.fill_(1)
                m.bias.data.zero_()

    #blocks是Bottleneck的数量
    def _make_layer(self,block,planes,blocks,stride=1):
        downsample = None
        if stride != 1 or self.inplanes!= block.expansion*planes:
            #这里的downsample用来实现Bottleneck的右边
            downsample = nn.Sequential(
                nn.Conv2d(self.inplanes,block.expansion*planes,kernel_size=1,stride=stride,bias=False),
                nn.BatchNorm2d(block.expansion*planes)
            )
        layers = []
        layers.append(block(self.inplanes,planes,stride,downsample))
        self.inplanes = planes * block.expansion
        for i in range(1,blocks):
            layers.append(block(self.inplanes,planes))

        return nn.Sequential(*layers)

    #通过这个操作来结合左边过来的1*1+top-bottom
    def _upsample_add(self,x,y):
        _,_,H,W = y.size()
        return F.upsample(x,size=(H,W),mode='bilinear')+y

    def forward(self,x):
        #Bottom-up
        x = self.conv1(x)
        x = self.bn1(x)
        x = self.relu(x)
        c1 = self.maxpool(x)

        c2 = self.layer1(c1)
        c3 = self.layer2(c2)
        c4 = self.layer3(c3)
        c5 = self.layer4(c4)

        #top-bottom
        p5 = self.toplayer(c5)
        p4 = self._upsample_add(p5,self.latlayer1(c4))
        p3 = self._upsample_add(p4,self.latlayer2(c3))
        p2 = self._upsample_add(p3,self.latlayer3(c2))

        #smooth
        p4 = self.smooth1(p4)
        p3 = self.smooth2(p3)
        p2 = self.smooth3(p2)
        return p2,p3,p4,p5

#101以上的才为Bottleneck
def FPN101():
    return FPN(Bottleneck,[2,2,2,2])

代码的话有以下参考:
fpn.pytorch
FPN_Tensorflow

  • 1
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Cpp编程小茶馆

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值