FPN(Feature Pyramid Networks)

FPN详细

FPN(Feature Pyramid Networks)_第1张图片

  • 图a
    图a是在传统的图像处理当中是比较常见的一个方法。针对我们要检测不同尺度的目标时,会将图片缩放成不同的尺度,然后将每个尺度的图片依次通过我们的算法进行预测。 优点是它创建的多尺度特征的所有层次都包含很强的语义特征,包括高分辨率的层次。这种方法的优点是精度比较高。缺点是我们生成多少尺度的图片我们就要重新去预测多少次,需要大量的算力和内存空间。

  • 图b
    图b是Fast R-CNN和Faster R-CNN中采用的一种方式。将图片通过进行卷积和池化操作得到我们最终的特征图,然后在最终的特征图上进行预测。它只使用了最后卷积层的结果,卷积网络中的不同层次会产生不同空间分辨率的特征图,但是不同的卷积层得到的特征图会有很大的语义鸿沟。高分辨率具有很好的低层次特征,但是不利于识别物体,低分辨率的特征具有很好的高层次特征,但是不利于识别小物体。

  • 图c
    SSD网络中使用的是图c所示金字塔,SSD中将卷积网络中不同层计算出的特征图组成了一个特征金字塔。但是为了避免使用低层次的特征,在构建特征金子塔的时候是从后面的层次开始,并且又追加了几个新层。这样就损失了高分辨率的特征图,对检测小目标是不利的。

  • 图d
    也就是FPN结构,将不同特征图上的特征去进行融合,在融合得到的特征图上再进行预测。主要分了自下向上的一条路径和自上到下的一条路径。自下向上就是深度卷积网络的前向提取特征的过程,自上而下则是对最后卷积层的特征图进行上采样的过程,横向的连接则是融合深层的卷积层特征和浅层卷积特征的过程。这也就是为什么对小物体也有很好的检测效果,它融合了深层卷积层的高级别特征和浅层卷积层的低级别特征。

FPN结构

FPN(Feature Pyramid Networks)_第2张图片

第一步: 自下而上的路径。取深度卷积网络,每次卷积图片尺度缩小一倍,我们记为C2 , C3 , C4。C4为最小的特征图。将空间信息少但是语义信息强的最深层卷积层的C4进行1x1卷积调整通道数再进行最大池化操作得到P5,注意P5只用于RPN部分,不在Fast-RCNN中使用。

第二步: 自上而下的路径。首先得到P4;将P4进行2倍的上采样与进行1x1的卷积调整通道后的C3利用横向连接加到一起得到P3;最后将P3进行2倍的上采样与进行1x1的卷积调整通道后的C2利用横向连接加到一起得到P2。
[Image]
第三步: 得到的结果P4,P3,P2后面接一个3x3的卷积来减轻上采样的混叠效应(aliasing effect),进行预测。

coding

class FPN(nn.Module):
    '''
    FPN需要初始化一个list,代表ResNet每一个阶段的Bottleneck的数量
    '''
    def __init__(self, layers):
        super(FPN, self).__init__()
        #构建C1
        self.inplanes = 64
        self.conv1 = nn.Conv2d(3, 64, 7, 2, 3, bias=False)
        self.bn1 = nn.BatchNorm2d(64)
        self.relu = nn.ReLU(inplace=True)
        self.maxpool = nn.MaxPool2d(3, 2, 1)
 
        #自下而上搭建C2、C3、C4、C5
        self.layer1 = self._make_layer(64, layers[0])
        self.layer2 = self._make_layer(128, layers[1], 2)
        self.layer3 = self._make_layer(256, layers[2], 2)
        self.layer4 = self._make_layer(512, layers[3], 2)
        #对C5减少通道,得到P5
        self.toplayer = nn.Conv2d(2048, 256, 1, 1, 0)
 
        #3*3卷积融合
        self.smooth1 = nn.Conv2d(256, 256, 3, 1, 1)
        self.smooth2 = nn.Conv2d(256, 256, 3, 1, 1)
        self.smooth3 = nn.Conv2d(256, 256, 3, 1, 1)
 
        #横向连接,保证每一层通道数一致
        self.latlayer1 = nn.Conv2d(1024, 256, 1, 1, 0)
        self.latlayer2 = nn.Conv2d( 512, 256, 1, 1, 0)
        self.latlayer3 = nn.Conv2d( 256, 256, 1, 1, 0)
 
    #构建C2到C5
    def _make_layer(self, planes, blocks, stride=1):
        downsample  = None
        #如果步长不为1,进行下采样
        if stride != 1 or self.inplanes != Bottleneck.expansion * planes:
            downsample  = nn.Sequential(
                nn.Conv2d(self.inplanes, Bottleneck.expansion * planes, 1, stride, bias=False),
                nn.BatchNorm2d(Bottleneck.expansion * planes)
            )
        layers = []
        layers.append(Bottleneck(self.inplanes, planes, stride, downsample))
        #更新输入输出层
        self.inplanes = planes * Bottleneck.expansion
        #根据block数量添加bottleneck的数量
        for i in range(1, blocks):
            layers.append(Bottleneck(self.inplanes, planes))
        return nn.Sequential(*layers
                             
    #自上而下上采样
    def _upsample_add(self, x, y):
        _,_,H,W = y.shape
        #逐个元素相加
        return F.upsample(x, size=(H,W), mode='bilinear') + y
 
    def forward(self, x):
        #自下而上
        c1 = self.maxpool(self.relu(self.bn1(self.conv1(x))))
        c2 = self.layer1(c1)
        c3 = self.layer2(c2)
        c4 = self.layer3(c3)
        c5 = self.layer4(c4)
 
        #自上而下,横向连接
        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))
        
        #卷积融合,平滑处理
        p4 = self.smooth1(p4)
        p3 = self.smooth2(p3)
        p2 = self.smooth3(p2)
        return p2, p3, p4, p5
 

你可能感兴趣的:(卷积神经网络,深度学习,计算机视觉,人工智能)