FPN很老的东西了,整体来讲并不是涨点神器,而且非常的臃肿,不建议使用,但是可以拿来玩玩。网上大部分是基于2DCNN的FPN,因为开发使用5D的输出尺寸,所以更改为3DCNN,不过原理都一样。通过ResNet,自上而下的卷积和自下而上的卷积提取空间信息,再经过横向连接进行特征融合,总体来讲没有现在火热的注意力机制效果好。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
#特征金字塔:FPN
import numpy as np
import torch.nn as nn
import torch.nn.functional as F
import math
import torch
#resNet的基本Bottleneck类
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.Conv3d(in_planes,planes,1, bias=False),
nn.BatchNorm3d(planes),
nn.ReLU(inplace= True),
nn.Conv3d(planes, planes , (3,3,1), stride ,(1,1,0) ,bias=False),
nn.BatchNorm3d(planes),
nn.ReLU(inplace=True),
nn.Conv3d(planes, self.expansion * planes, 1 , bias=False),
nn.BatchNorm3d(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

#FPN的类,初始化需要一个list,代表resNet每一个阶段的Bottleneck的数量
class FPN(nn.Module):
def __init__(self,layers):
super(FPN,self).__init__()
self.inplanes = 16

#处理输入的C1模块
self.conv1 = nn.Conv3d(8, 16, 1, 1, bias = False)
self.bn1 = nn.BatchNorm3d(16)
self.relu = nn.ReLU(inplace = True)
self.maxpool = nn.MaxPool3d(1,1,0)

#搭建自上而下的C2、C3、C4、C5
self.layer1 = self._make_layer(16,layers[0])
self.layer2 = self._make_layer(32,layers[1],(2,2,1))
self.layer3 = self._make_layer(64,layers[2],(2,2,1))
self.layer4 = self._make_layer(128,layers[3],(2,2,1))

#对C5减少通道数得到P5
self.toplayer = nn.Conv3d(512,64,1,1,0)

#3x3卷积融合特征
self.smooth1 = nn.Conv3d(64, 64, (3,3,1), 1, (1,1,0))
self.smooth2 = nn.Conv3d(64, 64, (3,3,1), 1, (1,1,0))
self.smooth3 = nn.Conv3d(64, 64, (3,3,1), 1, (1,1,0))

#横向连接,保证通道数相同
self.latlayer1 = nn.Conv3d(256,64,1,1,0)
self.latlayer2 = nn.Conv3d(128, 64, 1, 1, 0)
self.latlayer3 = nn.Conv3d(64, 64, 1, 1, 0)


#构建C2到C5需要注意stride值为1和2的情况
def _make_layer(self,planes, blocks, stride = 1):
downsample = None
if stride != 1 or self.inplanes != Bottleneck.expansion * planes :
downsample = nn.Sequential(
nn.Conv3d(self.inplanes,Bottleneck.expansion * planes, 1 , stride , bias=False),
nn.BatchNorm3d(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,Z = y.shape
return F.interpolate(x,size=(H,W,Z),mode='trilinear',align_corners=False) + 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

net_fpn = FPN([3,4,6,3]).cuda()
input = torch.randn(16,8,16,16,52).cuda()
output = net_fpn(input)
print(output[0].shape)