深入浅出之FPN (Feature Pyramid Networks for Object Detection)网络
浩瀚之水_csdn 2024-10-02 13:37:01 阅读 77
FPN(Feature Pyramid Network),即特征金字塔网络,是一种用于解决目标检测和语义分割中多尺度问题的深度学习网络结构。以下是对FPN网络的详细介绍:
一、概述
FPN网络是在2017年的CVPR会议上提出的,主要目的是通过特征融合的方式,在不显著增加计算量的情况下,提升多尺度目标的检测性能,尤其是对小目标的检测能力。它通过构建多尺度特征金字塔,将高层特征图的语义信息与低层特征图的空间信息进行融合,生成具有丰富多尺度信息的特征表示。
二、FPN的提出背景、时间及作者
提出背景:
为了增强语义性,传统的物体检测模型通常只在深度卷积网络的最后一个特征图上进行后续操作,而这一层对应的下采样率(图像缩小的倍数)通常又比较大,如16、32,造成小物体在特征图上的有效信息较少,小物体的检测性能会急剧下降,这个问题也被称为多尺度问题。 解决多尺度问题的关键在于如何提取多尺度的特征。传统的方法有图像金字塔(Image Pyramid),主要思路是将输入图片做成多个尺度,不同尺度的图像生成不同尺度的特征,这种方法简单而有效,大量使用在了COCO等竞赛上,但缺点是非常耗时,计算量也很大。 从前面几大主干网络的内容可以知道,卷积神经网络不同层的大小与语义信息不同,本身就类似一个金字塔结构。
如上图,金字塔底部可以较为浅层特征图,金字塔顶部可以较为深层特征图!
浅层的特征图感受野小,比较适合检测小目标(要检测大目标,则其只“看”到了大目标的一部分,有效信息不够);深层的特征图感受野大,适合检测大目标(要检测小目标,则其”看“到了太多的背景噪音,冗余噪音太多),因此FPN应运而生!!!
2017年的FPN(Feature Pyramid Network)方法融合了不同层的特征,较好地改善了多尺度检测问题。
提出时间及作者:
FPN由何凯明等人在2017年提出,这一网络结构的提出为解决物体检测中的多尺度问题提供了新的思路。
三、主要贡献
FPN的主要贡献在于:
提出了一种新的特征金字塔结构:FPN通过构建自底向上和自顶向下的特征融合路径,将不同尺度的特征图进行融合,从而生成具有丰富多尺度信息的特征金字塔。显著提升了小物体检测的性能:在不显著增加计算量的情况下,FPN通过特征融合的方式,大幅度提高了模型对小尺度物体的检测能力。广泛应用于多种计算机视觉任务:FPN不仅适用于目标检测任务,还被广泛应用于语义分割、实例分割等任务中,并取得了显著的效果。
四、优缺点
优点:
多尺度特征融合:FPN通过融合不同尺度的特征图,使得模型能够同时处理不同大小的目标。计算效率高:在基本不增加计算量的情况下,FPN显著提升了模型的检测性能。灵活性强:FPN可以轻松地与其他网络结构相结合使用,提高整体性能。
缺点(相对而言):
对硬件资源有一定要求:虽然FPN的计算量没有显著增加,但处理多尺度特征图仍然需要一定的硬件资源支持。网络结构相对复杂:FPN引入了自顶向下的特征融合路径和横向连接,使得网络结构相对复杂,可能增加模型训练的难度。
五、网络结构
FPN的网络结构主要由自底向上的特征提取路径和自顶向下的特征融合路径组成。自底向上的路径是基础网络(如ResNet)的正向传播过程,用于提取不同层次的特征图。自顶向下的路径则通过上采样和横向连接的方式,将高层特征图的语义信息与低层特征图的空间信息进行融合。
FPN的总体架构如上图所示,主要包含自下而上网络、自上而下网络、横向连接与卷积融合4个部分。
FPN网络通过以下步骤实现多尺度特征的提取和融合:
自底向上的特征提取:
使用一个基础网络(如ResNet、VGG等)作为骨干网络(backbone),从输入图像中逐层提取特征。这些特征图(feature maps)在卷积神经网络中逐层传递,每一层都包含了不同尺度和不同层次的特征信息。在这个过程中,特征图的分辨率逐渐降低,而语义信息逐渐丰富。每一层特征图都代表了输入图像在不同尺度上的抽象表示。自顶向下的特征融合:
为了解决高层特征图分辨率低、细节信息少的问题,FPN引入了自顶向下的特征融合路径。高层特征图首先通过上采样(如双线性插值、反卷积等)操作,使其分辨率与低一层特征图相匹配。然后,将上采样后的高层特征图与对应的低层特征图进行融合。这种融合通常是通过逐元素相加(element-wise addition)或特征拼接(concatenation)的方式实现的。在融合过程中,为了保持特征图的通道数一致,可能需要对低层特征图进行1x1卷积操作来减少其通道数。横向连接:
自顶向下的特征融合路径与自底向上的特征提取路径之间通过横向连接(lateral connections)相连。这些横向连接将低层特征图的高分辨率信息与高层特征图的丰富语义信息相结合,形成新的特征图。横向连接不仅有助于传递低层特征图的细节信息,还可以增强高层特征图的定位能力。特征金字塔的构建:
通过上述步骤,FPN构建了一个特征金字塔(feature pyramid)。这个金字塔包含了从底层到顶层的多个尺度的特征图,每个特征图都融合了不同层次的特征信息。特征金字塔的每一层都对应一个特定的尺度范围,使得模型能够同时处理不同大小的目标。预测与输出:
在特征金字塔的每一层上,都可以独立地进行目标检测或语义分割等任务的预测。对于目标检测任务,通常会在每个特征图上设置不同尺度和长宽比的锚点(anchors),并预测锚点对应的边界框(bounding boxes)和类别标签。对于语义分割任务,则可以直接在特征图上应用分割器来预测每个像素的类别标签。
六、pytorch实现
<code>'''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 F
from torch.autograd import Variable
class Bottleneck(nn.Module):
expansion = 4
def __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 out
class FPN(nn.Module):
def __init__(self, block, num_blocks):
super(FPN, self).__init__()
self.in_planes = 64
self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3, bias=False)
self.bn1 = nn.BatchNorm2d(64)
# Bottom-up layers
self.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 layer
self.toplayer = nn.Conv2d(2048, 256, kernel_size=1, stride=1, padding=0) # Reduce channels
# Smooth layers
self.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 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)
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.expansion
return 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 map
with `F.upsample(..., scale_factor=2, mode='nearest')`code>
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') + ycode>
def forward(self, x):
# Bottom-up
c1 = F.relu(self.bn1(self.conv1(x)))
c1 = F.max_pool2d(c1, kernel_size=3, stride=2, padding=1)
print(f'c1:{c1.shape}')
c2 = self.layer1(c1)
print(f'c2:{c2.shape}')
c3 = self.layer2(c2)
print(f'c3:{c3.shape}')
c4 = self.layer3(c3)
print(f'c4:{c4.shape}')
c5 = self.layer4(c4)
print(f'c5:{c5.shape}')
# Top-down
p5 = self.toplayer(c5)
print(f'p5:{p5.shape}')
p4 = self._upsample_add(p5, self.latlayer1(c4))
print(f'latlayer1(c4):{self.latlayer1(c4).shape}, p4:{p4.shape}')
p3 = self._upsample_add(p4, self.latlayer2(c3))
print(f'latlayer1(c3):{self.latlayer2(c3).shape}, p3:{p3.shape}')
p2 = self._upsample_add(p3, self.latlayer3(c2))
print(f'latlayer1(c2):{self.latlayer3(c2).shape}, p2:{p2.shape}')
# Smooth
p4 = self.smooth1(p4)
p3 = self.smooth2(p3)
p2 = self.smooth3(p2)
return p2, p3, p4, p5
def FPN18():
# return FPN(Bottleneck, [2,4,23,3])
return FPN(Bottleneck, [2,2,2,2])
def test():
net = FPN18()
fms = net(Variable(torch.randn(1,3,600,900)))
for fm in fms:
print(fm.size())
# p=[fms[0], fms[1], fms[2] , fms[3]]
# print(p)
test()
七、FPN的演化版本
FPN(Feature Pyramid Networks,特征金字塔网络)自提出以来,在目标检测、语义分割等领域展现出了强大的性能。随着计算机视觉领域的发展,FPN也经历了一系列的后续演化版本,这些版本在保持FPN基本思想的基础上,进行了不同的改进和优化。以下是一些FPN的后续演化版本:
PANet(Path Aggregation Network):
PANet通过引入一个额外的自下而上的路径增强,将低层次的特征信息传递到高层次的特征图中,从而增强特征金字塔的表示能力。这种结构有助于捕捉更多层次的上下文信息,提高目标检测的准确性。NAS-FPN(Neural Architecture Search Feature Pyramid Network):
NAS-FPN利用神经架构搜索(NAS)技术来自动设计特征金字塔网络的结构。通过在大规模搜索空间中搜索最优的跨尺度连接模式,NAS-FPN能够找到比传统手工设计更高效的特征金字塔结构。BiFPN(Bidirectional Feature Pyramid Network):
BiFPN是EfficientDet中的核心组件之一,它进一步改进了FPN的跨尺度连接策略。BiFPN通过引入双向连接和加权特征融合,使得特征金字塔中的每一层都能接收到来自其他所有层的信息,从而提高了特征的表示能力。HRNet(High-Resolution Net):
虽然HRNet本身不是FPN的直接演化版本,但它通过在整个网络中保持高分辨率的表示,实现了类似于FPN的多尺度特征融合。HRNet通过并行连接不同分辨率的子网络,并在各个子网络之间进行特征交换,从而实现了高效的特征融合和多尺度表示。其他变体:
在此基础上,还有许多研究者提出了针对特定任务或数据集优化的FPN变体,如轻量级的FPN结构、针对小目标检测的FPN改进等。这些变体通常通过调整FPN的结构参数、引入注意力机制、结合其他网络组件等方式来提高性能。
需要注意的是,随着计算机视觉技术的不断发展,新的FPN演化版本也在不断涌现。因此,在实际应用中,需要根据具体任务和数据集的特点选择合适的FPN版本或变体。同时,也可以借鉴最新的研究成果,对FPN进行进一步的改进和优化。
八、应用场景
FPN的应用场景非常广泛,主要包括:
目标检测:FPN通过构建特征金字塔,提高了模型对不同尺度目标的检测能力,在COCO、PASCAL VOC等目标检测数据集上取得了优异的表现。语义分割:在语义分割任务中,FPN通过融合多尺度特征图,使得模型能够同时利用深层特征的语义信息和浅层特征的空间信息,提升分割效果。实例分割:实例分割是目标检测和语义分割的结合体,FPN同样可以应用于实例分割任务中,提高模型的分割精度和鲁棒性。
总之,FPN作为一种创新的特征融合网络结构,在计算机视觉领域具有广泛的应用前景和重要的研究价值。
参考:
FPN网络结构及Pytorch实现
声明
本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。