个人论文阅读笔记,可能存在许多瑕疵和错误,欢迎评论指正,谢谢~~
1.总括
NBnet是一种新的图像去噪框架,通过图像自适应投影降低噪声。
具体来说,通过在特征空间中学习一组重构基来训练一个能够分离信号和噪声的网络。然后,通过选择相应的信号子空间基并将输入投影到该空间,可以实现图像去噪。投影可以自然地保持输入信号的局部结构,特别是对于光线不足或纹理较弱的区域。为此,提出了SSA,这是一个设计用来明确学习基生成和子空间投影的非局部注意模块。进一步将SSA与NBNet结合起来,NBNet是一种用于端到端图像表示的UNet结构化网络。
通过投影来利用非局部图像信息。图像投影的基本概念如图2所示,其中从输入图像生成一组图像基向量,然后我们在这些基向量所跨越的子空间内重建图像。
子空间投影去噪:我们的NBNet学习为信号子空间生成一组基,通过将输入投影到这个空间,可以在重建后增强信号,以便于与噪声分离。
由于自然图像通常位于低秩信号子空间中,通过适当地学习和生成基向量,重建图像可以保留大部分原始信息,抑制与生成的基集无关的噪声。基于这个想法,我们提出了NBNet,如图3所示。NBNet的总体架构是一个常用的UNet,除了关键成分子空间注意(SSA)模块,它以端到端的方式学习子空间基础和图像投影。
NBNet的总体架构和关键构建块的结构。NBNet基于深度为5的UNet架构,SSA模块用于投影编码器的跳过连接功能。
2.基于神经网络的子空间投影
如图2所示,投影包含两个主要步骤:
(1)基生成:从图像特征图生成子空间基向量;
(2)投影:将特征图变换到信号子空间。
作为单个图像的两个特征图。它们是CNN的中间激活物,可以位于不同的层,但大小相同。首先根据X1和X2估计K基向量
,每个
是信号子空间的基向量,其中N=HW。然后将X1变换到{v}所跨越的子空间。
2.1基生成
设是由θ参数化的函数,基生成可以写成:
其中X1和X2是图像特征图,V =是一个由基向量组成的矩阵。我们用一个小型卷积网络来实现函数fθ。首先将X1和X2沿通道轴串联起来,作为
。然后将其送入具有K个输出通道的浅层残差卷积块(图3(b)),其输出可以被重塑为HW×K。基数生成块的权重和偏置在训练过程中以端到端方式更新。
2.2投影
理论推导参考:线性代数系列(十)--子空间投影和最小二乘法_Thincor的博客-CSDN博客
给定上述矩阵列为K维信号子空间
的基向量,我们可以通过正交线性投影将图像特征图X1投影到V上。
让P:是信号子空间的正交投影矩阵,P可以从V计算得出,公式如下
其中,归一化项是必需的,因为基生成过程不能确保基向量彼此正交。
最后,图像特征映射X1可以在信号子空间中重建为Y,由下式给出
投影中的操作是纯线性矩阵操作,具有一些适当的重塑,这是完全可微的,可以很容易地在现代神经网络框架中实现。
结合基生成和子空间投影,我们构造了所提出的SSA模块的结构。
源代码中基生成和子空间投影的相关代码
class Subspace(nn.Module):
def __init__(self, in_size, out_size):
super(Subspace, self).__init__()
self.blocks = []
self.blocks.append(UNetConvBlock(in_size, out_size, False, 0.2))
self.shortcut = nn.Conv2d(in_size, out_size, kernel_size=1, bias=True)
def forward(self, x):
sc = self.shortcut(x)
for i in range(len(self.blocks)):
x = self.blocks[i](x)
return x + sc
class UNetUpBlock(nn.Module):
def __init__(self, in_size, out_size, relu_slope, subnet_repeat_num, subspace_dim=16):
super(UNetUpBlock, self).__init__()
self.up = nn.ConvTranspose2d(in_size, out_size, kernel_size=2, stride=2, bias=True)
self.conv_block = UNetConvBlock(in_size, out_size, False, relu_slope)
self.num_subspace = subspace_dim
print(self.num_subspace, subnet_repeat_num)
self.subnet = Subspace(in_size, self.num_subspace)
self.skip_m = skip_blocks(out_size, out_size, subnet_repeat_num)
def forward(self, x, bridge):
up = self.up(x)
bridge = self.skip_m(bridge)
out = F.concat([up, bridge], 1)
if self.subnet:
b_, c_, h_, w_ = bridge.shape
sub = self.subnet(out)
V_t = sub.reshape(b_, self.num_subspace, h_*w_)
V_t = V_t / (1e-6 + F.abs(V_t).sum(axis=2, keepdims=True))
mat = F.matmul(V_t, V) # 矩阵相乘
mat_inv = F.matinv(mat) # 矩阵求逆
project_mat = F.matmul(mat_inv, V_t)
bridge_ = bridge.reshape(b_, c_, h_*w_)
project_feature = F.matmul(project_mat, bridge_.transpose(0, 2, 1))
bridge = F.matmul(V, project_feature).transpose(0, 2, 1).reshape(b_, c_, h_, w_)
out = F.concat([up, bridge], 1)
out = self.conv_block(out)
return out
3.NBNet架构和损失函数
NBNet的架构如图3(a)所示。总体结构基于典型的UNet体系结构。NBNet有4个编码器级和4个相应的解码器级,其中特征映射在每个编码器级的末尾通过4×4步长为2的卷积下采样到1 /2×尺度,在每个解码器级之前通过2×2反卷积上采样到2×尺度。跳跃连接将大规模低级特征映射从每个编码器阶段传递到相应的解码器阶段。编码器、解码器和跳跃连接中的基本卷积构建块遵循图3(b)所示的相同剩余卷积结构。我们使用LeakyReLU作为每个卷积层的激活函数。
建议的SSA模块放置在每个跳线连接中。由于来自低级别的特征地图包含更详细的原始图像信息,将低级别特征地图作为X1,将高级别特征作为X2,并将它们馈送到SSA模块。换言之,来自跳转连接的低级特征映射被投影到由上采样的高级特征引导的信号子空间中。然后,投影特征与原始高级特征融合,然后输出到下一个解码器阶段。
与传统的类UNet结构相比,它在每个解码器阶段直接融合低层和高层特征图,NBNet的主要区别在于低层特征在融合前由SSA模块投影。
最后,最后一个解码器的输出通过一个线性3×3卷积层作为噪声输入的全局残差,输出去噪结果。
使用干净和有噪图像对网络进行训练,我们使用干净图像和去噪结果之间的简单“L1距离”作为损失函数,写为:
其中x、y和G(·)分别表示干净图像、有噪图像和NBNet。
完结~~