摘要
目标检测中的金字塔集合通常指的是特征金字塔网络(Feature Pyramid Networks,FPN),这是一种在深度学习目标检测模型中常用的结构,用于构建多尺度的特征表示,以检测不同尺寸的目标。特征金字塔网络通过结合深层(高语义但低分辨率)和浅层(低语义但高分辨率)的特征图,生成一个多尺度的特征金字塔,每个尺度的特征图都适合检测特定尺寸的目标。
特征金字塔网络的主要组成部分包括:
自底向上路径(Bottom-Up Pathway):这是标准的卷积神经网络路径,通过连续的卷积和池化操作,逐渐降低特征图的空间分辨率,同时增加特征图的深度,从而提取图像的高层语义信息。
自顶向下路径(Top-Down Pathway):通过将深层的高语义特征图进行上采样(如使用反卷积或双线性插值),增加其空间分辨率,使其更适合检测小尺寸目标。
横向连接(Lateral Connections):在自顶向下路径中,将上采样的深层特征图与相应尺度的浅层特征图通过1x1卷积融合,以增强特征图的语义信息。
特征融合:通过上述结构,特征金字塔网络能够在不同尺度的特征图上同时保留丰富的语义信息和细节信息,从而有效地检测不同尺寸的目标。
特征金字塔网络在多种目标检测模型中得到应用,如Faster R-CNN、Mask R-CNN、RetinaNet等,显著提高了模型对多尺度目标的检测性能。
此外,还有一些变种和改进的结构,例如:
- BiFPN:构建了带有自学习权重的特征融合模块,然后重复使用该模块。
- PANet:在FPN的基础上添加了自底向上的路径聚合模块,进一步融合信息。
- NAS-FPN:采用网络结构搜索的方法,得出新的特征融合模块。
这些结构的共同目标是更有效地融合不同尺度的特征,以提高目标检测的准确性和效率。
FPN
特征金字塔网络(Feature Pyramid Networks,FPN)是一种在计算机视觉领域中用于目标检测任务的网络结构,它由Tsung-Yi Lin等人在2017年的CVPR会议上提出。FPN的主要贡献是有效地结合了深层和浅层特征,以处理不同尺寸的目标检测问题。以下是FPN的关键特点和组件:
自底向上路径(Bottom-Up Pathway):
- 这是标准的卷积神经网络层,通过连续的卷积和池化操作逐渐减少特征图的空间维度,同时增加通道数,从而提取图像的高层语义信息。
自顶向下路径(Top-Down Pathway):
- FPN通过自顶向下的路径来增强深层特征图的分辨率。这通常通过将高层的特征图进行上采样(如使用最近邻或双线性插值)来实现,以便与低层的特征图具有相似的空间分辨率。
横向连接(Lateral Connections):
- 为了将低层特征图的高分辨率细节信息和高层特征图的强语义信息结合起来,FPN引入了横向连接。这些连接通常是1x1的卷积操作,用于匹配特征图的通道数,并将自顶向下路径的特征图与自底向上路径的特征图融合。
特征融合:
- 通过横向连接融合的特征图,FPN生成了一个新的特征金字塔,每个级别的特征图都包含了丰富的语义信息和空间信息,适合检测不同尺寸的目标。
多尺度特征预测:
- FPN的输出是一系列多尺度的特征图,这些特征图可以直接用于目标检测任务,如边框回归和分类。
应用广泛:
- FPN不仅提高了目标检测的准确性,还提高了检测的效率。它被广泛应用于各种目标检测模型中,如Faster R-CNN、Mask R-CNN、RetinaNet等。
改进和变体:
- 由于FPN的成功,研究者们提出了许多基于FPN的改进和变体,如BiFPN、PANet、NAS-FPN等,这些变体进一步优化了特征融合和多尺度特征的表示。
FPN的提出,解决了深度卷积神经网络在目标检测中对小目标检测能力不足的问题,并且提高了对大目标的检测精度。通过构建特征金字塔,FPN使得网络能够同时捕捉到图像的全局语义信息和局部细节信息,从而在多尺度目标检测任务中取得了显著的性能提升。
# Modification 2020 RangiLyu
# Copyright 2018-2019 Open-MMLab.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import torch.nn as nn
import torch.nn.functional as F
from ..module.conv import ConvModule
from ..module.init_weights import xavier_init
class FPN(nn.Module):
def __init__(self,
in_channels,
out_channels,
num_outs,
start_level=0,
end_level=-1,
conv_cfg=None,
norm_cfg=None,
activation=None
):
super(FPN, self).__init__()
assert isinstance(in_channels, list)
self.in_channels = in_channels
self.out_channels = out_channels
self.num_ins = len(in_channels)
self.num_outs = num_outs
self.fp16_enabled = False
if end_level == -1:
self.backbone_end_level = self.num_ins
assert num_outs >= self.num_ins - start_level
else:
# if end_level < inputs, no extra level is allowed
self.backbone_end_level = end_level
assert end_level <= len(in_channels)
assert num_outs == end_level - start_level
self.start_level = start_level
self.end_level = end_level
self.lateral_convs = nn.ModuleList()
for i in range(self.start_level, self.backbone_end_level):
l_conv = ConvModule(
in_channels[i],
out_channels,
1,
conv_cfg=conv_cfg,
norm_cfg=norm_cfg,
activation=activation,
inplace=False)
self.lateral_convs.append(l_conv)
self.init_weights()
# default init_weights for conv(msra) and norm in ConvModule
def init_weights(self):
for m in self.modules():
if isinstance(m, nn.Conv2d):
xavier_init(m, distribution='uniform')
def forward(self, inputs):
assert len(inputs) == len(self.in_channels)
# build laterals
laterals = [
lateral_conv(inputs[i + self.start_level])
for i, lateral_conv in enumerate(self.lateral_convs)
]
# build top-down path
used_backbone_levels = len(laterals)
for i in range(used_backbone_levels - 1, 0, -1):
prev_shape = laterals[i - 1].shape[2:]
laterals[i - 1] += F.interpolate(
laterals[i], scale_factor=2, mode='bilinear')
# build outputs
outs = [
# self.fpn_convs[i](laterals[i]) for i in range(used_backbone_levels)
laterals[i] for i in range(used_backbone_levels)
]
return tuple(outs)
# if __name__ == '__main__':
PAN
PANet(Path Aggregation Network)是一种用于实例分割的深度学习网络,由Shu Liu等人在2018年的CVPR会议上提出。它在COCO 2017挑战赛中取得了实例分割任务第一名和目标检测任务第二名的优异成绩。PANet的核心贡献在于它通过增强信息流和整合不同层级的特征来提高预测掩码的质量。
PANet的主要特点包括:
自底向上的路径增强(Bottom-up Path Augmentation):这一特性通过在低层使用精确的定位信号来增强整个特征层次结构,从而缩短了从底层到顶层的信息路径。
自适应特征池化(Adaptive Feature Pooling):它允许每个提议(proposal)获取所有特征层级的信息,避免了仅依赖于被人为指定的某个特征层。
全连接融合(Fully-connected Fusion):在掩码预测分支中增加全连接层,与原有的FCN路径互补,以获取不同视角的预测结果进行融合,从而提高分割质量。
PANet在设计上有几个关键组件:
- 特征金字塔网络(FPN):作为基础网络,为路径聚合模块提供了多尺度的特征图。
- 路径聚合模块:包括自上而下和自下而上的路径聚合,通过横向连接和逐点加法操作整合不同尺度的信息。
PANet的优势在于:
- 提高了对小目标和遮挡目标的处理能力。
- 提升了模型的泛化性能。
- 降低了计算复杂度,使得模型在实际应用中更加高效。
PANet在目标检测与实例分割任务中取得了显著的成果,证明了其在处理小目标和遮挡目标时的强大能力。此外,PANet采用了多尺度特征融合的方式,使得模型能够适应不同尺度的目标,从而提高了模型的泛化性能。
import torch.nn as nn
import torch.nn.functional as F
from ..module.conv import ConvModule
from .fpn import FPN
class PAN(FPN):
"""Path Aggregation Network for Instance Segmentation.
This is an implementation of the `PAN in Path Aggregation Network
<https://arxiv.org/abs/1803.01534>`_.
Args:
in_channels (List[int]): Number of input channels per scale.
out_channels (int): Number of output channels (used at each scale)
num_outs (int): Number of output scales.
start_level (int): Index of the start input backbone level used to
build the feature pyramid. Default: 0.
end_level (int): Index of the end input backbone level (exclusive) to
build the feature pyramid. Default: -1, which means the last level.
add_extra_convs (bool): Whether to add conv layers on top of the
original feature maps. Default: False.
extra_convs_on_inputs (bool): Whether to apply extra conv on
the original feature from the backbone. Default: False.
relu_before_extra_convs (bool): Whether to apply relu before the extra
conv. Default: False.
no_norm_on_lateral (bool): Whether to apply norm on lateral.
Default: False.
conv_cfg (dict): Config dict for convolution layer. Default: None.
norm_cfg (dict): Config dict for normalization layer. Default: None.
act_cfg (str): Config dict for activation layer in ConvModule.
Default: None.
"""
def __init__(self,
in_channels,
out_channels,
num_outs, # 输出规模的数量
start_level=0, # 用于起始输入骨干级别的索引构建特征金字塔
end_level=-1,
conv_cfg=None,
norm_cfg=None,
activation=None):
super(PAN,
self).__init__(in_channels, out_channels, num_outs, start_level,
end_level, conv_cfg, norm_cfg, activation)
self.init_weights()
def forward(self, inputs):
"""Forward function."""
assert len(inputs) == len(self.in_channels)
# build laterals
laterals = [
lateral_conv(inputs[i + self.start_level])
for i, lateral_conv in enumerate(self.lateral_convs)
]
# build top-down path
used_backbone_levels = len(laterals) # 3
for i in range(used_backbone_levels - 1, 0, -1):
laterals[i - 1] += F.interpolate(
laterals[i], scale_factor=2, mode='bilinear')
# build outputs
# part 1: from original levels
inter_outs = [
laterals[i] for i in range(used_backbone_levels)
]
# part 2: add bottom-up path
for i in range(0, used_backbone_levels - 1):
inter_outs[i + 1] += F.interpolate(inter_outs[i], scale_factor=0.5, mode='bilinear')
outs = []
outs.append(inter_outs[0])
outs.extend([
inter_outs[i] for i in range(1, used_backbone_levels)
])
# print(outs)
return tuple(outs)
BiFPN
BiFPN(Bidirectional Feature Pyramid Network)是一种高效的特征融合网络,用于目标检测和图像分割任务。它在传统的特征金字塔网络(FPN)的基础上进行了优化,引入了双向特征融合机制和加权特征融合方法,以提高特征融合的效率和效果。
BiFPN的核心特点包括:
双向特征融合:BiFPN不仅保持了自顶向下的特征融合路径(如传统FPN),还引入了自底向上的路径,使得不同尺度的特征可以在两个方向上进行有效的信息交流和融合。
加权特征融合:在特征融合过程中,BiFPN引入了可学习的权重,以区分不同输入特征的重要性,从而优化特征融合的效果。
结构优化:BiFPN通过移除只有一个输入边的节点、添加同一层级的输入输出节点之间的额外边,并将每个双向路径视为一个特征网络层,重复多次以实现更高级别的特征融合。
多尺度特征融合:BiFPN能够处理不同尺度的特征图,并在融合过程中考虑到这些尺度变化,通过上下采样等操作,有效地处理不同分辨率的特征图。
BiFPN在设计上非常灵活,可以适应不同的网络架构和任务需求。它通过双向特征融合和加权特征融合,克服了传统FPN的局限性,提高了特征融合的效率和效果。这种灵活高效的设计使其在计算机视觉任务中得到广泛应用,为提升模型性能提供了有力支持。
在实际应用中,BiFPN被集成到EfficientDet模型中,作为其特征融合网络部分,显著提升了模型的检测性能。EfficientDet模型结合了EfficientNet骨干网络和BiFPN特征融合网络,通过高效的特征融合机制,在目标检测任务中实现了优异的性能。
import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.autograd import Variable
class DepthwiseConvBlock(nn.Module):
"""
Depthwise seperable convolution.
"""
def __init__(self, in_channels, out_channels, kernel_size=1, stride=1, padding=0, dilation=1, freeze_bn=False):
super(DepthwiseConvBlock, self).__init__()
self.depthwise = nn.Conv2d(in_channels, in_channels, kernel_size, stride,
padding, dilation, groups=in_channels, bias=False)
self.pointwise = nn.Conv2d(in_channels, out_channels, kernel_size=1,
stride=1, padding=0, dilation=1, groups=1, bias=False)
self.bn = nn.BatchNorm2d(out_channels, momentum=0.9997, eps=4e-5)
self.act = nn.ReLU()
def forward(self, inputs):
x = self.depthwise(inputs)
x = self.pointwise(x)
x = self.bn(x)
return self.act(x)
class ConvBlock(nn.Module):
"""
Convolution block with Batch Normalization and ReLU activation.
"""
def __init__(self, in_channels, out_channels, kernel_size=1, stride=1, padding=0, dilation=1, freeze_bn=False):
super(ConvBlock, self).__init__()
self.conv = nn.Conv2d(in_channels, out_channels, kernel_size, stride=stride, padding=padding)
self.bn = nn.BatchNorm2d(out_channels, momentum=0.9997, eps=4e-5)
self.act = nn.ReLU()
def forward(self, inputs):
x = self.conv(inputs)
x = self.bn(x)
return self.act(x)
class BiFPNBlock(nn.Module):
"""
Bi-directional Feature Pyramid Network
"""
def __init__(self, feature_size=64, epsilon=0.0001):
super(BiFPNBlock, self).__init__()
self.epsilon = epsilon
self.p3_td = DepthwiseConvBlock(feature_size, feature_size)
self.p4_td = DepthwiseConvBlock(feature_size, feature_size)
self.p5_td = DepthwiseConvBlock(feature_size, feature_size)
self.p6_td = DepthwiseConvBlock(feature_size, feature_size)
self.p4_out = DepthwiseConvBlock(feature_size, feature_size)
self.p5_out = DepthwiseConvBlock(feature_size, feature_size)
self.p6_out = DepthwiseConvBlock(feature_size, feature_size)
self.p7_out = DepthwiseConvBlock(feature_size, feature_size)
# TODO: Init weights
self.w1 = nn.Parameter(torch.Tensor(2, 4))
self.w1_relu = nn.ReLU()
self.w2 = nn.Parameter(torch.Tensor(3, 4))
self.w2_relu = nn.ReLU()
def forward(self, inputs):
p3_x, p4_x, p5_x, p6_x, p7_x = inputs
# Calculate Top-Down Pathway
w1 = self.w1_relu(self.w1)
w1 /= torch.sum(w1, dim=0) + self.epsilon
w2 = self.w2_relu(self.w2)
w2 /= torch.sum(w2, dim=0) + self.epsilon
p7_td = p7_x
p6_td = self.p6_td(w1[0, 0] * p6_x + w1[1, 0] * F.interpolate(p7_x, scale_factor=2))
p5_td = self.p5_td(w1[0, 1] * p5_x + w1[1, 1] * F.interpolate(p6_x, scale_factor=2))
p4_td = self.p4_td(w1[0, 2] * p4_x + w1[1, 2] * F.interpolate(p5_x, scale_factor=2))
p3_td = self.p3_td(w1[0, 3] * p3_x + w1[1, 3] * F.interpolate(p4_x, scale_factor=2))
# Calculate Bottom-Up Pathway
p3_out = p3_td
p4_out = self.p4_out(w2[0, 0] * p4_x + w2[1, 0] * p4_td + w2[2, 0] * nn.Upsample(scale_factor=0.5)(p3_out))
p5_out = self.p5_out(w2[0, 1] * p5_x + w2[1, 1] * p5_td + w2[2, 1] * nn.Upsample(scale_factor=0.5)(p4_out))
p6_out = self.p6_out(w2[0, 2] * p6_x + w2[1, 2] * p6_td + w2[2, 2] * nn.Upsample(scale_factor=0.5)(p5_out))
p7_out = self.p7_out(w2[0, 3] * p7_x + w2[1, 3] * p7_td + w2[2, 3] * nn.Upsample(scale_factor=0.5)(p6_out))
return [p3_out, p4_out, p5_out, p6_out, p7_out]
class BiFPN(nn.Module):
def __init__(self, in_channels, out_channels=64, num_layers=2, epsilon=0.0001):
super(BiFPN, self).__init__()
self.p3 = nn.Conv2d(in_channels[0], out_channels, kernel_size=1, stride=1, padding=0)
self.p4 = nn.Conv2d(in_channels[1], out_channels, kernel_size=1, stride=1, padding=0)
self.p5 = nn.Conv2d(in_channels[2], out_channels, kernel_size=1, stride=1, padding=0)
# p6 is obtained via a 3x3 stride-2 conv on C5
self.p6 = nn.Conv2d(in_channels[2], out_channels, kernel_size=3, stride=2, padding=1)
# p7 is computed by applying ReLU followed by a 3x3 stride-2 conv on p6
self.p7 = ConvBlock(out_channels, out_channels, kernel_size=3, stride=2, padding=1)
bifpns = []
for _ in range(num_layers):
bifpns.append(BiFPNBlock(out_channels))
self.bifpn = nn.Sequential(*bifpns)
def forward(self, inputs):
c3, c4, c5 = inputs
# Calculate the input column of BiFPN
p3_x = self.p3(c3)
p4_x = self.p4(c4)
p5_x = self.p5(c5)
p6_x = self.p6(c5)
p7_x = self.p7(p6_x)
features = [p3_x, p4_x, p5_x, p6_x, p7_x]
return self.bifpn(features)