【LLIE专题】ZeroDCE: Zero-Reference Deep Curve Estimation for Low-Light Image Enhancement

专题介绍

在低光照环境下,传统成像设备往往因画面昏暗、细节丢失而受限。LLIE(低照度暗光增强)技术应运而生,它通过提升图像亮度、对比度,减少噪点并恢复色彩细节,让暗夜变得清晰可见。
LLIE技术从传统方法如直方图均衡化、Retinex模型等起步,近年来借助深度学习,尤其是卷积神经网络(CNN),GAN模型,扩散模型实现了质的飞跃。这些算法能自动学习图像特征,精准处理低光照图像,效果显著优于传统技术。
本专题将聚焦LLIE技术的核心原理、应用案例及最新进展,让我们一起见证LLIE如何点亮暗夜,开启视觉新视界!欢迎一起探讨交流!


本文将对 ZeroDCE: Zero-Reference Deep Curve Estimation for Low-Light Image Enhancement,这篇暗光增强算法进行讲解。参考资料如下:
[1].[ZeroDCE论文地址]
[2].[ZeroDCE代码地址]

一、研究背景

低光照条件下的图像增强对于提高图像的视觉质量以及计算机视觉系统在极端光照条件下的性能至关重要。然而,传统的低光照图像增强方法通常需要大量的配对或未配对数据进行训练,这在实际应用中往往难以获取。此外,现有的一些基于深度学习的方法在处理复杂光照条件时存在过拟合和计算效率低下的问题。因此,研究一种无需参考图像的低光照图像增强方法具有重要的理论和实际意义。

二、ZeroDCE方法

ZeroDCE的框架图如下:

在这里插入图片描述

Zero-DCE是一种新型的低光照图像增强方法,其核心思想是将光照增强任务转化为图像特定曲线(LE-curve)的估计问题。该方法通过训练一个轻量级的深度网络(DCE-Net)来估计像素级的高阶曲线(网络为每个像素点估计α值,通过α值来构造高阶曲线),从而实现对输入图像动态范围的调整。该曲线具有以下特点:

1.像素值范围限制:确保增强后的图像像素值在归一化的 [0,1] 范围内,避免信息丢失。
2.单调性:保持相邻像素的差异(对比度)。
3.可微性:便于通过深度卷积神经网络进行参数调整。

为了达到以上三个目标,文中设计了一个二次方曲线:

L E ( I ( x ) ; α ) = I ( x ) + α I ( x ) ( 1 − I ( x ) ) LE(I(x);α)=I(x)+αI(x)(1-I(x)) LE(I(x);α)=I(x)+αI(x)(1I(x))

其中,x为像素坐标; LE(I(x);α)是输入I(x)的增强版本; α∈[−1,1]是曲线的可训练参数,用于调整LE-curve的级数以及曝光度。每个像素值都在[0,1]之间,每个运算都是像素层面上的。使用时,在输入的RGB通道分别应用LE-Curve,这可以更好地保持固有颜色。下图是不同α值的LE-curve示意图:

在这里插入图片描述

可以看到设计的曲线可以很好地满足上述的三个要求。此外,该曲线还可以叠加使用,实现更灵活的调整,以应对具有挑战性的低光条件。

L E n ( x ) = L E n − 1 ( x ) + α n L E n − 1 ( x ) ( 1 − L E n + + 1 ( x ) ) L E_{n}(\mathbf{x})=L E_{n-1}(\mathbf{x})+\alpha_{n} L E_{n-1}(\mathbf{x})\left(1-L E_{n++1}(\mathbf{x})\right) LEn(x)=LEn1(x)+αnLEn1(x)(1LEn++1(x))

其中,n是迭代的次数,文中将迭代次数设置为8,可以满足大多数情况。当n为1时,上式就退化为了基本形式。下图是n为4时候曲线形态,可以看出,n增大后曲线调节能力更强。

在这里插入图片描述

DCE-Net 网络结构:DCE-Net 是一个轻量级的深度网络,用于估计最佳拟合的 LE 曲线。输入为low-light图像,输出为一组用于高阶曲线的pixel-wise curve parameter maps(上文提到的α值)。

DCE-Net由7个具有对称结构的卷积层组成(类似于U-Net)

  • 前6层的卷积核为(3x3x32,stride=1)然后接一个ReLU激活,抛弃了down-sampling和bn层(作者认为这会破坏领域像素间的关系);
  • 最后一层卷积通道为24(用于8个迭代轮次的parameter maps,也就是为每个像素点估计不同的α值),接一个Tanh激活函数。

其网络结构代码如下所示:

class enhance_net_nopool(nn.Module):

	def __init__(self):
		super(enhance_net_nopool, self).__init__()
		self.relu = nn.ReLU(inplace=True)
		number_f = 32
		self.e_conv1 = nn.Conv2d(3,number_f,3,1,1,bias=True) 
		self.e_conv2 = nn.Conv2d(number_f,number_f,3,1,1,bias=True) 
		self.e_conv3 = nn.Conv2d(number_f,number_f,3,1,1,bias=True) 
		self.e_conv4 = nn.Conv2d(number_f,number_f,3,1,1,bias=True) 
		self.e_conv5 = nn.Conv2d(number_f*2,number_f,3,1,1,bias=True) 
		self.e_conv6 = nn.Conv2d(number_f*2,number_f,3,1,1,bias=True) 
		self.e_conv7 = nn.Conv2d(number_f*2,24,3,1,1,bias=True) 
		self.maxpool = nn.MaxPool2d(2, stride=2, return_indices=False, ceil_mode=False)
		self.upsample = nn.UpsamplingBilinear2d(scale_factor=2)

	def forward(self, x):

		x1 = self.relu(self.e_conv1(x))
		x2 = self.relu(self.e_conv2(x1))
		x3 = self.relu(self.e_conv3(x2))
		x4 = self.relu(self.e_conv4(x3))
		x5 = self.relu(self.e_conv5(torch.cat([x3,x4],1)))
		x6 = self.relu(self.e_conv6(torch.cat([x2,x5],1)))

		x_r = F.tanh(self.e_conv7(torch.cat([x1,x6],1)))
		r1,r2,r3,r4,r5,r6,r7,r8 = torch.split(x_r, 3, dim=1)

		x = x + r1*(torch.pow(x,2)-x)
		x = x + r2*(torch.pow(x,2)-x)
		x = x + r3*(torch.pow(x,2)-x)
		enhance_image_1 = x + r4*(torch.pow(x,2)-x)		
		x = enhance_image_1 + r5*(torch.pow(enhance_image_1,2)-enhance_image_1)		
		x = x + r6*(torch.pow(x,2)-x)	
		x = x + r7*(torch.pow(x,2)-x)
		enhance_image = x + r8*(torch.pow(x,2)-x)
		r = torch.cat([r1,r2,r3,r4,r5,r6,r7,r8],1)
		return enhance_image_1,enhance_image,r

Zero-DCE损失函数
为了保证Zero-Reference(即无参考增强),本文提出了4个损失函数。

  1. Spatial Consistency Loss
    该损失能够维持输入图像与其增强版本之间的邻域差异(梯度),从而促进增强后图像仍能保持空间一致性。计算公式为:
    L spa  = 1 K ∑ i = 1 K ∑ j ∈ Ω ( i ) ( ∣ ( Y i − Y j ) ∣ − ∣ ( I i − I j ) ∣ ) 2 L_{\text {spa }}=\frac{1}{K} \sum_{i=1}^{K} \sum_{j \in \Omega(i)}\left(\left|\left(Y_{i}-Y_{j}\right)\right|-\left|\left(I_{i}-I_{j}\right)\right|\right)^{2} Lspa =K1i=1KjΩ(i)((YiYj)(IiIj))2
    其中,K为局部区域的数量,Ω(i)是以区域i为中心的相邻的四个区域(top,down,left,right). Y和I分别为增强图像和输入图像的局部区域平均强度值。这个局部区域的Size经验性地设置为4 x 4,代码实现中作者采用了pooling操作来实现局部区域的分块。详细代码如下:
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]]).cuda().unsqueeze(0).unsqueeze(0)
        kernel_right = torch.FloatTensor( [[0,0,0],[0,1,-1],[0,0,0]]).cuda().unsqueeze(0).unsqueeze(0)
        kernel_up = torch.FloatTensor( [[0,-1,0],[0,1, 0 ],[0,0,0]]).cuda().unsqueeze(0).unsqueeze(0)
        kernel_down = torch.FloatTensor( [[0,0,0],[0,1, 0],[0,-1,0]]).cuda().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)	

        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)
        return E
  1. Exposure Control Loss
    该损失为曝光控制损失。为了抑制曝光不足或过度区域,作者设计了一个曝光控制损失Lexp来控制曝光水平。曝光控制损失测量局部区域的平均强度值与良好曝光水平E之间的距离。作者在实验中将E设置为0.6,尽管作者没有发现将E设置在[0.4,0.7]范围内的性能差异。损失Lexp可以表示为:
    L e x p = 1 M ∑ k = 1 M ∣ Y k − E ∣ L_{e x p}=\frac{1}{M} \sum_{k=1}^{M}\left|Y_{k}-E\right| Lexp=M1k=1MYkE
    其中M表示大小为16×16的非重叠局部区域的数目,Y是增强图像中局部区域的平均强度值。该损失代码如下:
class L_exp(nn.Module):

    def __init__(self,patch_size,mean_val):
        super(L_exp, self).__init__()
        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] ).cuda(),2))
        return d
  1. Color Constancy Loss
    该损失为颜色一致性损失函数,以灰度世界假设为原理。该假设认为:对于一幅有着大量色彩变化的图像,其R,G,B 三个色彩分量的平均值趋于同一灰度值 K,该损失约束增强后图像三通道色彩分量平均值相等。损失函数如下所示:
    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)ε(JpJq)2,ε={(R,G),(R,B),(G,B)}
    其中,Jp表示增强图像中p通道的平均强度值,(p,q)表示一对通道。该损失代码如下:
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
  1. Illumination Smoothness Loss
    为了保持相邻像素之间的单调性关系,为每个曲线参数图A添加了光照平滑性损失。该损失函数如下:
    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=1Ncξ(xAnc+yAnc)2,ξ={R,G,B}

其中N是迭代的次数,▽x和▽y表示水平和垂直方向的梯度。其代码如下:

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

最终损失函数为四个函数的加权和:
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

三、实验结果

训练集合并了low-light和over-exposed图像(Part 1 of SICE数据集,3022张不同曝光程度的图像,其中2422张图片用于训练),图像尺寸为512x512。并使用上述损失函数进行无监督训练。

  1. 损失函数消融实验

在这里插入图片描述

从上图可以看出,移除Lspa会导致对比度降低(例如云的区域);移除Lexp 会导致低亮度区域曝光不足;移除Lcol会出现严重的色偏现象;移除LtvA会降低邻域间的相关性,从而导致明显的artifacts。

  1. 网络大小,迭代次数消融实验

在这里插入图片描述

如上图所示,l-f-n代表Zero-DCE有l层卷积,每层有f个feature map以及迭代次数为n。从实验结果可以看出:

  • 迭代次数太少效果不佳,和之前我们说的迭代次数越少曲线调节能力越弱相呼应;
  • 网络结构层数以及特征数过少,也即网络参数量过少效果也会受影响。
  1. 训练数据对效果影响

在这里插入图片描述

  • a是输入图像;
  • b是上面提到的数据训练的效果;
  • c是b中训练数据中的暗光的数据训练的网络出的效果;
  • d是DarkFace数据集中提供的9000张暗光图像训练的网络出的效果;
  • e是SICE数据集中Part1和Part2子集的4800张多曝光图像训练的网络的效果;

这些结果表明了在网络训练过程中使用多曝光训练数据的合理性和必要性。此外,当使用更多的多曝光训练数据(即Zero-DCELargeLH)时,Zero-DCE可以更好地恢复暗区。

  1. 和经典方法对比效果

在这里插入图片描述

如图所示,作者和经典暗光增强算法做了对比,可以看出本文方案存在一定优势。定量实验可以参考作者原论文。

在这里插入图片描述

最后作者做了推理耗时对比实验,可以看出,该网络耗时非常低,优势很大。

四、总结

Zero-DCE的核心思想是将低光照图像增强问题转化为图像特定的曲线估计问题,通过训练一个轻量级的神经网络(DCE-Net)来预测像素级的高阶曲线,再利用该曲线对图像进行调整,从而实现图像的亮度和对比度增强。其主要特点包括以下几个方面:

  • 零参考增强 :无需任何成对或非成对的训练数据,也不需要参考图像,降低了应用门槛。
  • 轻量级网络 :所采用的 DCE-Net 网络结构轻量化,参数量少,易于训练,且推理速度快,可在手机等移动设备上实现实时图像增强。
  • 端到端训练 :网络结构设计简洁,可实现端到端的训练,简化了传统图像增强方法中复杂的预处理和后处理步骤。

五、Zero-DCE++

作者在充分实验和观察的基础上,对Zero-DCE从三个方面进行优化,最终获得了参数更少,计算量更低,推理速度更快的Zero-DCE++。
主要包括一下几个改进点:

  • 采用轻量级的网络结构:在保持基本网络结构的基础上,采用深度可分离卷积替代传统的卷积层从而大幅降低网络的参数量和计算量。
  • 简化亮度增强曲线:在经过大量试验后,作者发现在每次迭代过程中α值变化微小可以采取一个统一的曲线参数近似代替,因此将亮度增强曲线简化为:
    L E n ( x ) = L E ( n − 1 ) ( x ) + A ( x ) L E ( n − 1 ) ( x ) ( 1 − L E ( n − 1 ) ( x ) ) LE_n (x)=LE_(n-1) (x)+A(x)LE_(n-1) (x)(1-LE_(n-1) (x)) LEn(x)=LE(n1)(x)+A(x)LE(n1)(x)(1LE(n1)(x))
    A表示相同的增强曲线参数。这样可以减少参数量。
  • 采样降低网络推理耗时:对输入图像进行下采样用于估计曲线参数,再将曲线参数上采样至原始尺寸后用于图像的迭代增强。

六、思考

Zero-DCE具有如下优势:

  • 出色的增强效果 :能够显著提升低光照图像的亮度和对比度,使图像细节更加清晰可见,同时保持图像的自然度,效果优于许多传统的图像增强方法。
  • 高效的推理 :推理速度快,适合实时图像处理应用,如手机摄影、实时视频监控等。
  • 广泛的适用性 :不仅适用于常见的低光照图像增强场景,还可作为计算机视觉任务(如目标检测、识别等)的预处理步骤,提高输入图像的质量,进而提升整个视觉系统的性能。

但是其迭代的曲线生成方式或许对于推理速度要求更高的应用场景来说没有做到极致。

  1. 传统方法调整图像亮度对比度可以通过gamma曲线来调整,是否我们可以用网络来生成一条自适应的gamma曲线,然后通过该曲线直接调整图像。这样我们就避免了迭代,减少了耗时
  2. Zero-DCE并未考虑噪声因素,在暗区提亮后噪声会被明显放大
  3. 虽然无监督损失设计巧妙,但是如何权衡这些损失函数需要较多实验才能得出较好的增强效果
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值