FPN详述

FPN详述详细讲解特征金字塔(FPN)这个网络的结构。

大家好,又见面了,我是你们的朋友全栈君。

简介

为了使用更多的语义信息,目标检测模型一般在卷积神经网络最后一层的特征图上进行后续操作(随着不断地下采样,语义信息更丰富,空间信息更稀少),而这一层对应的下采样率一般是比较大的,如16或者32。这就造成原始图像上地小目标在特征图上有效信息较少,小物体检测性能急剧下降,这就是目标检测中的多尺度问题,这个问题很直观,小目标在原图中对应的像素本就不多,下采样后很难找到对应信息。

为了解决多尺度问题,关键在于如何获取多尺度的特征,从数据准备的角度出发有图像金字塔这一策略,它的想法就是将图像调整为多个尺度,不同尺度图像对应不同的特征,这个思路的缺点就是计算量非常之大。不过,它也为后来的一些方法提供了思考,要知道,卷积神经网络层层递进,尺度和语义信息不断变化的过程就是一个金字塔结构,如何能够有效融合不同层卷积生成的特征图,应该可以较好地改善多尺度检测问题,FPN(Feature Pyramid Network)就是以此为出发点诞生的工作,极大地推动了后来目标检测的发展。

  • 论文标题

    Feature Pyramid Networks for Object Detection

  • 论文地址

    http://arxiv.org/abs/1612.03144

  • 论文源码

    https://github.com/jwyang/fpn.pytorch(非官方)

网络结构

FPN的结构如下图所示,主要有自下而上、自上而下、横向连接和卷积融合四个操作流程,我们一个个来看。不过,在此之前先对下图做一些必要的说明,图中最左侧的一列表示输入图像经过多个stage的卷积层不断下采样修改特征图的过程,其中C1为最浅几层,空间尺度大语义信息极少,一般不做考虑。

在这里插入图片描述

首先,来看最左侧的自下而上的操作,这个流是一个普通的ResNet网络,用来提取语义信息,C1至C5代表网络的5个stage(即不同的卷积层),每个stage内部特征图尺寸不变,从C1到C5特征图尺寸递减。由这个卷积网络提取特征的过程,就是自下而上的过程。

接着,我们来看中间那个自上而下操作,首先,对最小的特征图进行1×1卷积降维通道得到P5,然后对P5依此进行上采样得到P4、P3和P2,这个步骤是为了保证P4、P3和P2与C4、C3和C2长宽相同,以方便后面的逐元素相加。需要注意的是,这里的上采样实际上是最近邻上采样(临近元素复制),而不是线性插值。

然后,就是所谓的横向连接(Lateral Connection),这个步骤的目的就是将上采样后的高语义特征与下采样之前对应大小的定位细节特征进行融合。不过,上采样的尺寸虽然和浅层特征图一样,但是通道数是不同的,因此为了相加,需要将C2、C3和C4通过1×1卷积调整和P2、P3和P4通道数目一致,均为256。继而逐元素相加得到真正的P2、P3和P4,这个过程就叫做横向连接。

最后就是卷积融合的过程,得到相加的P2、P3和P4之后,再通过3×3卷积对这三个特征图进行变换以消除上采样过程带来的重叠效应,生成最终的P2、P3和P4,它们与P5一同输出进行后续任务。

至此,FPN网络结构最核心的部分就讲完了,不过实际目标检测需要在特征图上进行RoI特征提取(通过anchor等),而FPN有四个特征图,因此选用哪个就是值得考虑的。FPPN的方案是,对于不同尺寸的RoI区域,使用不同的特征图,大尺寸RoI需要更加精细的语义信息,即使深层特征图也会有较多信息保留,如在P5上进行;而小尺寸RoI因为尺寸小,高语义信息很难定位,因此采样浅层特征图,如P2。

实验

在检测网络中,将neck换为FPN,获得了非常恐怖的效果提升,包括后来的anchor-free的发展,也与FPN的成功是分不开的。

在这里插入图片描述

代码实现

FPN实现起来不是很难,很多工具箱都做了封装,这里就给出一个比较使用的PyTorch实现版本。

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


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


if __name__ == '__main__':
    fpn = FPN([3, 4, 6, 3])
    print(fpn)

总结

FPN通过金字塔结构将深层语义信息回传补充浅层语义信息,从而获得了高分辨率、强语义的多层特征,在小目标检测、实例分割领域有着不俗的表现。本文涉及的代码都可以在我的Github找到,欢迎star或者fork。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/134498.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • polkit启动失败_zabbix4.4 启动失败分析

    polkit启动失败_zabbix4.4 启动失败分析zabbix是基于WEB界面提供分布式系统监视以及网络监视功能的企业级开源解决方案,能监视各种网络参数,保证服务器系统的安全运营;并提供灵活的通知机制以让系统管理员快速定位/解决存在的各种问题。做为开源用户的支持者,我们大部分环境用的软件包含监控软件、数据库、继承应用、操作系统等都是用开源的,例如centos、PG、zabbix、openshift等,但是开源的在成熟度上是不错,但是安装软件有时比…

  • 提升进程权限-OpenProcessToken等函数的用法[通俗易懂]

    提升进程权限-OpenProcessToken等函数的用法[通俗易懂]提升进程权限文章一:在枚举/结束系统进程或操作系统服务时,会出现自己权限不足而失败的情况,这时就需要提升自己进程到系统权限,其实提升权限的代码很简单的,看到过的最经典的应该是《WINDOWS核心编程》第四章中操作进程给出的那个函数了,如果我们真的不了解它的操作也不要紧,因为只要在你需要的地方调用下面这个函数就是了,以下是它的代码:BOOLEnablePriv(){HAND

  • php工厂模式

    php工厂模式定义:我们只需要提供一个创建对象实例的功能,而无需关心其具体实现,被创建实例的类型可以是接口、抽象类,也可以是具体的类。一、简单工厂模式(平时开发中基本上简单工厂模式就够用了)说明: Api:定义客户所需要的功能接口(后面具体实现的类基本上就根据这个来) Impl:具体实现Api的实现类,一般有多个, Factory:工厂,选择合适的实现类来创建Api接…

  • Java线程和进程区别

    Java线程和进程区别什么是进程,什么是线程?进程:是并发执行的程序在执行过程中分配和管理资源的基本单位,是一个动态概念,竞争计算机系统资源的基本单位。线程:是进程的一个执行单元,是进程内科调度实体。比进程更小的独立运行的基本单位。线程也被称为轻量级进程。一个程序至少一个进程,一个进程至少一个线程。进程线程的区别1、地址空间:同一进程的线程共享本进程的地址空间,而进程之间则是独立的地址空间。2、…

  • 详细理解HashMap数据结构,太齐全了!「建议收藏」

    写在前面:小伙伴儿们,大家好!今天来学习HashMap相关内容,作为面试必问的知识点,来深入了解一波!思维导图:1,HashMap集合简介HashMap基于哈希表的Map接口实现,是以key-value存储形式存在,即主要用来存放键值对。HashMap的实现不是同步的,这意味着它不是线程安全的。它的key、value都可以为null。此外,HashMap中的映射不是有序的。JDK1.8之前的HashMap由数组+链表组成的,数组是HashMap的主体,链表则是主要为了节解决哈希碰

  • 分辨率,像素,像素密度易懂

    分辨率,像素,像素密度易懂分辨率是什么?一般会说这个屏幕的分辨率是1920*1080,这就说明纵向和横向上有1920个和1080个像素点;像素点是什么?一个像素点就是一个色彩块,没有实际的物理尺寸;什么是屏幕像素密度?一英寸长的一条线上理论上会有多少个像素点;例如:一个手机长边有1920个像素点,短边有1080个像素点,屏幕大小(对角线的物理大小)是5.2英寸的,那么屏幕密度是怎么计…

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号