YOLOF前传:特征金字塔(FPN)

前言

这几天在读CVPR2021的中稿论文YOLOF(You Only Look One-level Feature),文章回顾了单阶段的特征金字塔网络(FPN),指出FPN的成功的原因在于它对目标检测中优化问题的分而治之的解决策略,而不是多尺度特征融合。之前虽然经常看到特征金字塔相关结构,却也没有深入研究过,今天借着YOLOF把FPN的网络结构特征简要总结一下。

01

特征金字塔是多尺度(muiti-scale) 目标检测领域中的重要组成部分,但是由于此方法对计算和内存的需求,在FPN之前的深度学习任务都刻意回避了这类模型。在这篇文章中,作者利用深度神经网络固有的多尺度、多层级的金字塔结构,使用一种 自上而下的侧边连接 在所有尺度上构建出高级语义特征图,构造了特征金字塔的经典结构。

具体做法其实并不难理解:

把低分辨率、高语义信息的高层特征和高分辨率、低语义信息的低层特征自上而下进行融合,使得所有尺度下的特征都有丰富的语义信息。

02

当然,FPN并非只有上图所示的一种结构,下面大体介绍一下特征金字塔网络:

Featurized image pyramid

一种比较笨的多尺度方法,对输入图像设置不同的缩放比例实现多尺度。这样可以解决多尺度,但是相当于训练了多个模型(假设要求输入大小固定),即便允许输入大小不固定,但是也增加了存储不同尺度图像的内存空间。

Single feature map

其实就是早期的CNN模型,通过卷积层不断学习图像的高级语义特征。

Pyramidal feature hierarchy

SSD较早尝试了使用CNN金字塔形的层级特征,重用了前向过程计算出的多尺度特征图,因此这种形式是不消耗额外的资源的。但是SSD为了避免使用low-level的特征,放弃了浅层的特征图信息,直接从conv4_3开始建立金字塔,并且加入了一些新的层,但是这些低层级、高分辨率的特征图信息对检测小目标是非常重要的。

Feature Pyramid Network

FPN为了能够自然地利用CNN层级特征的金字塔形式,同时生成在所有尺度上都具有强语义信息的特征金字塔,便以此为目的设计了top-down结构和lateral connection。这种金字塔结构以此融合具有高分辨率的浅层feature和具有丰富语义信息的深层feature。这样就实现了从单尺度的单张输入图像,快速构建在所有尺度上都具有强语义信息的特征金字塔,同时不产生明显的代价。

03

那么,如何做到top-down和

lateral connection呢?

top-down

def _upsample_add(self, x, y):_,_,H,W = y.size()return F.upsample(x, size=(H,W), mode='bilinear') + y

也就是说,这里的实现使用的是最简单的上采样,没有使用线性插值,没有使用反卷积,而是直接复制。

lateral connection

# init Lateral layers,其实就是做通道匹配任务
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)# forward
p4 = self._upsample_add(p5, self.latlayer1(c4))
p3 = self._upsample_add(p4, self.latlayer2(c3))
p2 = self._upsample_add(p3, self.latlayer3(c2))

结合上图我们可以理解这篇文章的核心思路:

通过2xup-sample,我们得到了上层传递下来的高层语义特征,其尺寸大小与lateral connection过程中的低层特征图尺寸相同;

通过1x1 conv,将高层特征通道数与低层特征通道数统一,解决了融合(sum)过程中channel数不匹配的问题。

04

FPN自上而下的网络结构代码怎么实现?

'''FPN in PyTorch.See the paper "Feature Pyramid Networks for Object Detection" for more details.
'''
import torch
import torch.nn as nn
import torch.nn.functional as Ffrom torch.autograd import Variableclass Bottleneck(nn.Module):expansion = 4def __init__(self, in_planes, planes, stride=1):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, bias=False)self.bn3 = nn.BatchNorm2d(self.expansion*planes)self.shortcut = nn.Sequential()if stride != 1 or in_planes != self.expansion*planes:self.shortcut = nn.Sequential(nn.Conv2d(in_planes, self.expansion*planes, kernel_size=1, stride=stride, bias=False),nn.BatchNorm2d(self.expansion*planes))def forward(self, x):out = F.relu(self.bn1(self.conv1(x)))out = F.relu(self.bn2(self.conv2(out)))out = self.bn3(self.conv3(out))out += self.shortcut(x)out = F.relu(out)return outclass FPN(nn.Module):def __init__(self, block, num_blocks):super(FPN, self).__init__()self.in_planes = 64self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3, bias=False)self.bn1 = nn.BatchNorm2d(64)# Bottom-up layersself.layer1 = self._make_layer(block,  64, num_blocks[0], stride=1)self.layer2 = self._make_layer(block, 128, num_blocks[1], stride=2)self.layer3 = self._make_layer(block, 256, num_blocks[2], stride=2)self.layer4 = self._make_layer(block, 512, num_blocks[3], stride=2)# Top layerself.toplayer = nn.Conv2d(2048, 256, kernel_size=1, stride=1, padding=0)  # Reduce channels# Smooth layersself.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)# Lateral layersself.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)def _make_layer(self, block, planes, num_blocks, stride):strides = [stride] + [1]*(num_blocks-1)layers = []for stride in strides:layers.append(block(self.in_planes, planes, stride))self.in_planes = planes * block.expansionreturn nn.Sequential(*layers)def _upsample_add(self, x, y):'''Upsample and add two feature maps.Args:x: (Variable) top feature map to be upsampled.y: (Variable) lateral feature map.Returns:(Variable) added feature map.Note in PyTorch, when input size is odd, the upsampled feature mapwith `F.upsample(..., scale_factor=2, mode='nearest')`maybe not equal to the lateral feature map size.e.g.original input size: [N,_,15,15] ->conv2d feature map size: [N,_,8,8] ->upsampled feature map size: [N,_,16,16]So we choose bilinear upsample which supports arbitrary output sizes.'''_,_,H,W = y.size()return F.upsample(x, size=(H,W), mode='bilinear') + ydef forward(self, x):# Bottom-upc1 = F.relu(self.bn1(self.conv1(x)))c1 = F.max_pool2d(c1, kernel_size=3, stride=2, padding=1)c2 = self.layer1(c1)c3 = self.layer2(c2)c4 = self.layer3(c3)c5 = self.layer4(c4)# Top-downp5 = 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))# Smoothp4 = self.smooth1(p4)p3 = self.smooth2(p3)p2 = self.smooth3(p2)return p2, p3, p4, p5

05

总之,FPN最主要的意图就是把高层的特征传下来,补充低层的语义,这样就可以在具有高分辨率的底层网络中获得强语义的高层特征,有利于小目标的检测。

FPN网络结构+源码讲解相关推荐

  1. C++简介源码讲解精辟版,C++入门级C++学习,C++与C的区别值得知晓

    C++简介源码讲解精辟版,C++入门级C++学习,C++与C的区别值得知晓 C语言和C++基础区别 C++标准输入和输出 命名空 1.命名空间的定义 : namespace 标识符{ } 例:name ...

  2. ORB特征点提取与均匀化——ORBSLAM2源码讲解(一)

    文章目录 前言 一.基础知识 二.ORB特征均匀化策略对性能的影响 三.ORB特征金字塔 四.ORB提取扩展图像 五.ORB特征均匀化 总结 前言 本博客结合哔哩大学视频ORBSLAM2[ORBSLA ...

  3. 顾客信息表mysql_Qt5.5.0使用mysql编撰小软件源码讲解-顾客信息登记表_mysql

    Qt5.5.0使用mysql编写小软件源码讲解---顾客信息登记表 Qt5.5.0使用mysql编写小软件源码讲解---顾客信息登记表 一个个人觉得比较简单小巧的软件. 下面就如何编写如何发布打包来介 ...

  4. Oriented Fast神奇高效的代码实现方式——ORBSLAM2源码讲解(二)

    文章目录 前言 一.基础知识 二.灰度质心法原理 三.UMAX 四.IC_Angle如何做加速运算 总结 前言 本博客结合哔哩大学视频ORBSLAM2[ORBSLAM2源码讲解专题一]ORB特征点提取 ...

  5. 27.串口通信实验源码讲解

    串口通信实验源码讲解 笔记基于正点原子官方视频 视频连接https://www.bilibili.com/video/BV1Wx411d7wT?p=71&spm_id_from=333.100 ...

  6. 双目相机标定OpenCV源码讲解

    双目相机标定OpenCV源码讲解 背景介绍 所述内容 参考资料 摄像机标定部分代码 代码思路 代码中的其他函数 找角点&求内参 求外参 求矫正映射矩阵 后记 背景介绍 暑假接近两个月的时间做了 ...

  7. android飞信短信箱程序源码讲解

    android飞信短信箱程序源码讲解! 一.程序演示 图1.进入程序后的第1页面,头部的新消息提示在任意页面都会弹出. 图2.未读消息数量提示,任意页面. 图3.点击新建短信,(或者会话中的转发选项) ...

  8. 讲解java源码_Java学习之Java源码讲解

    关于Java中源码的学习,是不少同学头疼的知识点.本文整理了JAVA源码学习的八大要点,分别是基础知识.面向对象.异常处理.集合.综合类核心代码.JAVA8新特性.Input/Output和Java小 ...

  9. OpenCV SIFT源码讲解——代码逻辑宏观窥探

    OpenCV SIFT源码讲解--代码逻辑宏观窥探 一.暴露在外的接口:SIFT 二.隐藏在SIFT背后的本质:SIFT_Impl 三.使用sift算法全流程 一.暴露在外的接口:SIFT 一般来说, ...

  10. 深入Redis内部-Redis 源码讲解

    Redis 作为 NoSQL 数据库的杰出代表,一直广受关注,其轻量级的敏捷架构,向来有存储中的瑞士军刀之称.下面推荐的一篇文章,从源码的角度讲解了Redis 的整个工作流程,是了解 Redis 流程 ...

最新文章

  1. 聊聊flink的StateTtlConfig
  2. Scala学习之映射(Map)
  3. android 的命令行工具(dos命令)
  4. tree命令的使用(过滤文件夹)
  5. MySQL中Slave_IO_Running: Connecting问题
  6. 多校3 1008 Solve this interesting problem
  7. 使用phpstudy中的apache进行虚拟主机的配置(自定义网站名进行访问,如http://wei.com)
  8. 黄聪:Linq初级班 Linq To XML体验(编程篇)
  9. matlab高级教程,Matlab绘图系列之高级绘图教程
  10. ucserver admin.php,如何修改discuz管理员密码以及Ucenter初始管理密码
  11. HeadFirstJava 10数字与静态
  12. 1688商品类目API接口-(item_cat_get-获得1688商品类目接口)
  13. table表单的制作
  14. .net core使用ef 6
  15. node对接微信支付 sdk tenpay
  16. JAVA毕业设计共享充电宝管理系统计算机源码+lw文档+系统+调试部署+数据库
  17. 如何从容应对新技术暗潮
  18. 什么是中华田园敏捷开发?
  19. android 8.0动态申请读写权限
  20. Robotstudio基础教程之系统创建

热门文章

  1. 在大多数情况下病毒入侵计算机系统以后,网络支付与安全练习题库
  2. 【经典算法题】排列序列
  3. 深度学习优化算法:AdaDelta算法
  4. java中 this详解
  5. Linux ab 压力测试工具
  6. 模具设计进程中应注意哪些问题
  7. CentOS 7下Red5流媒体服务器的搭建与测试
  8. 必须了解的一些IT知识点
  9. 屏幕共享软件都有什么功能呢?
  10. c# 火狐浏览器怎么嵌入窗体中_.net winform程序下使用firefox作为Web浏览器