经典网络结构 (七):FPN, DetNet

目录

本文主要介绍两个用于物体检测的 backbone:FPN 和 DetNet

多尺度问题

  • 多尺度问题: 为了增强语义性, 传统的物体检测模型通常只在深度卷积网络的最后一个特征图上进行后续操作,而这一层对应的 下采样率通常又比较大,如 16、32,造成小物体在特征图上的有效信息较少, *小物体的检测性能会急剧下降

特征金字塔: FPN (Feature Pyramid Network, 特征金字塔)

  • 解决多尺度问题的关键在于如何 提取多尺度的特征。传统的方法有 图像金字塔 (Image Pyramid),主要思路是将输入图片做成多个尺度,不同尺度的图像生成不同尺度的特征,这种方法简单而有效,大量使用在了 COCO 等竞赛上,但缺点是 非常耗时,计算量也很大
  • 卷积神经网络不同层的大小与语义信息不同,本身就类似一个金字塔结构FPN 就利用了这一特点, *将深层的语义信息传到底层,来补充浅层的语义信息,从而获得了高分辨率、强语义的特征,在小物体检测、实例分割等领域有着非常不俗的表现

FPN 的总体架构

经典网络结构 (七):FPN, DetNet
  • 自下而上:最左侧为普通的卷积网络,默认使用 ResNet 结构,用作提取语义信息。C 1 , . . . , C 5 C_1,…,C_5 C 1 ​,…,C 5 ​ 代表了不同的 ResNet 卷积组,这些卷积组包含了多个 Bottleneck 结构, 组内的特征图大小相同,组间大小递减
  • 自上而下:首先对C 5 C_5 C 5 ​ 进行1 × 1 1×1 1 ×1 卷积降低通道数得到P 5 P_5 P 5 ​,然后依次进行 上采样得到P 4 P_4 P 4 ​、P 3 P_3 P 3 ​ 和P 2 P_2 P 2 ​,目的是得到与C 4 C_4 C 4 ​、C 3 C_3 C 3 ​ 与C 2 C_2 C 2 ​ 长宽相同的特征,以方便下一步进行逐元素相加。这里采用 2 倍最邻近上采样,即直接对临近元素进行复制,而非线性插值
  • 横向连接 (Lateral Connection):目的是为了 将上采样后的高语义特征与浅层的定位细节特征进行融合。高语义特征经过上采样后,其长宽与对应的浅层特征相同,而通道数固定为 256,因此需要对底层特征C 2 C_2 C 2 ​ 至C 4 C_4 C 4 ​ 进行1 × 1 1\times1 1 ×1 卷积使得其通道数变为 256,然后两者进行逐元素相加得到P 4 P_4 P 4 ​、P 3 P_3 P 3 ​ 与P 2 P_2 P 2 ​。由于C 1 C_1 C 1 ​ 的特征图尺寸较大且语义信息不足,因此没有把C 1 C_1 C 1 ​ 放到横向连接中
  • 卷积融合:在得到相加后的特征后,利用3 × 3 3×3 3 ×3 卷积对生成的P 2 P_2 P 2 ​ 至P 4 P_4 P 4 ​ 再进行融合,目的是 *消除上采样过程带来的重叠效应,以生成最终的特征图

FPN 用于物体检测算法

  • 对于实际的物体检测算法,需要在特征图上进行 RoI 提取,而 FPN 有 4 个输出的特征图,选择哪一个特征图上面的特征也是个问题。FPN 给出的解决方法是, *对于不同大小的 RoI,使用不同的特征图,大尺度的 RoI 在深层的特征图上进行提取,小尺度的 RoI 在浅层的特征图上进行提取
import torch.nn as nn
import torch.nn.functional as F
import math

class Bottleneck(nn.Module):
    expansion = 4
    def __init__(self, in_planes, planes, stride=1, downsample=None):
        super(Bottleneck, self).__init__()
        self.bottleneck = nn.Sequential(
                nn.Conv2d(in_planes, planes, 1, bias=False),
                nn.BatchNorm2d(planes),
                nn.ReLU(inplace=True),
                nn.Conv2d(planes, planes, 3, stride, 1, bias=False),
                nn.BatchNorm2d(planes),
                nn.ReLU(inplace=True),
                nn.Conv2d(planes, self.expansion * planes, 1, bias=False),
                nn.BatchNorm2d(self.expansion * planes),
            )
        self.relu = nn.ReLU(inplace=True)
        self.downsample = downsample
    def forward(self, x):
        identity = x
        out = self.bottleneck(x)
        if self.downsample is not None:
            identity = self.downsample(x)
        out += identity
        out = self.relu(out)
        return out

class FPN(nn.Module):
    def __init__(self, layers=[3, 4, 6, 3]):
        super(FPN, self).__init__()
        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)

        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)

        self.toplayer = nn.Conv2d(2048, 256, 1, 1, 0)
        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)

    def _make_layer(self, planes, blocks, stride=1):
        downsample  = None

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

为检测而生: DetNet

  • 传统的 VGG、ResNet 等用于图像分类的 backbone 更加侧重于全图的特征提取,深层的特征图分辨率很低;而物体检测需要定位出物体位置,特征图分辨率不宜过小。而 FPN 虽然提取出了多尺度特征,但仍存在不足。总体可归结为如下两个 缺陷
  • (1) 大物体难以定位:对于 FPN 和 ResNet 等其他网络,大物体对应在较深的特征图上检测,由于网络较深时下采样率较大,物体的边缘难以精确预测,增加了回归边界的难度
  • (2) 小物体难以检测:对于传统网络,由于下采样率大造成小物体在较深的特征图上几乎不可见;FPN 虽从较浅的特征图来检测小物体,但浅层的语义信息较弱,且融合深层特征时使用的上采样操作也会增加物体检测的难度
  • 也就是说, 用于大物体检测的深层特征图也应该具有较大的分辨率,用于小物体检测的浅层特征图应该更加充分地融合深层特征信息。为此, DetNet 引入了 空洞卷积,使得模型兼具较大感受野与较高分辨率,同时避免了 FPN 的多次上采样,实现了较好的检测效果

DetNet 网络结构

  • DetNet 仍然选择性能优越的 ResNet-50 作为基础结构,并保持 前 4 个 stage 与 ResNet-50 相同,具体的结构细节有以下几点:
  • (1) 修改了 Stage 5 并加入新的 Stage 6 用于物体检测。Stage 5 与 Stage 6 使用了 新的 Bottleneck 结构,最大的特点是 利用空洞数为 2 的3 × 3 3×3 3 ×3 卷积取代了步长为2 2 2 的3 × 3 3×3 3 ×3 卷积,在保持感受野增大的同时还能保证特征图大小不变
  • (2) Stage 5 与 Stage 6 的每一个 Bottleneck 输出的特征图尺寸都为原图的1 16 \frac{1}{16}1 6 1 ​,通道数都为 256,因此 在组成特征金字塔时,由于特征图大小和通道数完全相同,因此可以直接从右向左传递相加,避免了 FPN 的上采样操作
  • (3) 为了进一步融合各通道的特征,需要对每一个阶段的输出进行1 × 1 1×1 1 ×1 卷积后再与后一 Stage 传回的特征相加
    经典网络结构 (七):FPN, DetNet

经典网络结构 (七):FPN, DetNet

注意, B B B 相比于A A A ,在恒等映射部分增加了一个1 × 1 1×1 1 ×1 卷积,这样做可以区分开不同的 Stage,并且实验发现这种做法对于特征金字塔式的检测非常重要

from torch import nn

class DetBottleneck(nn.Module):
    def __init__(self, inplanes, planes, extra=False):
        super(DetBottleneck, self).__init__()
        self.bottleneck = nn.Sequential(
                nn.Conv2d(inplanes, planes, 1, bias=False),
                nn.BatchNorm2d(planes),
                nn.ReLU(inplace=True),
                nn.Conv2d(planes, planes, kernel_size=3, stride=1, padding=2,
                               dilation=2, bias=False),
                nn.BatchNorm2d(planes),
                nn.ReLU(inplace=True),
                nn.Conv2d(planes, planes, 1, bias=False),
                nn.BatchNorm2d(planes),
        )
        self.relu = nn.ReLU(inplace=True)
        self.extra = extra
        if self.extra:
            self.extra_conv = nn.Sequential(
                nn.Conv2d(inplanes, planes, 1, bias=False),
                nn.BatchNorm2d(planes)
            )

    def forward(self, x):
        if self.extra:
            identity = self.extra_conv(x)
        else:
            identity = x
        out = self.bottleneck(x)
        out += identity
        out = self.relu(out)
        return out
bottleneck_b = DetBottleneck(1024, 256, True)
bottleneck_a1 = DetBottleneck(256, 256)
bottleneck_a2 = DetBottleneck(256, 256)

References

  • 《深度学习之 PyTorch 物体检测实战》
  • D i v e Dive D i v e I n t o Into I n t o D e e p Deep D e e p L e a r n i n g Learning L e a r n i n g

Original: https://blog.csdn.net/weixin_42437114/article/details/123175302
Author: 连理o
Title: 经典网络结构 (七):FPN, DetNet

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/687522/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球