这篇文章是2021年CVPR会议论文,核心思想是用CNN网络训练一个LUT-Based的超分方法;由于表中存储了输入像素对应的HR像素值,故在测试的时候,我们只需要从表中进行读取(当然实践中还会增加插值操作,后续会讲到)。这篇文章是查表法在SR领域的首篇应用,推出的SR-LUT算法以快速实现 L R → S R LR\to SR LR→SR为特点,当然也存在着一些可改进点。
参考文档:
①源码(Pytorch)
②【CVPR2021】Practical Single-Image Super-Resolution Using Look-Up Table
Practical Single-Image Super-Resolution Using Look-Up Table
Abstract
- 文章受启发于SR于移动端速度欠缺的问题,提出了一种可以在测试阶段脱离CNN来实现超分的LUT算法。之前所提出的一系列基于CNN的超分方法虽然实现了较高的表现力(一般),但是CNN会在推断时候依赖神经网络,而在移动端的设备一般都没有配备GPU,这样就CNN-based的实用性就会大打折扣。因此基于实用性的基本要求,我们需要一个可以脱离神经网络来将移动端中的 L R LR LR图像转换为 H R HR HR图像的算法——SR-LUT。
- SR-LUT是将CNN的输出结果保存在LUT中,然后在query-set上不需要CNN,直接查表获取对应高分辨率的像素值来弥补低分辨率图像损失的细节。
- 此外由于不需要太多的浮点运算,故查表的过程也不会太慢。在SR-LUT的CNN中,网络输入一个较小的感受野,以及模型较少的layer个数使得模型的训练时间也比之前CNN-Based方法快很多。
综上所述,SR-LUT是一种结合CNN和LUT在SR领域的实用性算法,它以较快的执行( L R → S R LR\to SR LR→SR)速度以及适中的表现力为推送点(具体的相关实验见后续内容)。
1 Introduction
在SR-LUT的训练期间,我们训练一个输入具有较小感受野的像素(如
2
×
2
2\times 2
2×2)的CNN网络,输入端会经过self-ensemble处理来扩大感受野,然后将网络的训练结果存到一张查找表中,LUT的索引就是输入像素的值,其对应的内容就是SR像素。这样带来的问题就是由于你要考虑各种小感受野的输入,那么这张表就会很大,造成内存消耗太多,一般的移动端都吃不消;因此在实际中,我们会生成一张sampled-LUT,其本质就是通过下采样的方式来减小查找表占用的内存。
在SR-LUT测试期间,我们可以直接脱离CNN,对每一个输入像素,直接到Sampled-LUT中查找出来,然后通过四面体插值来得出最终的SR像素值。
下图是本文推出的3种SR-LUT变体和其余几种插值方法、CNN方法的对比:
从上图中可以看出SR-LUT以较快的实现速度完成了合适的PSNR优化。
总结一下SR-LUT:
- 这篇文章是第一次将CNN和LUT结合在SR中的应用。
- SR-LUT可以以较快的执行速度将LR图像恢复到SR,并且可以在手机端实现出来。
- SR-LUT不仅执行速度快,并且他可以与一些高表现的CNN方法进行比较,即其本身的表现力并不差。
- SR-LUT可以直接脱离CNN,通过查表的方式获取SR像素,这直接带来了免GPU的便利。
- SR-LUT的实践版本Sampled-LUT只需要占用较少的内存就可以近似实现全LUT本身的功能。
2 Related Work
3 Method
如上图所示:
从整体来看,SR-LUT可以分为训练和测试2个阶段:
训练阶段可进一步分为CNN训练(3.1小节)和HR像素值存储(3.2小节)2个部分。
测试阶段(3.3小节)可进一步分为查表和四面体插值2个部分。
想要搞清楚SR-LUT,首先得明白理解2个问题:
- 什么是SR-LUT?
- 输入像素和存到SR-LUT中的值是什么对应的?
Q1:什么是SR-LUT?
如上图所示SR-LUT就是一张
(
(
2
8
)
2
×
2
,
r
×
r
)
((2^8)^{2\times 2}, r\times r)
((28)2×2,r×r)大小的表格(源码中使用Numpy数组保存),表格的索引值代表着输入
L
R
LR
LR像素的像素值,我们以感受野为
2
×
2
,
r
=
2
2\times 2, r=2
2×2,r=2且每个像素都是8-bins(8-bits input image
)为例,那么对于一个全LUT而言,SR-LUT就是一个6维的张量,前四维分别是四个输入像素值,根据乘法原理,一共有
2
8
×
2
8
×
2
8
×
2
8
=
(
2
8
)
4
2^8\times 2^8\times 2^8\times 2^8=(2^8)^4
28×28×28×28=(28)4种组合,既然是Full-LUT
自然就要考虑到所有情况,又因为CNN网络的输出是
r
2
=
4
r^2=4
r2=4张feature map,所以上图配有
2
2
=
4
2^2=4
22=4个值。后面的2维表示的是输出4个像素值的序号,如“[0][0]表示HR像素值
V
0
V_0
V0”。我们可以计算一下一个这样的感受野产生的LUT有多大:
(
2
8
)
(
2
×
2
)
×
r
2
×
8
(
b
i
t
s
)
=
16
(
G
B
)
(2^8)^{(2\times 2)} \times r^2 \times 8(bits) = 16(GB)
(28)(2×2)×r2×8(bits)=16(GB)
Note:
- 从这里我们也可以看出感受野的增加会直接导致LUT呈指数式增加!
- 我们可以总结一个公式,对8bins而言,全LUT所占内存为:
(
2
8
)
n
×
r
2
×
8
(
b
i
t
s
)
(2^8)^{n}\times r^2 \times 8(bits)
(28)n×r2×8(bits)。但是在论文中的公式如下图所示:
图中对于4D输入感受野的全LUT内存消耗为64GB,是因为这里是当 r = 4 r=4 r=4的时候,而上面我们计算的是 r = 2 r=2 r=2的情况。
Q2:输入像素和存到SR-LUT中的值是什么对应的?
作者在网络的最后一层设置了深度为
r
2
r^2
r2的feature map,也就是说对每一个
2
×
2
2\times 2
2×2的输入,有
r
2
=
4
r^2=4
r2=4张size为
1
×
1
1\times 1
1×1的特征图产生,然后最后来一步PixelShuffle,让深度转换为空间平面状,将输出的4张feature map平铺开来,变成一张输入size为
r
×
r
r\times r
r×r的HR图像。
关于深度转空间平面,一般有以下3种方式:
理解①:
之所以输出通道数为 r 2 r^2 r2是因为图像的宽和高都被放大了 r r r倍,而深度转空间就是个张量reshape的过程,pixelshuffle之后长和宽各乘上 r r r倍就可以实现放大的效果了。
理解②:我们用代码来表示为:
out = out.view(batch*channels, 1, r*h, r*w )
Note:
- 这里 h h h和 w w w可以理解为输入图像的大小。
- 第一步前向的过程并不改变图像的size,但是增加了通道数。
理解③:这也是文中作者采用的方式,即亚像素卷积层:torch.nn.PixelShuffle(r)
这里假设SR缩放系数
r
=
3
r=3
r=3:
作者在文章中设置了多种格式的感知野,具体如下表所示:
上表中的Sampled size是我们实际在做测试的时候采取的办法,从表达式也可以推断出如果说全LUT一共有
(
2
8
)
n
(2^8)^n
(28)n行,那么sampled-LUT只是仅有
(
2
4
+
1
)
n
(2^4+1)^n
(24+1)n行而已,那么这个值是怎么来的呢?对于8-bins输入图像而言,我们设置一个采样间隔
W
=
2
4
W=2^4
W=24,我们对输入图像进行下采样,将输入空间划分为
{
0
,
2
4
,
32
,
48
,
64
,
80
,
96
,
112
,
128
,
144
,
160
,
176
,
192
,
208
,
224
,
240
,
255
}
\{0,2^4,32, 48, 64, 80, 96, 112, 128, 144, 160, 176, 192, 208, 224, 240, 255\}
{0,24,32,48,64,80,96,112,128,144,160,176,192,208,224,240,255}一共17个采样点。那么意思就是说,原本的全LUT将每个不同值输入都进行采样,填进表里面,现在将同属于一个范围(如
48
64
48~64
48 64)内的值都看成一个值来统计,这样的话就直接缩小了LUT的规模,大大降低了存储量。至于如何读取呢?这里就是SR-LUT在做test的时候,会采用四面体插值的方式来解决不属于采样点空间中的图像值(具体介绍见3.3节)。
Note:
- 在文章中,作者将RF=2,3,4输入规模的SR-LUT模型分别称之为Ours-V,Ours-F, Ours-S,前2者执行速度更快,但Ours-S可实现更高的PSNR。
- Sampled-LUT的思想和直方图统计很类似,在直方图统计中,对于24位的RGB值,如果要完全统计的话,要对 2 2 4 2^24 224种颜色统计他们各自的像素值,那就完蛋了!因此我们通常会划定一个个区间,一个区间内的颜色值具有类似的颜色值,比如我们划定512个区间,去统计512个区间各自的像素值即可,这样就节约了很多资源。
3.1 Training Deep SR Network
整个训练过程分为2个部分,首先是CNN训练,其完成之后再进行LUT的写入操作。
CNN网络结构
论文中训练部分的图(Figure 2)很有误导性,不看源码的话很有可能会理解错,因此我用自己画的图来介绍这一过程:
简要描述下网络的组成:
- 训练部分的输入是从DIV2K数据集中裁剪出来的patch,比如 48 × 48 48\times 48 48×48大小的,通过监督学习来学习到 L R → S R LR\to SR LR→SR这个重建函数 f f f,并不涉及 1 D 、 2 D 、 3 D 、 4 D 1D、2D、3D、4D 1D、2D、3D、4D的感受野输入,这种小感受野输入是在第二环节(存表部分)才输入网络的。而由于第二环节输入的感知野很小,所以网络不需要太多的层数去堆叠,作者采用了6层CNN网络以及一层亚像素卷积层。
- 除了第一层使用 2 × 2 2\times 2 2×2,其余几层都是用 1 × 1 1\times 1 1×1的卷积核;此外除了最后一层CNN以外,其余的CNN层都外接ReLU来增加模型非线性度。
- 输入端是
(
48
+
1
)
×
(
48
+
1
)
(48+1)\times (48+1)
(48+1)×(48+1)(这一过程可通过
torch.pad
来填充)是为了配合第一层 2 × 2 2\times 2 2×2的卷积核,从而直到亚像素卷积层之前都保持图像为大小为 48 × 48 48\times 48 48×48。 - 输入和输出的图像size保持不变。
- 最后一层CNN输出深度为 r 2 r^2 r2的feature map,为的是在图像size保持不变的基础上通过深度到平面空间的调整,使得输入图像的高和宽各自乘以 r r r倍来实现分辨率的提升。
- 网络最后一层使用PixelShuffle,主要目的在于调整网络输出为1张高为 r H rH rH,宽为 r W rW rW的高分辨率图像。
- 作者将自集成(self ensemble)的技巧用于CNN的训练中,主要目的在于在不增加LUT存储量的基础上扩大感受野。
Self-Ensemble
自集成的方法其实就是对图像做一些增强,之后对网络的输出做反增强操作,最后的输出就是几种反增强操作的平均,作者在文章采用了4种增强方式,分别是旋转90°、180°、270°,具体如下:
y
^
i
=
1
4
∑
j
=
0
3
R
j
−
1
(
f
(
R
j
(
x
i
)
)
)
\hat{y}_i = \frac{1}{4}\sum^3_{j=0}R_j^{-1}(f(R_j(x_i)))
y^i=41j=0∑3Rj−1(f(Rj(xi)))
Note:
- Self-ensemble的放法一般用在测试中,比如EDSR论文中就这样用,并得到了表现力的增强。而在本文中,作者将自集成用于模型的训练,好处在于在不增加LUT的size的情况下提升了输入的感受野,增加了模型的表现力。
- 将自集成之后的输出值和标签 y y y做loss之后就可以反向传播更新参数了。
对同一份数据
i
i
i做了4次自集成之后得到了
y
i
^
\hat{y_i}
yi^,然后与Ground Truth(
y
i
y_i
yi)做loss,通过梯度下降来更新SR-LUT网络模型的参数:
L
o
s
s
=
∑
i
l
(
y
i
^
,
y
i
)
.
Loss = \sum_i l(\hat{y_i}, y_i).
Loss=i∑l(yi^,yi).
小结一下:
训练环节就是输入图像patch进行训练,这和之前的一些SR方法是类似的,只是训练出了一个超分网络,训练的结果就是将网络模型的参数保存下来,供下一环节——存表的使用。
3.2 Transferring to LUT
LUT的写入操作在CNN训练完成之后进行。在了解了第三节开头之后关于LUT的两个问题之后,我们就可以开始本节的学习了!
接下来为了讨论方便,我假设4D(
2
×
2
2\times 2
2×2)感受野输入,缩放倍数
r
=
4
r=4
r=4,采样间隔
W
=
2
4
W=2^4
W=24。
这一环节分为2步骤:
①枚举出所有可能的
2
×
2
2\times 2
2×2情况,对于全LUT来说,有
25
6
2
×
2
256^{2\times 2}
2562×2种情况,对于Sampled-LUT来说只有
1
7
2
×
2
17^{2\times 2}
172×2种情况,因此为了节约LUT存储消耗,我们只需要枚举
出采样点像素对应的
1
7
4
17^4
174种情况即可。然后将
(
1
7
4
,
4
)
(17^4, 4)
(174,4)reshape成
(
1
7
4
,
1
,
2
,
2
)
(17^4, 1, 2, 2)
(174,1,2,2)的图像格式,准备下一步输入进之前训练好的SR-LUT网络。这一步就像是准备好自变量以及其定义域。千万要注意,这里不能像往常那样打乱数据集!
②这一步就是输入自变量来得到输出结果。将之前CNN训练的结果以上一步准备好的格式为
(
1
7
4
,
1
,
2
,
2
)
(17^4, 1, 2, 2)
(174,1,2,2)为输入,CNN输出结果为内容写进LUT中,得到一个
(
1
7
4
,
1
,
r
,
r
)
(17^4, 1, r, r)
(174,1,r,r)格式的张量,这个就是我们所要的LUT!这一步的前向传播如下图所示:
从图中可看出网络将输入
2
×
2
2\times 2
2×2像素块降采样成
1
×
1
1\times 1
1×1的像素,然后经过几层
1
×
1
1\times 1
1×1卷积之后经过亚像素卷积层输出
(
1
∗
r
)
×
(
1
∗
r
)
(1* r)\times (1* r)
(1∗r)×(1∗r)的高分辨率像素块。然后我们将网络输出格式
(
1
7
4
,
1
,
r
,
r
)
(17^4, 1, r, r)
(174,1,r,r)格式的张量reshape一下成
(
1
7
4
,
r
2
)
(17^4, r^2)
(174,r2)并保存为Numpy数组存起来供测试的时候使用。
Note:
- 为了避免LUT多大,作者提出了sampled-LUT去缓解这一问题,简单来说全LUT到sampled-LUT的变换就是(默认
R
F
=
4
,
W
=
2
4
RF=4,W=2^4
RF=4,W=24):
L U T [ 256 ] [ 256 ] [ 256 ] [ 256 ] → L U T [ 17 ] [ 17 ] [ 17 ] [ 17 ] . LUT[256][256][256][256]\to LUT[17][17][17][17]. LUT[256][256][256][256]→LUT[17][17][17][17]. - 采样点为: { 0 , 2 4 , 32 , 48 , 64 , 80 , 96 , 112 , 128 , 144 , 160 , 176 , 192 , 208 , 224 , 240 , 255 } \{0,2^4,32, 48, 64, 80, 96, 112, 128, 144, 160, 176, 192, 208, 224, 240, 255\} {0,24,32,48,64,80,96,112,128,144,160,176,192,208,224,240,255}。
- 在训练的时候,我们将采样空间进行
W
=
2
4
W=2^4
W=24等分,先以17个采样值为像素值,然后不断进行扩展,依次可获得
1
D
→
(
17
,
1
)
、
2
D
→
(
1
7
2
,
2
)
、
3
D
→
(
1
7
3
,
3
)
、
4
D
→
(
1
7
4
,
4
)
1D\to (17, 1)、2D\to (17^2, 2)、3D\to (17^3, 3)、4D\to (17^4, 4)
1D→(17,1)、2D→(172,2)、3D→(173,3)、4D→(174,4)的输入信息;我将此过程可视化出来如下图所示:
从上图可以看出,我们将每一种输入情况都罗列了出来。比如对于4D输入情况,第0列代表左上像素,第1列代表右上像素,第2列代表左下像素,第3列代表右下像素(这些都是可以通过PyTorch的底层数组排列实现)。然后reshape成 ( 1 7 4 , 1 , 2 , 2 ) (17^4, 1, 2, 2) (174,1,2,2)并归一化之后就可以输送进网络获取高分辨率块了。 - 存表环节尤其要注意的一点是:我们将一个 2 × 2 2\times 2 2×2的感受野输入进网络,输出的是一个 r × r r\times r r×r的高分辨率块。①一种理解方式:网络遍历了输入 2 × 2 2\times 2 2×2块的信息,之后进行整合( 2 × 2 2\times 2 2×2卷积核输出 1 × 1 1\times 1 1×1的feature map),最后输出一个 r × r r\times r r×r高分辨率块,是一个整体对应一个 r × r r\times r r×r块的过程;②还有一种理解方式是:当我们在测试的时候,由于是整张图像,我们对每个像素点 ( x , y ) (x,y) (x,y)遍历,以这个 ( x , y ) (x,y) (x,y)为左上像素,观察其右边1格、下边1格、右下1格的像素信息,相等于获取了 2 × 2 2\times 2 2×2感受野,接着去LUT里查找出以4个像素值为索引的 r × r r\times r r×r的内容结果来作为当前像素点 ( x , y ) (x,y) (x,y)重建出的 r × r r\times r r×r高分辨率像素块,因此存表也可以看成是 2 × 2 2\times 2 2×2感受野中左上像素对应的一个 r × r r\times r r×r块写入表中,这也解释了论文中Figure 2为何是一个蓝色像素对应了一整块绿色像素。
- 存表环节是不需要做自集成的,也不需要填充。
- 在测试的时候,对于非采样点,我们会根据距离这个非采样点最近的几个采样点通过插值的方式读取,具体如何读取见3.3节。
小结一下:
存表环节就是枚举出所有nD感受野的输入情况,利用训练环节保存下来的网络模型,输出
1
7
n
17^n
17n个
r
×
r
r\times r
r×r块,并保存下来供给下一环节测试使用,这就是Sampled-LUT。
3.3 Testing Using SR-LUT
测试阶段采用Set5等常用测试数据集,不同于训练阶段图像patch的输入、存表阶段
n
D
nD
nD感受野的输入,测试阶段输入的是整张图片。
在测试阶段,我们就可以完全脱离CNN,在不需要GPU的情况下只用查表得方式来恢复出
L
R
LR
LR图像欠缺的细节,产生高分辨率的图像。
如果是对于理论上的Full-LUT,那么测试的时候,只需要根据输入像素查表得出结果就好了,但在实际中,由于
2
×
2
2\times 2
2×2的感受野都会带来64GB的存储消耗,故我们可以采用上一节训练好的sampled-LUT来做测试阶段图像细节的恢复。
在SR-LUT中,恢复出SR图像的核心思想就是如果输入像素值是非采样点,那么就根据距离它最近的采样点来进行插值得出该输入点对应的SR像素值。也就是说我们在测试阶段需要使用查表+插值2个操作!
关于插值算法,最简单直接的就是线性插值,如bilinear(双线性插值)、trilinear(三线性插值)、tetralinear(四线性插值)、pentallinear(五线性插值)。但是这些线性插值的运算操作较多,执行速度并不是最快的,既然我们追求在移动端设备上的执行速度,自然应该选择一些更快的插值算法,如下表所示:2D感受野作者采用三角插值;在输入为3D的感受野的时候,tetrahefral(四面体插值)在乘法操作和比较操作次数上要小于三线性插值;在输入4D感受野的时候,4-单形插值是最高效的插值方法。这几种插值的具体代码在源码中都有写明。
Note:
- 关于顶点和输入感受野的关系: n D nD nD大小的输入感受野并不意味着要 ( n + 1 ) (n+1) (n+1)个顶点形成的空间体来做插值,如Table 2所示, 2 D 2D 2D感受野可以用4个(双线性插值)顶点,也可以用3个(三角插值)顶点。但总体来看,输出感受野越大,所需要的空间体肯定越复杂,顶点会多一些,计算复杂度也会上升。
Triangular Interpolation
为了理解4D下的四面体插值算法,作者介绍了在2D下的三角插值算法(两种插值算法原理相似,可以互相迁移):
目前我们的输入是一个
1
×
2
1\times 2
1×2的图像,那么我们就需要
(
2
+
1
)
(2+1)
(2+1)个顶点做一些运算得到我们非采样点的像素值,这几个顶点就是距离这个非采样点最近的3个采样点,具体来看:
①:设输入为
I
0
=
24
,
I
1
=
60
I_0=24,I_1=60
I0=24,I1=60,转换成二进制就是
I
0
=
(
0001
_
1000
)
2
,
I
1
=
(
0011
_
1100
)
2
I_0=(0001\_1000)_2,I_1=(0011\_1100)_2
I0=(0001_1000)2,I1=(0011_1100)2
②:取两个像素的高四位(MSBs)作为第一个顶点
P
00
P_{00}
P00的索引值,我们通过查表来得出结果:
P
00
=
L
U
T
[
1
]
[
3
]
P_{00} = LUT[1][3]
P00=LUT[1][3],根据就近原则,对角的那个值可以作为第二个顶点
P
11
=
L
U
T
[
1
+
1
]
[
3
+
1
]
=
L
U
T
[
2
]
[
4
]
P_{11} = LUT[1+1][3+1]=LUT[2][4]
P11=LUT[1+1][3+1]=LUT[2][4]。
③:第三个顶点由输入二进制后四位(LSBs)组成,2D输入情况下,我们只有
{
P
00
,
P
01
,
P
10
,
P
11
}
\{P_{00}, P_{01},P_{10}, P_{11}\}
{P00,P01,P10,P11}四种结果,那么到底是
P
01
P_{01}
P01还是
P
10
P_{10}
P10呢?这取决于LSBs,
I
0
I_0
I0的LSB记为
L
x
=
8
L_x=8
Lx=8,则
I
1
I_1
I1的LSB为
L
y
=
12
L_y=12
Ly=12,然后查下面这个表(虽然是给四面体插值用的,但是三角形插值也适用):比较
L
x
、
L
y
L_x、L_y
Lx、Ly可知,因为
L
x
<
L
y
L_x<L_y
Lx<Ly,所以取
O
1
=
P
01
、
O
2
=
P
11
O_1=P_{01}、O_2=P_{11}
O1=P01、O2=P11,从这里也验证了第②步中为何取对角顶点。故第三个顶点就是
P
01
=
L
U
T
[
1
]
[
4
]
P_{01}=LUT[1][4]
P01=LUT[1][4]。
④:获取了3个采样点的像素值之后,接下来就要获取3个对应的权重值,也是查询上表:
W
−
L
y
=
4
,
L
y
−
L
x
=
4
,
L
x
=
8
W-L_y=4,L_y-L_x=4, L_x=8
W−Ly=4,Ly−Lx=4,Lx=8,具体如下所示:
⑤:最后的插值结果就是将3个顶点进行加权平均:
V
^
=
1
W
∑
i
=
0
2
w
i
O
i
.
\hat{V} = \frac{1}{W}\sum^2_{i=0}w_iO_i.
V^=W1i=0∑2wiOi.其中
w
i
、
O
i
w_i、O_i
wi、Oi分别是权重和顶点像素值。
Note:
- MSBs是通过像素值除以采样间隔 W W W取商获得的;LSBs是通过像素值除以采样间隔 W W W取余得到的。
- 我们根据MSBs获取顶点 O i O_i Oi的值,对于 n D nD nD感受野,共有 2 n 2^n 2n个顶点 P P P,获取顶点值的过程就是查表的过程,我们利用输入感受野中 n n n个像素值来获取sampled-LUT的索引,从而得到一个 r × r r\times r r×r的 H R HR HR像素块作为一个顶点值。
- LSBs决定了每个顶点对应的权值 w i w_i wi以及选用哪几个顶点。
对于3D输入的正四面体插值,我们需要找到4个顶点,那么对于4D感受野,我们需要找到5个顶点,以此类推。特别的,对于4D输入,则插值结果为:
V
^
=
1
W
∑
i
=
0
4
w
i
O
i
.
\hat{V} = \frac{1}{W}\sum^4_{i=0}w_iO_i.
V^=W1i=0∑4wiOi.其中
O
0
=
P
0000
,
O
4
=
P
1111
O_0=P_{0000},O_4= P_{1111}
O0=P0000,O4=P1111,也就是不管是四面体插值还是三角插值,最初2个顶点是可以固定的,就是MSB得出的初始点和其对角顶点。
Note:
- 上述讲的是2D输入下的三角形插值算法,对于更高的3D和4D也是一样的算法。
- 输入感受野越大,则插值的复杂度越高!
- 对于4D感受野输入,作者采用4-simplex单形插值算法。
小结一下(以
2
×
2
2\times 2
2×2感受野为例):
测试过程,对于每一张图片,我们遍历
其所有
H
×
W
H\times W
H×W个像素点,对于其中每个像素点
(
x
,
y
)
(x,y)
(x,y),我们以该点为左上像素,查询其右边1格、下面1格以及右下1格的像素信息,我们在第二个环节中说过,左上像素代表了LUT中的第0列,右上代表LUT中第1列,左下代表LUT中第2列,右下代表LUT中第3列,那么这个像素值情况和索引如何对应起来呢?如下图所示:
a、b、c、d分别表示他们的MSBs值,设
L
=
2
8
W
L=\frac{2^8}{W}
L=W28,那么
a
∗
L
∗
L
∗
L
a*L*L*L
a∗L∗L∗L就表示进入了a所代表像素值的采样点范围内,比如a所代表像素值是250,
250
/
W
250/W
250/W取商得到a=15,那么
a
∗
L
∗
L
∗
L
=
15
×
L
3
a*L*L*L=15\times L^3
a∗L∗L∗L=15×L3,其表示进入了17个采样点中“240”这个采样点代表的范围内,这个范围一共有
L
3
L^3
L3行,那么具体到哪一行呢?那就要其余3个MSBs来决定了;同理对于右上点:
b
∗
L
∗
L
b*L*L
b∗L∗L;同理对于左下点:
c
∗
L
c*L
c∗L;同理对于右下点:
d
d
d,所以我们就根据输入像素点对应的MSBs信息获取到了某一行的索引值:
k
=
a
∗
L
∗
L
∗
L
+
b
∗
L
∗
L
+
c
∗
L
+
d
k = a*L*L*L + b*L*L + c*L + d
k=a∗L∗L∗L+b∗L∗L+c∗L+d,那么LUT[
k
k
k]所得的那个
r
×
r
r\times r
r×r数组就是上述这个
2
×
2
2\times 2
2×2像素块超分的结果,也就是一个顶点值。最后的插值结果就是根据多个顶点以及对应的权值相加而成!遍历完所有像素点之后,我们就获得了格式为
(
C
,
H
,
W
,
r
,
r
)
(C,H,W, r, r)
(C,H,W,r,r)的结果,通过reshape之后就成了
(
C
,
r
H
,
r
W
)
(C, rH, rW)
(C,rH,rW)的输出图像:
(
C
,
H
,
W
,
r
,
r
)
→
(
C
,
H
,
r
,
W
,
r
)
→
(
C
,
r
H
,
r
W
)
.
(C,H,W, r, r)\to (C, H, r, W, r)\to (C, rH, rW).
(C,H,W,r,r)→(C,H,r,W,r)→(C,rH,rW).
Note:
测试阶段也使用了自集成的方法
,总体而言,对每一张图片分别进行自集成(原图+3次旋转),每一种增强先pad再进行插值算法处理,结果再进行逆增强,最后将4次增强的结果进行平均输出的图像就是我们最终需要保存下来的 H R HR HR图像,到此SR-LUT的超分重建算是完成了。- 关于SR-LUT在插值方面更多的细节,可参考我的另一篇SR-LUT源码解析。
4 Experiments
4.1 Experimental Setting
- 使用DIV2K作为训练集;使用Set5、Set14、BSD100、Urban100、Manga109作测试集。
- 使用PNSR/SSIM作为评价指标,虽然训练都是在3个通道上进行的,但是PSNR的测试只在Ycbcr的Y单通道上进行。
- upscaling factor=4。
- mini-batch=32。
- 学习率 l r = 1 0 − 4 lr=10^{-4} lr=10−4。
4.2 Comparison with Others
本节是将SR-LUT3个变体和其他几种算法在训练集上的比较,其中Ours-V表示使用 R F = 1 × 2 RF=1\times 2 RF=1×2和Full-LUT;Ours-F表示使用 R F = 1 × 3 RF=1\times 3 RF=1×3和Sampled-LUT;Ours-S表示使用 2 × 2 2\times 2 2×2和Sampled-LUT。
具体结果如下图表所示:
从图中我们可以得出以下结论:
- Table 4中的测试时间是基于 320 × 180 → 1280 × 720 320\times 180\to 1280\times 720 320×180→1280×720的转换测试的。
- Ours-V实现了几乎是最少的执行时间,同时PSNR也还不错;Ours-F相比速度慢了一些且LUT内存上升,但是表现力提升了;至于Ours-S也保持着runtime和LUT内存的上升,同时也带来了PSNR的提升。因此我们可以说SR-LUT实现了较快的执行速度,同时表现力也挺合适。
- 基于DNN的一些方式,虽然达到了最佳的表现力,但是所消耗的时间是SR-LUT的好几倍,显然SR-LUT在实际移动端快速实现SR的优势体现了出来。
- 在Figure 4中,Ours-V和Ours-F在横向具有比Ours-S更好的表现,可能是因为其感受野形状的原因;而Ours-S的 2 × 2 2\times 2 2×2感受更喜欢捕捉对角方向的细节特征。
4.3 Analysis
RF Size and Kernel Shape
研究的是感受野的个数以及RF形状(即Kernel Shape)对于runtime以及PSNR的影响,实验在Set5数据集上测试,结果如下所示:
- RF-Size的研究:从Ours-V、Ours-F、B可以看出越大的感受野会产生更高质量的图像,但是带来的测试时间也会更久,因为size越大,插值带来的计算复杂度越高,因为顶点的个数会增多。。
- Kernel-Shape的研究:从A、Ours-F对比来看,后者有更好的表现,我个人认为是后者包含的感受野较多;从B和Ours-S对比来看,两者数值上差不多,但是后者在视觉上更加舒适,可能和其 2 × 2 2\times 2 2×2的kernel形状有关,这种形状更加考虑相邻像素的关系。
- 从Ours-S、Ours-S w/o RE对比来看,前者有更好的表现,说明了
self-ensemble
对表现力的提升还是有帮助的。
Sampling Interval
这里研究的是采样间隔
W
W
W对模型表现力的影响。一般来说采样间隔越大,LUT规模越小,带来存储量越小,但是其采样点越稀疏,由于测试阶段非采样点的值需要通过采样点插值计算出来,因此采样点分布越系数,对于插值的影响越坏,所以直接导致表现力受损。
下图是作者进行的实验结果:
- 从上图可以看出, W W W越大,LUT存储量越低,但是图像质量的恢复效果越差。
- 此外,在 W ≤ 2 4 W\leq 2^4 W≤24的时候,PSNR的下降不大,但是存储量却少了很多;超过这个值之后,PSNR就小幅下降了,因此作者会采用 W = 2 4 W=2^4 W=24作为采样间隔。
5 Conclusion
文章最大的贡献在于推出了一种CNN和LUT结合的SR方法——SR-LUT
:
- SR-LUT分为训练、存表、测试三个步骤。其中
训练部分
就是训练一个6层的CNN,输入是图像patch,比如 48 × 48 48\times 48 48×48的图像,输出需要经过PixelShuffle来将深度转换为图像平面从而产生分辨率放大的效果,此外作者采用了self-ensemble来增大模型的表现力,其在不增加LUT存储量的基础上增加感受野的技巧;存表
是在训练结束后,将输入 n D nD nD感受野中的像素值进行组合作为LUT的索引,CNN输出就是我们要往表里填写的内容;测试部分
是脱离CNN,直接喂进去整张图片的输入像素值,通过逐个查表读出SR像素值的过程。 - 为了减小LUT的存储消耗,作者采用
Sampled-LUT
而不是Full-LUT,这就是需要在测试阶段,对于非采样点根据采样点进行插值计算得到最后的输出结果。 - SR-LUT最大的价值在于其在测试阶段脱离CNN使用,避免了GPU的配置,从而可以更方便的在移动端、电视端使用。此外,其执行( L R → S R LR\to SR LR→SR)速度也比传统的插值办法快很多,可以说SR-LUT在满足较快的执行速度下同时兼具适中的表现力(PSNR)。
此外,为了进一步理解SR-LUT,我在另一篇中对SR-LUT的源码进行了梳理和详细解析,有兴趣的可以看看。