一、论文
《Pyramid Real Image Denoising Network》
摘要—尽管深卷积神经网络(CNN)表现出了出色的建模特定噪声和降噪能力,但它们在现实世界中的噪点图像上仍然表现不佳。 主要原因是现实世界中的噪声更加复杂多样。 为了解决盲降噪问题,本文提出了一个新的金字塔实像降噪网络(PRIDNet),它分为三个阶段。 首先,噪声估计阶段使用通道注意机制来重新校准输入噪声的通道重要性。 其次,在多尺度降噪阶段,利用金字塔池提取多尺度特征。 第三,特征融合阶段采用核选择运算来自适应融合多尺度特征。在两个真实嘈杂照片的数据集上进行的实验表明,在定量测量和视觉感知质量方面,与最新的去噪器相比,我们的方法可以实现竞争性能。
• 通道注意:通道注意机制用于提取的噪声特征,可自适应地重新校准频道重要性。
• 多尺度特征提取:我们设计了金字塔降噪结构,其中每个分支都关注一个尺度的特征。 得益于此,我们可以同时提取全局信息和保留局部细节,从而为后续的全面去噪做好准备。
• 特征自适应融合:在级联多尺度特征中,每个通道代表一个尺度特征。 我们介绍一个内核选择模块。 通过线性组合融合具有不同卷积核大小的多个分支,从而允许通过大小不同的核来表达不同的特征图。
二、网络结构
A.网络架构
如图2所示,我们的模型包括三个阶段:噪声估计阶段,多尺度降噪阶段和特征融合阶段。 输入的噪点图像按三个阶段依次处理。 由于所有操作在空间上都是不变的,因此它足够健壮,可以处理任意大小的输入图像。为了避免信息丢失,在馈入下一级之前,将第一级的输出与其输入连接起来,然后进入第二级。
B.噪声估计阶段
这个阶段着重于从输入噪声图像中提取判别特征,这被认为是对噪声水平的估计[8]。 我们采用无池和批处理规范化的普通五层全卷积子网,每次卷积后都会部署ReLU。 在每个卷积层中,要素通道的数量设置为32(最后一层除外(1或3)),并且卷积核大小为3×3。在阶段的最后一层之前,插入了通道注意力模块[11],以明确校准特征通道之间的相互依赖性。 如图3所示,通道权重的集合是我们的目标,它用于重新缩放输入特征图以生成重新校准的特征。 我们首先使用全局平均池(GAP)将U的全局信息压缩到信道描述符中。 然后,紧随其后的是两个完全连接的层(FC),中间层的通道数设置为2。
通道注意模块的最终输出(表示为)是通过
C.多尺度降噪阶段
金字塔池化的概念广泛应用于场景解析[12],图像压缩等领域。 据我们所知,它从未在图像去噪中使用过。 周等。 [13]表明,CNN的经验接受域要比理论域小得多,尤其是在高层上,这意味着在提取特征时全局信息没有完全整合。 相反,为了消除覆盖整个图像的噪声,将目标块与整个图像中的相似内容相匹配具有很大的帮助。
为了减轻这个问题,我们开发了一个五层金字塔。通过五种并行方式,将输入特征图下采样为不同大小,从而帮助分支获得相对比例不同的接收场,以同时捕获原始,本地和全局信息。 合并内核分别设置为1×1、2×2、4×4、8×8和16×16。 然后,每个合并的特征之后都是U-Net [14],它是由深度编码-解码和跳过连接组成的网络,因为研究表明,连续的上采样和下采样有助于对任务进行降噪。 请注意,五个U-Net不共享权重。 在该阶段的最后,通过双线性插值将多级去噪特征上采样到相同的大小,然后将它们连接在一起。
D.特征融合阶段
为了在级联多尺度结果中为每个通道选择大小不同的内核,受[15]的启发,引入了内核选择模块。 内核选择模块的详细信息如图4所示。给定的特征图由内核大小分别为3、5和7的三个并行卷积进行,得到。 我们首先通过元素求和来整合来自所有分支的信息:
然后收缩,然后通过GAP和两个FC进行扩展,其操作与频道注意模块中的操作相同,但最后没有Sigmoid。 FC2的三个输出由Softmax进行操作,它们像通道控制机制一样跨通道地应用于分支:
其中α,β和γ分别表示,的软注意力向量。 注意,αc是α的第c个元素,βc和γc同样。 最终输出特征图V是通过将各种内核及其注意力权重相结合来计算的:
其中α,β和γ需要满足,并且。 最后,我们利用1×1卷积层将尺寸压缩为1或3以进行特征融合。
三、结论
为了进行训练,我们从智能手机图像去噪数据集(SIDD)[16]的原始RGB空间和sRGB空间中利用了320对图像(噪声和地面真实)。
在本文中,提出了一种PRIDNet,用于对真实图像进行盲降噪。 拟议的网络包括三个连续阶段。 第一阶段探讨要素渠道的相对重要性。 在第二阶段,开发了金字塔池化以去除多尺度特征。 在最后阶段,介绍了自适应核选择的操作以进行特征融合。定性和定量实验均表明我们的方法具有竞争优势。
参考资料:
四、代码‘
https://github.com/491506870/PRIDNet 代码
import tensorflow as tf
import tensorflow.contrib.slim as slim
from tflearn.layers.conv import global_avg_pool
def lrelu(x):
return tf.maximum(x * 0.2, x)
def upsample_and_concat(x1, x2, output_channels, in_channels):
pool_size = 2
deconv_filter = tf.Variable(tf.truncated_normal([pool_size, pool_size, output_channels, in_channels], stddev=0.02))
deconv = tf.nn.conv2d_transpose(x1, deconv_filter, tf.shape(x2), strides=[1, pool_size, pool_size, 1])
deconv_output = tf.concat([deconv, x2], 3)
deconv_output.set_shape([None, None, None, output_channels * 2])
return deconv_output
def unet(input):
conv1 = slim.conv2d(input, 32, [3, 3], rate=1, activation_fn=lrelu)
conv1 = slim.conv2d(conv1, 32, [3, 3], rate=1, activation_fn=lrelu)
conv1 = slim.conv2d(conv1, 32, [3, 3], rate=1, activation_fn=lrelu)
conv1 = slim.conv2d(conv1, 32, [3, 3], rate=1, activation_fn=lrelu)
pool1 = slim.max_pool2d(conv1, [2, 2], padding='SAME')
conv2 = slim.conv2d(pool1, 64, [3, 3], rate=1, activation_fn=lrelu)
conv2 = slim.conv2d(conv2, 64, [3, 3], rate=1, activation_fn=lrelu)
conv2 = slim.conv2d(conv2, 64, [3, 3], rate=1, activation_fn=lrelu)
conv2 = slim.conv2d(conv2, 64, [3, 3], rate=1, activation_fn=lrelu)
pool2 = slim.max_pool2d(conv2, [2, 2], padding='SAME')
conv3 = slim.conv2d(pool2, 128, [3, 3], rate=1, activation_fn=lrelu)
conv3 = slim.conv2d(conv3, 128, [3, 3], rate=1, activation_fn=lrelu)
conv3 = slim.conv2d(conv3, 128, [3, 3], rate=1, activation_fn=lrelu)
conv3 = slim.conv2d(conv3, 128, [3, 3], rate=1, activation_fn=lrelu)
pool3 = slim.max_pool2d(conv3, [2, 2], padding='SAME')
conv4 = slim.conv2d(pool3, 256, [3, 3], rate=1, activation_fn=lrelu)
conv4 = slim.conv2d(conv4, 256, [3, 3], rate=1, activation_fn=lrelu)
conv4 = slim.conv2d(conv4, 256, [3, 3], rate=1, activation_fn=lrelu)
conv4 = slim.conv2d(conv4, 256, [3, 3], rate=1, activation_fn=lrelu)
pool4 = slim.max_pool2d(conv4, [2, 2], padding='SAME')
conv5 = slim.conv2d(pool4, 512, [3, 3], rate=1, activation_fn=lrelu)
conv5 = slim.conv2d(conv5, 512, [3, 3], rate=1, activation_fn=lrelu)
conv5 = slim.conv2d(conv5, 512, [3, 3], rate=1, activation_fn=lrelu)
conv5 = slim.conv2d(conv5, 512, [3, 3], rate=1, activation_fn=lrelu)
up6 = upsample_and_concat(conv5, conv4, 256, 512)
conv6 = slim.conv2d(up6, 256, [3, 3], rate=1, activation_fn=lrelu)
conv6 = slim.conv2d(conv6, 256, [3, 3], rate=1, activation_fn=lrelu)
conv6 = slim.conv2d(conv6, 256, [3, 3], rate=1, activation_fn=lrelu)
up7 = upsample_and_concat(conv6, conv3, 128, 256)
conv7 = slim.conv2d(up7, 128, [3, 3], rate=1, activation_fn=lrelu)
conv7 = slim.conv2d(conv7, 128, [3, 3], rate=1, activation_fn=lrelu)
conv7 = slim.conv2d(conv7, 128, [3, 3], rate=1, activation_fn=lrelu)
up8 = upsample_and_concat(conv7, conv2, 64, 128)
conv8 = slim.conv2d(up8, 64, [3, 3], rate=1, activation_fn=lrelu)
conv8 = slim.conv2d(conv8, 64, [3, 3], rate=1, activation_fn=lrelu)
conv8 = slim.conv2d(conv8, 64, [3, 3], rate=1, activation_fn=lrelu)
up9 = upsample_and_concat(conv8, conv1, 32, 64)
conv9 = slim.conv2d(up9, 32, [3, 3], rate=1, activation_fn=lrelu)
conv9 = slim.conv2d(conv9, 32, [3, 3], rate=1, activation_fn=lrelu)
conv9 = slim.conv2d(conv9, 32, [3, 3], rate=1, activation_fn=lrelu)
conv10 = slim.conv2d(conv9, 1, [1, 1], rate=1, activation_fn=None)
#out = tf.depth_to_space(conv10, 2)
return conv10
def feature_encoding(input):
conv1 = slim.conv2d(input, 32, [3, 3], rate=1, activation_fn=lrelu, scope='fe_conv1')
conv2 = slim.conv2d(conv1, 32, [3, 3], rate=1, activation_fn=lrelu, scope='fe_conv2')
conv3 = slim.conv2d(conv2, 32, [3, 3], rate=1, activation_fn=lrelu, scope='fe_conv3')
conv4 = slim.conv2d(conv3, 32, [3, 3], rate=1, activation_fn=lrelu, scope='fe_conv4')
conv4 = squeeze_excitation_layer(conv4, 32, 2)
output = slim.conv2d(conv4, 1, [3, 3], rate=1, activation_fn=lrelu, scope='fe_conv5')
return output
def avg_pool(feature_map):
ksize = [[1, 1, 1, 1], [1, 2, 2, 1], [1, 4, 4, 1], [1, 8, 8, 1], [1, 16, 16, 1]]
pool1 = tf.nn.avg_pool(feature_map, ksize=ksize[0], strides=ksize[0], padding='VALID')
pool2 = tf.nn.avg_pool(feature_map, ksize=ksize[1], strides=ksize[1], padding='VALID')
pool3 = tf.nn.avg_pool(feature_map, ksize=ksize[2], strides=ksize[2], padding='VALID')
pool4 = tf.nn.avg_pool(feature_map, ksize=ksize[3], strides=ksize[3], padding='VALID')
pool5 = tf.nn.avg_pool(feature_map, ksize=ksize[4], strides=ksize[4], padding='VALID')
return pool1, pool2, pool3, pool4, pool5
def all_unet(pool1, pool2, pool3, pool4, pool5):
unet1 = unet(pool1)
unet2 = unet(pool2)
unet3 = unet(pool3)
unet4 = unet(pool4)
unet5 = unet(pool5)
return unet1, unet2, unet3, unet4, unet5
def resize_all_image(unet1, unet2, unet3, unet4, unet5):
resize1 = tf.image.resize_images(images=unet1, size=[tf.shape(unet1, out_type=tf.int32)[1],tf.shape(unet1, out_type=tf.int32)[2]], method=tf.image.ResizeMethod.BILINEAR)
resize2 = tf.image.resize_images(images=unet2, size=[tf.shape(unet1, out_type=tf.int32)[1],tf.shape(unet1, out_type=tf.int32)[2]], method=tf.image.ResizeMethod.BILINEAR)
resize3 = tf.image.resize_images(images=unet3, size=[tf.shape(unet1, out_type=tf.int32)[1],tf.shape(unet1, out_type=tf.int32)[2]], method=tf.image.ResizeMethod.BILINEAR)
resize4 = tf.image.resize_images(images=unet4, size=[tf.shape(unet1, out_type=tf.int32)[1],tf.shape(unet1, out_type=tf.int32)[2]], method=tf.image.ResizeMethod.BILINEAR)
resize5 = tf.image.resize_images(images=unet5, size=[tf.shape(unet1, out_type=tf.int32)[1],tf.shape(unet1, out_type=tf.int32)[2]], method=tf.image.ResizeMethod.BILINEAR)
return resize1, resize2, resize3, resize4, resize5
def to_clean_image(feature_map, resize1, resize2, resize3, resize4, resize5):
concat = tf.concat([feature_map, resize1, resize2, resize3, resize4, resize5], 3)
sk_conv1 = slim.conv2d(concat, 7, [3, 3], rate=1, activation_fn=lrelu)
sk_conv2 = slim.conv2d(concat, 7, [5, 5], rate=1, activation_fn=lrelu)
sk_conv3 = slim.conv2d(concat, 7, [7, 7], rate=1, activation_fn=lrelu)
sk_out = selective_kernel_layer(sk_conv1, sk_conv2, sk_conv3, 4, 7)
output = slim.conv2d(sk_out, 1, [3, 3], rate=1, activation_fn=None)
return output
def squeeze_excitation_layer(input_x, out_dim, middle):
squeeze = global_avg_pool(input_x)
excitation = tf.layers.dense(squeeze, use_bias=True, units=middle)
excitation = tf.nn.relu(excitation)
excitation = tf.layers.dense(excitation, use_bias=True, units=out_dim)
excitation = tf.nn.sigmoid(excitation)
excitation = tf.reshape(excitation, [-1, 1, 1, out_dim])
scale = input_x * excitation
return scale
def selective_kernel_layer(sk_conv1, sk_conv2, sk_conv3, middle, out_dim):
sum_u = sk_conv1 + sk_conv2 + sk_conv3
squeeze = global_avg_pool(sum_u)
squeeze = tf.reshape(squeeze, [-1, 1, 1, out_dim])
z = tf.layers.dense(squeeze, use_bias=True, units=middle)
z = tf.nn.relu(z)
a1 = tf.layers.dense(z, use_bias=True, units=out_dim)
a2 = tf.layers.dense(z, use_bias=True, units=out_dim)
a3 = tf.layers.dense(z, use_bias=True, units=out_dim)
before_softmax = tf.concat([a1, a2, a3], 1)
after_softmax = tf.nn.softmax(before_softmax, dim=1)
a1 = after_softmax[:, 0, :, :]
a1 = tf.reshape(a1, [-1, 1, 1, out_dim])
a2 = after_softmax[:, 1, :, :]
a2 = tf.reshape(a2, [-1, 1, 1, out_dim])
a3 = after_softmax[:, 2, :, :]
a3 = tf.reshape(a3, [-1, 1, 1, out_dim])
select_1 = sk_conv1 * a1
select_2 = sk_conv2 * a2
select_3 = sk_conv3 * a3
out = select_1 + select_2 + select_3
return out
def network(in_image):
feature_map = feature_encoding(in_image)
feature_map_2 = tf.concat([in_image, feature_map], 3)
pool1, pool2, pool3, pool4, pool5 = avg_pool(feature_map_2)
unet1, unet2, unet3, unet4, unet5 = all_unet(pool1, pool2, pool3, pool4, pool5)
resize1, resize2, resize3, resize4, resize5 = resize_all_image(unet1, unet2, unet3, unet4, unet5)
out_image = to_clean_image(feature_map_2, resize1, resize2, resize3, resize4, resize5)
return out_image
代码为tensorflow版本,但写的也比较清楚,论文中的UNet没有画出详细结构,看一下代码中就可以了。