3.1 概述
-
分类:Zero-shot Learning
-
数据集:SICE、NPE、LIME、MEF、DICM、VV、DARK FACE
-
损失函数:
- spatial consistency loss(空间一致性损失函数)
- exposure control loss(曝光控制损失函数)
- color constancy loss(颜色恒定损失函数)
- illumination smoothness loss(照明平滑度损失函数)
-
评估标准:User Study PI; PNSR SSIM; MAE Runtime; Face detection
-
现存难点:
- 配对数据训练:1)数据难以收集 2)如果使用合成数据训练,模型泛化能力往往有限
- 非配对数据训练:需要精心选择非配对训练数据
-
动机:有些图像编辑软件更改曲线就能修改图像,所以本文也想设计一种通过修改曲线就能实现暗图像增强
3.2 贡献
- 贡献一: 首创不基于配对数据或非配对数据的增强网络
- 优点:1)防止过拟合 2)泛化能力强
- 贡献二:设计了一种图像特定的曲线,这些曲线能对输入动态范围进行像素级调整,以获得增强图像
- 贡献三:提出了利用特定非参考损失函数间接评估增强质量,探索在没有参考图像的情况下训练一个深度图像增强模型的可能性
3.3 摘要
- Zero-DCE以低光图像为输入,产生高阶曲线作为输出。然后使用这些曲线对输入动态范围进行像素级调整,以获得增强图像
- 曲线是专门设计的,考虑了像素值范围、单调性、可微性
- 优点:训练的时候不需要配对数据,也不需要非配对数据
- 缺点:需要精心设计非参考损失函数以下是本文精心设计的非参考损失函数
- spatial consistency loss(空间一致性损失函数)
- exposure control loss(曝光控制损失函数)
- color constancy loss(颜色恒定损失函数)
- illumination smoothness loss(照明平滑度损失函数)
3.4 网络结构及解析
3.4.1 LE曲线
LE曲线:Light-Enhancement Curve (LE-curve)
3.4.1.1 曲线要求
- 增强图像的每个像素值应在[0,1]的归一化范围内,以避免溢出截断引起的信息丢失
- 疑问:为什么需要归一化,在什么情况下会导致溢出截断
- 解答:如果按照正常像素值进行操作,多次 255 255 255相加就可能因数值过大导致截断溢出
- 这条曲线应该是单调的,以保持相邻像素的差异(对比度)
- 疑问:为什么曲线得要是单调的,单调曲线为啥能保持相邻像素的差异
- 解答:如果一个像素值本来就是比另一个大,那么经过变换后这个像素值仍然应该比另一个像素值大。这样可以保持相邻像素值之前的差异,也就是对比度。如果不是单调的,经过变换后亮的变暗,暗的变亮,这会降低图像的质量
- 在梯度反向传播过程中,这条曲线的形式应尽可能简单和可微
- 疑问:曲线简单和可微对于反向传播有何益处?
- 解答:
- 简单:简单的曲线参数量少,相应的计算量少,而且能降低过拟合的风险
- 可微:这样网络就可以通过梯度反向传播更新参数,从而提升性能
3.4.1.2 曲线函数
1)公式
L E ( I ( x ) ; α ) = I ( x ) + α I ( x ) ( 1 − I ( x ) ) LE(I(x);\alpha)=I(x)+\alpha I(x)(1-I(x)) LE(I(x);α)=I(x)+αI(x)(1−I(x))
x \color{#DD5145}x x:像素坐标
α \color{#DD5145}\alpha α: α ∈ [ − 1 , 1 ] \alpha\in [-1,1] α∈[−1,1], α \alpha α能控制 I E IE IE曲线的大小,并能控制曝光水平
I ( x ) \color{#DD5145}I(x) I(x):输入内容, I ( x ) ∈ [ 0 , 1 ] I(x)\in [0,1] I(x)∈[0,1]
L E ( I ( x ) ; α ) \color{#DD5145}LE(I(x);\alpha) LE(I(x);α):输入的增强版本
2)图像
![]() |
L E ( I ( x ) ; α ) = { I ( x ) 2 α = − 1 I ( x ) α = 0 − ( I ( x ) − 1 ) 2 + 1 α = 1 LE(I(x);\alpha)= \begin{cases} I(x)^2 & \alpha=-1\\ I(x) & \alpha=0 \\ -(I(x)-1)^2+1 & \alpha =1 \end{cases} LE(I(x);α)=⎩ ⎨ ⎧I(x)2I(x)−(I(x)−1)2+1α=−1α=0α=1
由图像可知: L E ( I ( x ) ; α ) LE(I(x);\alpha) LE(I(x);α)大多时候是一个简单的单调递增的二次函数
3)效果
这函数能有效地增加或减少输入图像的动态范围,还能移除过曝伪影
-
疑问:
-
什么是过曝?
- 过曝是指在拍照时,有太多的光线进入相机,使得照片变得太亮,失去了细节和对比度
过曝图像示例 -
什么是伪影?
- 伪影是指在照片中出现一些不真实或不自然的现象,比如颜色失真,噪点,模糊,条纹等。
图1 颜色失真
图2 噪点失真
图3 模糊图像
图4 条纹伪影
-
什么是过曝伪影?
- 过曝伪影是指在图片中,由于过曝导致的一种伪影
太阳光强的周围建筑明显出现了 颜色失真等伪影问题
-
3.4.1.3 高阶曲线
由于二次函数曲线变化有限,为了拟合更为复杂的曲线,本文迭代嵌套了这个函数
L E 1 ( x ) = I ( x ) + α 1 I ( x ) ( 1 − I ( x ) ) L E 2 ( x ) = L E 1 ( x ) + α 2 L E 1 ( x ) ( 1 − L E 1 ( x ) ) ⋯ ⋯ L E n ( x ) = L E n − 1 ( x ) + α n L E n − 1 ( 1 − L E n − 1 ( x ) ) \begin{aligned} LE_1(x)&=I(x)+\alpha_1I(x)(1-I(x)) \\ LE_2(x)&=LE_1(x)+\alpha_2LE_1(x)(1-LE_1(x))\\ \\ \cdots\cdots\\ \\ LE_n(x)&=LE_{n-1}(x)+\alpha_nLE_{n-1}(1-LE_{n-1}(x)) \end{aligned} LE1(x)LE2(x)⋯⋯LEn(x)=I(x)+α1I(x)(1−I(x))=LE1(x)+α2LE1(x)(1−LE1(x))=LEn−1(x)+αnLEn−1(1−LEn−1(x))
n用于控制曲率的迭代次数,本文n设置为8
![]() |
3.4.1.4 逐像素曲线
高阶曲线可以在更宽的动态范围内调整图像。尽管如此,它仍然是一个全局调整,因为 α 用于所有像素。全局映射倾向于过度/不足地增强局部区域。为了解决这个问题,我们将 α 表述为逐像素参数,即给定输入像的每个像素都有一个对应的曲线,具有最佳拟合 α 来调整其动态范围。
L E n ( x ) = L E n − 1 ( x ) + A n ( x ) ( 1 − L E n − 1 ( x ) ) LE_n(x)=LE_{n-1}(x)+\mathcal{A}_n(x)(1-LE_{n-1}(x)) LEn(x)=LEn−1(x)+An(x)(1−LEn−1(x))
A \mathcal{A} A是与给定图像大小相同的参数图
![]() |
3.4.2 D C E − N e t DCE-Net DCE−Net
![]() |
输入:低光照图像( 256 × 256 × 3 256\times256\times3 256×256×3)
输出:一组像素级的曲线参数图(最终产生了 24 24 24个参数图)
卷积核:每一层由 32 32 32个大小为 3 × 3 3\times 3 3×3,步长为 1 1 1的卷积核组成,
激活函数:除了最后一个卷积层使用 T a n h Tanh Tanh激活函数外,其余层都是使用的是 R e L U ReLU ReLU激活函数
3.4.3 无参考损失函数
3.4.3.1 Spatial Consistency Loss
Spatial Consistency Loss :空间一致性损失,一种用于衡量图像提亮效果的方法
主要思想:提亮后的图像应该和原来的图像在空间上保持一致,也就是说,相邻的像素之间的差别不应该变得太大或太小
L s p a = 1 K ∑ i = 1 K ∑ j ∈ Ω ( i ) ( ∣ Y i − Y j ∣ − ∣ ( I i − I j ) ∣ ) 2 L_{spa}=\frac{1}{K}\sum_{i=1}^{K}\sum_{j\in\Omega(i) }(|Y_i-Y_j|-|(I_i-I_j)|)^2 Lspa=K1i=1∑Kj∈Ω(i)∑(∣Yi−Yj∣−∣(Ii−Ij)∣)2
K:局部区域的数量
Ω ( i ) \color{#DD5145}\Omega(i) Ω(i): Ω ( i ) \Omega(i) Ω(i)是以区域 i i i 为中心的四个相邻区域(顶部、下、左、右)
Y:增强版本的局部区域的平均强度值
I:输入版本的局部区域的平均强度值
2)代码
# Spatial Consistency Loss : 空间一致性损失
class L_spa(nn.Module):
def __init__(self):
super(L_spa, self).__init__()
# print(1)kernel = torch.FloatTensor(kernel).unsqueeze(0).unsqueeze(0)
kernel_left = torch.FloatTensor([[0, 0, 0], [-1, 1, 0], [0, 0, 0]]).unsqueeze(0).unsqueeze(0)
kernel_right = torch.FloatTensor([[0, 0, 0], [0, 1, -1], [0, 0, 0]]).unsqueeze(0).unsqueeze(0)
kernel_up = torch.FloatTensor([[0, -1, 0], [0, 1, 0], [0, 0, 0]]).unsqueeze(0).unsqueeze(0)
kernel_down = torch.FloatTensor([[0, 0, 0], [0, 1, 0], [0, -1, 0]]).unsqueeze(0).unsqueeze(0)
self.weight_left = nn.Parameter(data=kernel_left, requires_grad=False)
self.weight_right = nn.Parameter(data=kernel_right, requires_grad=False)
self.weight_up = nn.Parameter(data=kernel_up, requires_grad=False)
self.weight_down = nn.Parameter(data=kernel_down, requires_grad=False)
self.pool = nn.AvgPool2d(4)
def forward(self, org, enhance):
b, c, h, w = org.shape
org_mean = torch.mean(org, 1, keepdim=True)
enhance_mean = torch.mean(enhance, 1, keepdim=True)
org_pool = self.pool(org_mean)
enhance_pool = self.pool(enhance_mean)
weight_diff = torch.max(
torch.FloatTensor([1]) + 10000 * torch.min(org_pool - torch.FloatTensor([0.3]),
torch.FloatTensor([0])),
torch.FloatTensor([0.5]))
E_1 = torch.mul(torch.sign(enhance_pool - torch.FloatTensor([0.5])), enhance_pool - org_pool)
D_org_letf = F.conv2d(org_pool, self.weight_left, padding=1)
D_org_right = F.conv2d(org_pool, self.weight_right, padding=1)
D_org_up = F.conv2d(org_pool, self.weight_up, padding=1)
D_org_down = F.conv2d(org_pool, self.weight_down, padding=1)
D_enhance_letf = F.conv2d(enhance_pool, self.weight_left, padding=1)
D_enhance_right = F.conv2d(enhance_pool, self.weight_right, padding=1)
D_enhance_up = F.conv2d(enhance_pool, self.weight_up, padding=1)
D_enhance_down = F.conv2d(enhance_pool, self.weight_down, padding=1)
D_left = torch.pow(D_org_letf - D_enhance_letf, 2)
D_right = torch.pow(D_org_right - D_enhance_right, 2)
D_up = torch.pow(D_org_up - D_enhance_up, 2)
D_down = torch.pow(D_org_down - D_enhance_down, 2)
E = (D_left + D_right + D_up + D_down)
# E = 25*(D_left + D_right + D_up +D_down)
return E
3.4.3.2 Exposure Control Loss
Exposure Control Loss :曝光控制损失,一种用来衡量图像曝光效果的方法
主要思想:提亮后的图像应该避免有过暗或过亮的区域,而是让每个像素的亮度都接近一个合适的水平
计算:
- 将提亮图像转换成灰度图
- 把灰度图划分成很多个小块,每个小块里有 16 × 16 16\times16 16×16个像素
- 计算所有像素的平均亮度,然后和预设的理想亮度进行比较,本文的理想亮度叫做:well-exposedness level,值为 0.6 0.6 0.6
- 计算每个小块的平均亮度和理想亮度之间的差别求绝对值,然后求平均
- Exposure Control Loss值越小,提亮后的图像和理想亮度月接近,反之则提亮后的图像存在过亮或过暗的问题
1)公式
L e x p = 1 M ∑ k = 1 M ∣ Y k − E ∣ L_{exp}=\frac{1}{M}\sum_{k=1}^{M}|Y_k-E| Lexp=M1k=1∑M∣Yk−E∣
M:表示大小为$ 16\times16$ 的非重叠局部区域的数量
Y:增强图像的局部区域的平均强度值
E:well-exposedness level理想亮度值,本文设置的值为 0.6 0.6 0.6
2)代码
# Exposure Control Loss : 曝光控制损失
class L_exp(nn.Module):
def __init__(self, patch_size, mean_val):
super(L_exp, self).__init__()
# print(1)
self.pool = nn.AvgPool2d(patch_size)
self.mean_val = mean_val
def forward(self, x):
b, c, h, w = x.shape
x = torch.mean(x, 1, keepdim=True)
mean = self.pool(x)
d = torch.mean(torch.pow(mean - torch.FloatTensor([self.mean_val]), 2))
return d
3.4.3.3 Color Constancy Loss
Color Constancy Loss : 颜色恒定损失,一种用来保持图像颜色平衡的方法
主要思想:提亮后的图像应该避免有色偏的现象,而是让每个颜色通道(红、绿、蓝)的平均亮度都接近一个灰度值
计算:
- 将提亮图像分成 R R R、 G G G、 B B B三通道,计算每个通道的平均亮度,记作 J p J^p Jp
- 将不同通道的平均亮度两两相减,求平均和
- Color Constancy Loss值越小,说明提亮图像颜色越平衡,损失越大则说明提亮图像可能有色偏的问题
1)公式
L c o l = ∑ ∀ ( p , q ) ∈ ε ( J p − J q ) 2 , ε = ( R , G ) , ( R , B ) , ( G , B ) L_{col}=\sum_{\forall (p,q)\in\varepsilon }(J^p-J^q)^2,\varepsilon ={(R,G),(R,B),(G,B)} Lcol=∀(p,q)∈ε∑(Jp−Jq)2,ε=(R,G),(R,B),(G,B)
2)代码
# Color Constancy Loss : 颜色恒定损失
class L_color(nn.Module):
def __init__(self):
super(L_color, self).__init__()
def forward(self, x):
b, c, h, w = x.shape
mean_rgb = torch.mean(x, [2, 3], keepdim=True)
mr, mg, mb = torch.split(mean_rgb, 1, dim=1)
Drg = torch.pow(mr - mg, 2)
Drb = torch.pow(mr - mb, 2)
Dgb = torch.pow(mb - mg, 2)
k = torch.pow(torch.pow(Drg, 2) + torch.pow(Drb, 2) + torch.pow(Dgb, 2), 0.5)
return k
3.4.3.4 Illumination Smoothness Loss
Illumination Smoothness Loss :照明光滑度损失,一种用来保持图像光照平滑的方法
主要思想:提亮后的图像应该避免有明显的光照变化或伪影,而是让每个像素的光照变化都很小
计算:
- 将提亮图像分成三个颜色通道,然后计算每个通道的光照曲线参数图 A \mathcal{A} A
- 在每个参数图 A \mathcal{A} A在水平方向和垂直方向上求梯度,也就是求它们的变化率
- 计算所有梯度的平方和后求平均
- Illumination Smoothness Loss越小,提亮图像光照越平滑,反之说明提亮图像光照有突变或伪影
1)公式
L t v A = 1 N ∑ n = 1 N ∑ c ∈ ξ ( ∣ ∇ x A n c + ∇ y A n c ∣ ) 2 , ξ = { R , G , B } L_{tv_\mathcal{A} }=\frac{1}{N}\sum _{n=1}^N\sum_{c\in\xi }(|\nabla_x\mathcal{A}_n^c +\nabla_y \mathcal{A}_n^c |) ^2,\xi =\{R,G,B\} LtvA=N1n=1∑Nc∈ξ∑(∣∇xAnc+∇yAnc∣)2,ξ={R,G,B}
N:迭代次数
∇ x \color{#DD5145} \nabla_x ∇x :水平梯度操作
∇ y \color{#DD5145}\nabla_y ∇y:垂直梯度操作
A \color{#DD5145}\mathcal{A} A:曲线参数图
2)代码
# Illumination Smoothness Loss : 照明损失度函数
class L_TV(nn.Module):
def __init__(self, TVLoss_weight=1):
super(L_TV, self).__init__()
self.TVLoss_weight = TVLoss_weight
def forward(self, x):
batch_size = x.size()[0]
h_x = x.size()[2]
w_x = x.size()[3]
count_h = (x.size()[2] - 1) * x.size()[3]
count_w = x.size()[2] * (x.size()[3] - 1)
h_tv = torch.pow((x[:, :, 1:, :] - x[:, :, :h_x - 1, :]), 2).sum()
w_tv = torch.pow((x[:, :, :, 1:] - x[:, :, :, :w_x - 1]), 2).sum()
return self.TVLoss_weight * 2 * (h_tv / count_h + w_tv / count_w) / batch_size
3.4.3.5 总损失
L t o t a l = L s p a + L e x p + W c o l L c o l + W t v A L t v A L_{total}=L_{spa}+L_{exp}+W_{col}L_{col}+W_{tv_{\mathcal{A}}}L_{tv_{\mathcal{A}}} Ltotal=Lspa+Lexp+WcolLcol+WtvALtvA
3.5 实验部分
3.5.1 概述
训练图像: 2422 2422 2422张,图像大小为: 512 × 512 512\times 512 512×512
测试图像: 600 600 600张
网络参数:
- batch size: 8 8 8
- 网络权值初始化:均值为 0 0 0,方差为 0.02 0.02 0.02的标准差高斯函数
- 偏置初始化:常量
- 优化器: A D A M ADAM ADAM
- 学习率: 1 e − 4 1e^{-4} 1e−4
- W c o l \color{#9414aa}W_{col} Wcol: 0.5 0.5 0.5
- W t v A \color{#9414aa}W_{tv_{\mathcal{A}}} WtvA: 20 20 20
3.5.2 消融实验
3.5.2.1 各损失函数的贡献
w / o L s p a \color{#DD5145}w/o\ L_{spa} w/o Lspa:对比度较低(例如:云区)
w / o L e x p \color{#DD5145}w/o\ L_{exp} w/o Lexp:无法恢复低光区域
w / o L c o l \color{#DD5145}w/o\ L_{col} w/o Lcol:出现了严重的颜色投射
w / o L t v A \color{#DD5145}w/o\ L_{tv_{\mathcal{A}}} w/o LtvA:出现了明显的伪影
3.5.2.2 参数设置效果
![]() f表示每层卷积核个数(即输出通道数)、n表示曲线的迭代次数 |
Z e r o − D C E 3 − 32 − 8 \color{#dd5145}Zero-DCE_{3-32-8} Zero−DCE3−32−8:这个的效果已经很好了
Z e r o − D C E 7 − 32 − 8 、 Z e r o − D C E 7 − 32 − 16 \color{#dd5145}Zero-DCE_{7−32−8}、Zero-DCE_{7−32−16} Zero−DCE7−32−8、Zero−DCE7−32−16:在自然曝光和适当对比度下产生最令人愉悦的视觉效果
Z e r o − D C E 7 − 32 − 8 \color{#9414aa}Zero-DCE_{7-32-8} Zero−DCE7−32−8:本文选择的最终模型,在效率和恢复性能之间取得了良好的平衡
3.5.2.3 训练数据的影响
数据集:
- Z e r o − D C E l o w \color{#1f71e0}Zero-DCE_{low} Zero−DCElow: 2422 2422 2422张图像,其中 900 900 900张微光图像
- Z e r o − D C E L a r g e L \color{#1f71e0}Zero-DCE_{LargeL} Zero−DCELargeL:暗脸数据集提供的 9000 9000 9000张未标记的微光图像
- Z e r o − D C E L a r g e L H \color{#1f71e0}Zero-DCE_{LargeLH} Zero−DCELargeLH:来自 S I C E SICE SICE数据集 P a r t 1 Part1 Part1和 P a r t 2 Part2 Part2子集的数据增强组合的 4800 4800 4800幅多次曝光图像
效果:
- ( c ) 、 ( d ) \color{#1f71e0}(c)、(d) (c)、(d):倾向过度增强光照良好的区域(例如:面部),表明了多曝光训练数据的合理性和必要性
- ( e ) \color{#1f71e0}(e) (e):能更好地恢复暗区域
3.5.3 基准评估
略这部分基本是和其它最先进的方法比较,然后说明自己的方法牛逼
3.6 结论
我们提出了一个用于微光图像增强的深度网络。它可以通过零参考图像进行端到端训练。这是通过将微光图像增强任务制定为图像特定的曲线估计问题来实现的,并设计了一组可微的非参考损失。实验证明了我们的方法与现有的光增强方法的优越性。在未来的工作中,我们将尝试引入语义信息来解决困难情况并考虑噪声的影响。