[20190925 添加开源code]
CVPR2019jiaya.me
firenxygao/deblurgithub.com
本文是香港中文大学、腾讯优图贾佳亚团队投于CVPR2019的一篇关于图像去模糊的方法。
动态场景去模糊(相机抖动、目标运动导致的模糊)是底层视觉中一种极具挑战性的任务。不同于已有方法中的参数独立或参数共享模式,作者提出一种广义而有效的 选择性共享机制用于约束去模糊网络。在每个尺度的子网络中,作者提出一种嵌套跳过连接架构替代残差模块/卷积堆叠模块。除此之外,作者还构建一个更大的去模糊数据集。最后,作者通过充分的实验验证了所提嵌套跳过连接的SOTA性能。
在网络架构方面,作者引入一种嵌套跳过连接模块;在数据集方面,作者构建一个更多更高质量包含5290图相对的数据集。该文的贡献主要包含以下几个方面:
上图给出了作者所提出的网络架构示意图,它由特征提取、特征非线性变换、特征重建三种类型的模块构成。下面分别从参数选择性共享、嵌套跳过连接两个方面进行简单介绍。
文献1提出一种参数独立的Coarse-to-Fine
的图像去模糊架构,但是它缺乏处理不同尺度模糊的约束;文献2提出一种参数共享的Coarse-to-Fine
的图像去模糊架构。
作者从以下两个角度考虑参数问题:
上图给出了锐利块与模块块在不同尺度下的效果对比,可以看出:(1)锐利块具有相似性,下采样后仍然锐利;(2)粗尺度下模糊块在细尺度下变得锐利。
如果特征提取模块参数跨尺度共享,它无法同时提取锐利与模糊特征。比如,粗尺度下学习的锐利特征提取参数无法在细尺度下提取模块特征。基于该发现,作者将文献2中的参数共享机制进行了松弛处理,特征提取采用非共享模式(见下图b),这样可以保证网络可以在每个尺度自动提取最具辨别力的特征。
完成尺度可变特征提取后,特征非线性变化与特征重建的目的是将其转换为锐利特征,故而非线性特征变换模块可以进行跨尺度参数共享(类似SRN),将上图b。受启发于传统迭代去模糊方案,作者假设同样存在尺度内参数共享,进而提出了尺度内参数共享架构。(见上图c)
上图给出了作者在SRN(上图a)基础上提出的改进参数共享方案(见上图b和c)。上图b方案为特征提取模块参数不共享,特征非线性变换参数共享;上图c为特征提取模块参数不共享,特征非线性变换跨尺度、跨模块参数共享。
上图给出了一阶跳过连接、以及二阶三阶跳过连接示意图。他们分别定义如下:
上述架构从视觉上有些类似DenseNet架构,但存在两点区别:
更高阶的残差提升信息传递改善梯度消失问题。该嵌套跳过连接模块可以生成更复杂的特征同时更易于优化训练。作者采用该架构替换ResBlock模块。
个人理解嵌套跳过连接实际上就是残差连接,只不过三阶残差由三个残差模块构成罢了。 不过嵌套连接可以在更微小的Block中进行嵌套,这点是残差模块所不具备的。可参考文末提供的Block代码。期待作者@jiangsutx
开源代码 。
类似于文献1与文献2,作者采用了Coarse-to-Fine
策略构建网路架构。三个尺度中的特征提取、特征重建采用参数不共享方案,特征非线性变换采用特征共享方案。卷积核大小设为为3x3以控制模型大小。默认每个非线性特征变换模块包含4个处理单元(每个单元包含两个卷积),特征提取与重建模块则为stride=2的卷积核转置卷积,子网络的每个阶段包含17个卷积核为$3\times3$的卷积。
损失函数选择MSE,定义如下:
参考文献1中GoPro数据集的制作方法,作者采用GoPro Hero6与iPhone7设备构建了更大的数据集。为避免GoPro数据缺陷,作者在制作数据过程中提出如下三个指导原则,共计收集5290图像对,它是对GoPro的补充。
在训练过程中,图像块大小为256x256,BatchSize=16,参数初始化方法为Xavier,优化器为Adam,学习率为0.0001,指数型学习率下降(0.3),共计训练4000个epoch。
为验证所提参数选择性共享机制的有效性,作者对比了参数独立、参数全局共享、参数选择性共享等对比,相关指标如下所示。
从上表可以看出一下几个重要发现:
为验证所提嵌套跳过连接的有效性,作者对比了(均只含有8个卷积)卷积堆叠、ResBlock、DenseBlock以及Nested,如下表所示:
从中可以看出:
最后,作者对比了所提方法与其他SOTA方法在GoPro数据及上的性能对比与视觉效果(从左到右:输入、文献1、文献2以及本文方法)对比,如下所示。
作者分析了去模糊架构中的参数贡献问题并提出参数选择性共享机制;同时作者还提出一种新的嵌入跳过连接架构;除此之外,作者还构建了一个更大、更高质量的去模糊数据集。通过集成上述改进,作者所提框架取得了SOTA去模糊性能。
--------------------------------------------------
# 因作者并未开源,故而不确定NestedBlock具体形式。
# 这里只是参考文中架构图简单的对比了NestedBlock与ResBlock
# 可以与下面的标准残差模块对比分析两者之间的差异。
# 更具体的网路架构这里不再列出
# 看懂了文中架构图,结合NestedBlock以及SRN可以轻松的写出相关架构代码
class Block(nn.Module):
def __init__(self, inc):
super().__init__()
self.conv1 = nn.Conv2d(inc, inc, 3, 1, 1)
self.relu = nn.ReLU()
self.conv2 = nn.Conv2d(inc, inc, 3, 1, 1)
def forward(self, x):
return self.conv2(self.relu(self.conv1(x)))
class NestedBlock(nn.Module):
def __init__(self, inc):
super().__init__()
self.blk1 = Block(inc)
self.blk2 = Block(inc)
self.blk3 = Block(inc)
self.blk4 = Block(inc)
def forward(self, x):
x1 = self.blk1(x)
x2 = self.blk2(x + x1)
x3 = self.blk3(x + x1 + x2)
x4 = self.blk3(x + x1 + x2 + x3)
return x + x1 + x2 + x3 + x4
class ResBlock(nn.Module):
def __init__(self, inc):
super().__init__()
self.conv1 = nn.Conv2d(inc, inc, 3, 1, 1)
self.relu = nn.ReLU()
self.conv2 = nn.Conv2d(inc, inc, 3, 1, 1)
def forward(self, x):
res = self.conv2(self.relu(self.conv1(x)))
return res + x
编辑于 2019-09-25