本篇文章是论文阅读笔记和网络理解心得总结而来,部分资料和图参考论文和网络资料

论文背景

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

引言(Introduction)

从上图可以看出,(a)使用图像金字塔构建特征金字塔。每个图像尺度上的特征都是独立计算的,速度很慢。(b)最近的检测系统选择(比如 Faster RCNN)只使用单一尺度特征进行更快的检测。(c)另一种方法是重用 ConvNet(卷积层)计算的金字塔特征层次结构(比如 SSD),就好像它是一个特征化的图像金字塔。(d)我们提出的特征金字塔网络(FPN)与(b)和(c)类似,但更准确。在该图中,特征映射用蓝色轮廓表示,较粗的轮廓表示语义上较强的特征

特征金字塔网络 FPN

作者提出的 FPN 结构如下图:这个金字塔结构包括一个自底向上的线路,一个自顶向下的线路和横向连接(lateral connections)

自底向上其实就是卷积网络的前向过程。在前向过程中,feature map 的大小在经过某些层后会改变,而在经过其他一些层的时候不会改变,作者将不改变 feature map 大小的层归为一个 stage,因此这里金字塔结构中每次抽取的特征都是每个 stage 的最后一个层的输出。在代码中我们可以看到共有C1、C2、C3、C4、C5五个特征图,C1C2 的特征图大小是一样的,所以,FPN 的建立也是基于从 C2C5 这四个特征层上。

自顶向下的过程采用上采样(upsampling)进行,而横向连接则是将上采样的结果和自底向上生成的相同大小的 feature map 进行融合(merge)。在融合之后还会再采用 3*3 的卷积核对每个融合结果进行卷积,目的是消除上采样的混叠效应(aliasing effect)。并假设生成的 feature map 结果是 P2,P3,P4,P5,和原来自底向上的卷积结果 C2,C3,C4,C5一一对应。

这里贴一个 ResNet 的结构图:论文中作者采用 conv2_x,conv3_x,conv4_x 和 conv5_x 的输出,对应 C1,C2,C3,C4,C5,因此类似 Conv2就可以看做一个stage。

FPN网络建立

这里自己没有总结,因为已经有篇博文总结得很不错了,在这

通过 ResNet50 网络,得到图片不同阶段的特征图,最后利用 C2,C3,C4,C5 建立特征图金字塔结构:

  1. 将 C5 经过 256 个 1*1 的卷积核操作得到:32*32*256,记为 P5;
  2. 将 P5 进行步长为 2 的上采样得到 64*64*256,再与 C4 经过的 256 个 1*1 卷积核操作得到的结果相加,得到 64*64*256,记为 P4;
  3. 将 P4 进行步长为 2 的上采样得到 128*128*256,再与 C3 经过的 256 个 1*1 卷积核操作得到的结果相加,得到 128*128*256,记为 P3;
  4. 将 P3 进行步长为 2 的上采样得到 256*256*256,再与 C2 经过的 256 个 1*1 卷积核操作得到的结果相加,得到 256*256*256,记为 P2;
  5. 将 P5 进行步长为 2 的最大池化操作得到:16*16*256,记为 P6;

结合从 P2 到 P6 特征图的大小,如果原图大小 1024*1024, 那各个特征图对应到原图的步长依次为 [P2,P3,P4,P5,P6]=>[4,8,16,32,64]。

Anchor锚框生成规则

Faster RCNN 采用 FPN 的网络作 backbone 后,锚框的生成规则也会有所改变。基于上一步得到的特征图 [P2,P3,P4,P5,P6],再介绍下采用 FPN 的 Faster RCNN(或者 Mask RCNN)网络中 Anchor 锚框的生成,根据源码中介绍的规则,与之前 Faster-RCNN 中的生成规则有一点差别。

  1. 遍历 P2 到 P6 这五个特征层,以每个特征图上的每个像素点都生成 Anchor 锚框;
  2. 以 P2 层为例,P2 层的特征图大小为 256*256,相对于原图的步长为4,这样 P2上的每个像素点都可以生成一个基于坐标数组 [0,0,3,3] 即 4*4 面积为 16 大小的Anchor锚框,当然,可以设置一个比例 SCALE,将这个基础的锚框放大或者缩小,比如,这里设置 P2 层对应的缩放比例为 16,那边生成的锚框大小就是长和宽都扩大16倍,从 4*4 变成 64*64,面积从 16 变成 4096,当然在保证面积不变的前提下,长宽比可以变换为 32*128、64*64 或 128*32,这样以长、宽比率 RATIO = [0.5,1,2] 完成了三种变换,这样一个像素点都可以生成3个Anchor锚框。在 Faster-RCNN 中可以将 Anchor scale 也可以设置为多个值,而在MasK RCNN 中则是每一特征层只对应着一个 Anchor scale即对应着上述所设置的 16
  3. P2 层每个像素点位中心,对应到原图上,则可生成 256*256*3(长宽三种变换) = 196608 个锚框;
  4. P3 层每个像素点为中心,对应到原图上,则可生成 128*128*3 = 49152 个锚框;
  5. P4 层每个像素点为中心,对应到原图上,则可生成 64*64*3 = 12288 个锚框;
  6. P5 层每个像素点为中心,对应到原图上,则生成 32*32*3 = 3072 个锚框;
  7. P6 层每个像素点为中心,对应到原图上,则生成 16*16*3 = 768 个锚框。

从 P2 到 P6 层一共可以在原图上生成 \(196608 + 49152 + 12288 + 3072 + 768 = 261888\) 个 Anchor 锚框。

实验

看看加入FPN 的 RPN 网络的有效性,如下表 Table1。网络这些结果都是基于 ResNet-50。评价标准采用 AR,AR 表示 Average Recall,AR 右上角的 100 表示每张图像有 100 个 anchor,AR 的右下角 s,m,l 表示 COCO 数据集中 object 的大小分别是小,中,大。feature 列的大括号 {} 表示每层独立预测。

从(a)(b)(c)的对比可以看出 FPN 的作用确实很明显。另外(a)和(b)的对比可以看出高层特征并非比低一层的特征有效。

(d)表示只有横向连接,而没有自顶向下的过程,也就是仅仅对自底向上(bottom-up)的每一层结果做一个 1*1 的横向连接和 3*3 的卷积得到最终的结果,有点像 Fig1 的(b)。从 feature 列可以看出预测还是分层独立的。作者推测(d)的结果并不好的原因在于在自底向上的不同层之间的 semantic gaps 比较大。

(e)表示有自顶向下的过程,但是没有横向连接,即向下过程没有融合原来的特征。这样效果也不好的原因在于目标的 location 特征在经过多次降采样和上采样过程后变得更加不准确。

(f)采用 finest level 层做预测(参考 Fig2 的上面那个结构),即经过多次特征上采样和融合到最后一步生成的特征用于预测,主要是证明金字塔分层独立预测的表达能力。显然 finest level 的效果不如 FPN 好,原因在于 PRN 网络是一个窗口大小固定的滑动窗口检测器,因此在金字塔的不同层滑动可以增加其对尺度变化的鲁棒性。另外(f)有更多的 anchor,说明增加 anchor 的数量并不能有效提高准确率

代码解读

这里给出一个基于 PytorchFPN 网络的代码,来自这里

  1. ## ResNet的block
  2. class Bottleneck(nn.Module):
  3. expansion = 4
  4. def __init__(self, in_planes, planes, stride=1):
  5. super(Bottleneck, self).__init__()
  6. self.conv1 = nn.Conv2d(in_planes, planes, kernel_size=1, bias=False)
  7. self.bn1 = nn.BatchNorm2d(planes)
  8. self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=stride, padding=1, bias=False)
  9. self.bn2 = nn.BatchNorm2d(planes)
  10. self.conv3 = nn.Conv2d(planes, self.expansion*planes, kernel_size=1, bias=False)
  11. self.bn3 = nn.BatchNorm2d(self.expansion*planes)
  12. self.shortcut = nn.Sequential()
  13. if stride != 1 or in_planes != self.expansion*planes:
  14. self.shortcut = nn.Sequential(
  15. nn.Conv2d(in_planes, self.expansion*planes, kernel_size=1, stride=stride, bias=False),
  16. nn.BatchNorm2d(self.expansion*planes)
  17. )
  18. def forward(self, x):
  19. out = F.relu(self.bn1(self.conv1(x)))
  20. out = F.relu(self.bn2(self.conv2(out)))
  21. out = self.bn3(self.conv3(out))
  22. out += self.shortcut(x)
  23. out = F.relu(out)
  24. return out
  25. class FPN(nn.Module):
  26. def __init__(self, block, num_blocks):
  27. super(FPN, self).__init__()
  28. self.in_planes = 64
  29. self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3, bias=False)
  30. self.bn1 = nn.BatchNorm2d(64)
  31. # Bottom-up layers, backbone of the network
  32. self.layer1 = self._make_layer(block, 64, num_blocks[0], stride=1)
  33. self.layer2 = self._make_layer(block, 128, num_blocks[1], stride=2)
  34. self.layer3 = self._make_layer(block, 256, num_blocks[2], stride=2)
  35. self.layer4 = self._make_layer(block, 512, num_blocks[3], stride=2)
  36. # Top layer
  37. # 我们需要在C5后面接一个1x1, 256 conv,得到金字塔最顶端的feature
  38. self.toplayer = nn.Conv2d(2048, 256, kernel_size=1, stride=1, padding=0) # Reduce channels
  39. # Smooth layers
  40. # 这个是上面引文中提到的抗aliasing的3x3卷积
  41. self.smooth1 = nn.Conv2d(256, 256, kernel_size=3, stride=1, padding=1)
  42. self.smooth2 = nn.Conv2d(256, 256, kernel_size=3, stride=1, padding=1)
  43. self.smooth3 = nn.Conv2d(256, 256, kernel_size=3, stride=1, padding=1)
  44. # Lateral layers
  45. # 为了匹配channel dimension引入的1x1卷积
  46. # 注意这些backbone之外的extra conv,输出都是256 channel
  47. self.latlayer1 = nn.Conv2d(1024, 256, kernel_size=1, stride=1, padding=0)
  48. self.latlayer2 = nn.Conv2d( 512, 256, kernel_size=1, stride=1, padding=0)
  49. self.latlayer3 = nn.Conv2d( 256, 256, kernel_size=1, stride=1, padding=0)
  50. def _make_layer(self, block, planes, num_blocks, stride):
  51. strides = [stride] + [1]*(num_blocks-1)
  52. layers = []
  53. for stride in strides:
  54. layers.append(block(self.in_planes, planes, stride))
  55. self.in_planes = planes * block.expansion
  56. return nn.Sequential(*layers)
  57. ## FPN的lateral connection部分: upsample以后,element-wise相加
  58. def _upsample_add(self, x, y):
  59. '''Upsample and add two feature maps.
  60. Args:
  61. x: (Variable) top feature map to be upsampled.
  62. y: (Variable) lateral feature map.
  63. Returns:
  64. (Variable) added feature map.
  65. Note in PyTorch, when input size is odd, the upsampled feature map
  66. with `F.upsample(..., scale_factor=2, mode='nearest')`
  67. maybe not equal to the lateral feature map size.
  68. e.g.
  69. original input size: [N,_,15,15] ->
  70. conv2d feature map size: [N,_,8,8] ->
  71. upsampled feature map size: [N,_,16,16]
  72. So we choose bilinear upsample which supports arbitrary output sizes.
  73. '''
  74. _,_,H,W = y.size()
  75. return F.upsample(x, size=(H,W), mode='bilinear') + y
  76. def forward(self, x):
  77. # Bottom-up
  78. c1 = F.relu(self.bn1(self.conv1(x)))
  79. c1 = F.max_pool2d(c1, kernel_size=3, stride=2, padding=1)
  80. c2 = self.layer1(c1)
  81. c3 = self.layer2(c2)
  82. c4 = self.layer3(c3)
  83. c5 = self.layer4(c4)
  84. # Top-down
  85. # P5: 金字塔最顶上的feature
  86. p5 = self.toplayer(c5)
  87. # P4: 上一层 p5 + 侧边来的 c4
  88. # 其余同理
  89. p4 = self._upsample_add(p5, self.latlayer1(c4))
  90. p3 = self._upsample_add(p4, self.latlayer2(c3))
  91. p2 = self._upsample_add(p3, self.latlayer3(c2))
  92. # Smooth
  93. # 输出做一下smooth
  94. p4 = self.smooth1(p4)
  95. p3 = self.smooth2(p3)
  96. p2 = self.smooth3(p2)
  97. return p2, p3, p4, p5

参考资料

二阶段目标检测网络-FPN 详解的更多相关文章

  1. 语义分割--全卷积网络FCN详解

    语义分割--全卷积网络FCN详解   1.FCN概述 CNN做图像分类甚至做目标检测的效果已经被证明并广泛应用,图像语义分割本质上也可以认为是稠密的目标识别(需要预测每个像素点的类别). 传统的基于C ...

  2. Vmware在NAT模式下网络配置详解

    Vmware在NAT模式下网络配置详解 Linux中的网络配置对于接触Linux不久的小白菜来说,还是小有难度的,可能是不熟悉这种与windows系列迥然不同的命令行操作,也可能是由于对Linux的结 ...

  3. 用netstat查看网络状态详解

    --用netstat查看网络状态详解 -----------------------------2014/06/11 一.Linux服务器上11种网络连接状态:                     ...

  4. Docker:网络模式详解

    Docker作为目前最火的轻量级容器技术,牛逼的功能,如Docker的镜像管理,不足的地方网络方面. Docker自身的4种网络工作方式,和一些自定义网络模式 安装Docker时,它会自动创建三个网络 ...

  5. 【转】Linux 网络工具详解之 ip tuntap 和 tunctl 创建 tap/tun 设备

    原文:https://www.cnblogs.com/bakari/p/10449664.html -------------------------------------------------- ...

  6. Docker网络模式详解

    一.Docker四种工作模式 安装Docker时,它会自动创建三个网络,bridge(创建容器默认连接到此网络). none .hosthost:容器将不会虚拟出自己的网卡,配置自己的IP等,而是使用 ...

  7. ICCV2021 | TOOD:任务对齐的单阶段目标检测

    ​前言  单阶段目标检测通常通过优化目标分类和定位两个子任务来实现,使用具有两个平行分支的头部,这可能会导致两个任务之间的预测出现一定程度的空间错位.本文提出了一种任务对齐的一阶段目标检测(TOOD) ...

  8. JavaScript正则表达式详解(二)JavaScript中正则表达式函数详解

    二.JavaScript中正则表达式函数详解(exec, test, match, replace, search, split) 1.使用正则表达式的方法去匹配查找字符串 1.1. exec方法详解 ...

  9. Linux网络配置文件详解

    --Linux网络配置文件详解----------------------2013/10/03 目前在企业级服务器的Linux系统中,RHEL占有绝对的优势,不管是曾经在互联网公司还是在目前测试Vir ...

  10. Faster RCNN原理分析(二):Region Proposal Networks详解

    Faster RCNN原理分析(二):Region Proposal Networks详解 http://lib.csdn.net/article/deeplearning/61641 0814: A ...

随机推荐

  1. 利用curl命令访问Kubernetes API server

    kubectl 通过访问 Kubernetes API 来执行命令.我们也可以通过对应的TLS key, 使用curl 或是 golang client做同样的事. API 请求必须使用 JSON 格 ...

  2. 3_肯德基餐厅信息查询_动态加载_post请求

    肯德基餐厅信息查询网址:http://www.kfc.com.cn/kfccda/storelist/index.aspx import requests url = 'http://www.kfc. ...

  3. 标题,ico动态化

    //获取ico元素 var link = document.querySelector("link[rel*='icon']"); link.href = "image/ ...

  4. Django 之ModelAdmin对象

    一.后台管理系统配置 1 在该表对应的models类里面添加一个Meta类 class Meta: verbose_name_plural = '用户表' #指定该表在admin后台的名字为:用户表  ...

  5. SpringBoot(一) - SpringBoot 初识

    1.创建SpringBoot项目 1.1 使用Spring Initializr 的 Web页面创建项目 创建网址:https://start.spring.io/ 1.2 使用IDEA创建 省略: ...

  6. Spring Boot 配置 jar 包外面的 Properties 配置文件

    一.概述 Properties 文件是我们可以用来存储项目特定信息的常用方法.理想情况下,我们应该将其保留在 jar 包之外,以便能够根据需要对配置进行更改. 在这个教程中,我们将研究在 Spring ...

  7. 堆内存动态分配情况和jvm调优方向

    由上图可以看出: 堆中分为新生代(占堆1/3内存)和老年代(占堆2/3内存), 新生代又分为Eden区(占新生代内存的8/10)和survivor区(占新生代内存的2/10), survivor区又分 ...

  8. java.lang.Object类与equals()及toString()的使用

    1.Object类是所有Java类的根父类 2.如果在类的声明中未使用extends关键字指明其父类,则默认父类为java.lang.Object类 3.Object类中的功能(属性.方法)就具有通用 ...

  9. numba jit加速python程序

    numba numba加速循环.numpy的一些运算,大概是将python和numpy的一些代码转化为机器代码,速度飞快! 加速耗时很长的循环时: from numba import jit # 在函 ...

  10. Linux基础_5_文件管理

    创建 touch 文件名 #创建文件 stat 文件名 #查看文件属性 touch -am 文件名 #更改文件的访问时间及修改时间 删除 rm -rf s/* #递归强制删除s目录下的所有内容(包括文 ...