尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

低照度图像增强实战:从Retinex到Zero-DCE算法解析与复现

低照度图像增强实战:从Retinex到Zero-DCE算法解析与复现 简介低照度环境下的图像常因光线不足导致RGB特征信息匮乏目标检测时特征提取与识别定位的难度随之上升。这份代码资料正是针对此类痛点将传统算法与深度学习两类低照度增强手段合并整理涵盖Retinex、EnlightenGAN、Zero-DCE等经典与前沿模型可供图像处理初学者快速上手也适合算法工程师在夜间监控、安防等场景中参考选型。包内只有一个docx文档整体体积仅10KB文档把各算法的可直接运行代码、依赖环境与调用方式集中汇总经过了测试拿到后即可对照运行。目前已有1760人学习浏览说明这份清单在解决实际问题上有不错的参考价值。借助它读者无需再费时四处寻找源码能够同时比较多种增强策略对画质和后续检测精度的影响从而为项目挑选出最合适的预处理方案。 低照度图像增强这几年是计算机视觉里一个又热又“痛”的方向。说热是因为安防监控、自动驾驶夜间感知、手机拍照、无人机夜航这些场景全都绕不开它说痛是因为低照度图像本身信噪比极低影子区域一片死黑强行提亮往往把噪声和色偏也一起放大了效果反而辣眼睛。我自己在实际项目里测过不少公开代码从传统的直方图均衡化到基于深度学习的零参考曲线估计踩过的坑比写出来的代码还多。这篇就把我用过、验证过、觉得可以直接拿来改的代码和思路汇总一下顺便把每个方案背后的原理和适用场景讲透。这个汇总适合谁一类是要在真实项目里快速落地暗光增强的算法工程师另一类是刚接触这个方向、想找一份靠谱代码跑通全流程的研究生或者开发者。我会先把方案选型的逻辑理清再逐个拆解核心算法和可运行代码最后给出一套完整的复现流程和排查经验。目标很直接你拿到这篇文章能照着把代码跑起来并且知道每一行关键代码到底在干什么效果不好时知道往哪个方向调。1. 低照度图像增强的整体思路与方案选型1.1 先弄清需求你要的是“看清”还是“好看”低照度增强这个需求在不同场景下定义是完全不一样的这一点如果没想清楚后面做方案选型一定会翻车。安防监控和工业检测的诉求是“看清”——把黑暗区域里的人脸、车牌、文字、缺陷尽可能还原出来哪怕颜色有一点偏差都可以接受关键是要保留边缘细节和纹理信息。手机摄影和内容创作的诉求是“好看”——提亮的同时要保持色彩自然、影调舒服不能出现明显的色偏或伪影。还有一种场景是医学影像或者科研数据比如荧光显微图像这种对保真度要求极高亮度映射的每一步都得可解释不能随便让网络去“脑补”细节。理解这一点之后你再去看各种开源代码就能快速做减法。比如传统Retinex系列在监控场景里依然大量被使用因为它稳定、可解释、算得快而像Zero-DCE这类深度学习方案在手机端和摄影增强里更受欢迎因为它能学出非线性映射关系亮度和色彩的自然度做得更好。拿我自己接触的项目来说之前做过一个夜间车牌识别的前处理模块最初直接搬了某个增强效果很惊艳的深度学习模型结果在真实监控上反而把原本能看清的车牌纹理给抹平了因为那个模型是为摄影美颜设计的细节保持根本不是它的优化目标。后来换回轻量Retinex加自适应伽马校正识别准确率反而提升了十几个百分点。1.2 主流技术路线传统算法到深度学习的演进低照度图像增强的技术路线大致可以按两个维度来划分一是是否需要参考图像二是基于物理模型还是纯数据驱动。传统的直方图均衡化HE和自适应直方图均衡化CLAHE属于最基础的像素级方法原理简单、计算开销极小但问题是增强后容易出现块状伪影和过度放大噪声。Retinex系列是另一条经典路线核心思想是把一幅图像分解成反射分量和光照分量假设反射分量代表物体的固有属性光照分量代表环境亮度增强的本质是估计并去除不均匀光照的影响。早期实现比如单尺度RetinexSSR、多尺度RetinexMSR以及带色彩恢复的MSRCR到今天依然在很多工业软件里有应用。深度学习路线在2018年之后进入爆发期大体上分成三类有监督方法需要成对的低照度/正常光图像训练代表是LLNet、RetinexNet效果上限高但数据制作成本极大真实场景和训练域差异大的时候容易崩。无监督/零参考方法核心代表是Zero-DCE通过一系列精心设计的损失函数让网络在没有任何参考图的情况下自己学习亮度映射曲线部署友好成为目前社区使用最广的开源方案之一。自监督和扩散模型方法比如基于Retinex先验的自监督方案以及近几年把diffusion model引入low-light增强的研究路线如Retinexformer所属的Transformer路线也有部分研究开始结合扩散模型做生成式增强效果更强但计算资源要求高离工程落地还有距离。下面这张表是我个人在项目选型时常用的对比标准供参考方法是否需要配对数据速度细节保持色彩自然度适用场景CLAHE否极快中等较差实时监控、边缘设备SSR/MSR否快中等较差工业检测、简单增强Zero-DCE否快良好良好手机端、摄影增强RetinexNet是中等良好中等学术研究、训练数据充足时Retinexformer是慢优秀优秀高质量场景、离线处理我的建议是如果是项目预研阶段优先跑Zero-DCE和CLAHE两条线一个作为深度方案基线一个作为传统方案基线对比效率和效果之后再决定要不要上更重的模型。2. 核心算法原理与代码实现要点2.1 Zero-DCE零参考深度曲线增强Zero-DCEZero-Reference Deep Curve Estimation是2020年发表在CVPR上的工作它的核心洞察非常巧妙与其让网络直接预测增强后的图像不如让网络学习一组亮度映射曲线然后用这些曲线逐像素地对输入图像进行变换。这组曲线设计得很轻巧每个像素点对应一条二次或三次曲线公式长这样LE(I(x); a) I(x) a * I(x) * (1 - I(x))其中I(x)是输入图像在位置x的像素值归一化到0到1a是网络预测的曲线参数。这个公式的好处在于映射结果始终落在[0,1]区间里因为I(x)在0到1之间时I(x)*(1-I(x))最大值只有0.25只要a的取值合理输出不会溢出从数学上保证了增强结果不会出现像素值越界的问题。网络结构本身非常轻量就是几个卷积层串联输入是一张RGB图输出是24个通道的曲线参数图8个a参数每个迭代应用3次8×324。训练不需要成对的暗光和亮光数据而是靠一组精心设计的损失函数驱动包括空间一致性损失保证相邻区域的亮度变化平滑曝光控制损失或者叫曝光损失让增强后的图像平均亮度向0.6靠近这是经验值模拟摄影师偏好的正常曝光水平颜色恒常性损失避免出现明显的颜色偏移光照平滑损失让预测的参数图在空间上保持平滑当时我在一个夜间行车记录仪增强项目里跑Zero-DCE原版代码第一次看到一版代码没有做任何预训练就能直接推理还是很震撼的因为很多深度学习模型不开源权重根本玩不起来。后来读了源码才发现训练时不需要ground truth所以数据集制作成本极低几百张暗光图就能训练出可用的模型。核心代码实现里最关键的部分是曲线参数图的生成和迭代应用。下面这段就是Zero-DCE的核心网络结构PyTorch简化版import torch import torch.nn as nn class ZeroDCE(nn.Module): def __init__(self, n_curves8, n_iterations3): super().__init__() # 轻量级编码器逐步下采样提取特征 self.conv1 nn.Conv2d(3, 32, 3, padding1) self.conv2 nn.Conv2d(32, 32, 3, padding1, stride2) self.conv3 nn.Conv2d(32, 32, 3, padding1, stride2) self.conv4 nn.Conv2d(32, 32, 3, padding1, stride2) # 上采样恢复到原图分辨率输出n_curves * n_iterations个参数图 self.up1 nn.ConvTranspose2d(32, 32, 3, padding1, stride2, output_padding1) self.up2 nn.ConvTranspose2d(32, 32, 3, padding1, stride2, output_padding1) self.up3 nn.ConvTranspose2d(32, 32, 3, padding1, stride2, output_padding1) self.out nn.Conv2d(32, n_curves * n_iterations, 3, padding1) self.n_curves n_curves self.n_iterations n_iterations self.act nn.ReLU() self.up_act nn.Tanh() def forward(self, x): x1 self.act(self.conv1(x)) x2 self.act(self.conv2(x1)) x3 self.act(self.conv3(x2)) x4 self.act(self.conv4(x3)) x5 self.up_act(self.up1(x4)) x6 self.up_act(self.up2(x5 x3)) x7 self.up_act(self.up3(x6 x2)) curve_params self.out(x7 x1) return curve_params这段代码里有几个细节值得注意。一是下采样和上采样之间有跳跃连接这里用“x5 x3”的方式把浅层特征和深层特征融合可以保留更多空间细节这是恢复高分辨率曲线参数图的关键。二是最后几层用了Tanh激活函数把输出限制在[-1,1]区间这样在应用曲线公式时既可以让像素变亮a为正也可以变暗a为负灵活性更大。如果把这层激活函数改成ReLU你会发现模型只能做单向的亮度提升稍微复杂一点的暗光场景就压不住高光区。曲线迭代应用的部分也贴一下这个是我觉得整个方法最精妙的设计def apply_curve(img, curve_params): img: 归一化到[0,1]的输入图像形状(B, C, H, W) curve_params: 曲线参数图形状(B, n_curves*n_iterations, H, W) batch, _, h, w img.shape curves torch.chunk(curve_params, curve_params.shape[1] // 24, dim1) # 一般取24通道分3次迭代每次8条曲线对应8个局部区域 # 这里以24通道为例做3次迭代 n_iter 3 out img.clone() for i in range(n_iter): params curve_params[:, i * 8:(i 1) * 8, :, :] # 对每个通道独立应用曲线 a params[:, 0:1, :, :] out out a * out * (1 - out) # 还可以按通道拆分RGB分别处理 return out实际使用时建议把图像resize到256×256左右再输入网络输出曲线参数后再映射回原分辨率这样速度和效果相对均衡。如果你处理的是视频流还可以利用时序信息做参数平滑这是后话但效果提升非常明显。2.2 基于Retinex的实现与经典代码Retinex这个思想比深度学习要早得多它假设人眼感知到的颜色取决于物体表面对红、绿、蓝三种光的反射率而不是绝对的光照强度。换句话说一张暗光图可以拆解成“光照分量”和“反射分量”的乘积增强的目标就是去掉光照不均匀的影响把反射分量解放出来。经典的Retinex实现里有一个难点直接从图像分离光照分量是一个病态问题所以大多数算法都用高斯模糊来近似估计光照。单尺度RetinexSSR的做法就很直接用高斯核对原图做卷积得到估计的光照图然后把原图和光照图取对数相减得到反射图。多尺度RetinexMSR就是换了几个不同尺度的高斯核最后加权融合。这里我直接给你一个OpenCV版本的核心实现import cv2 import numpy as np def single_scale_retinex(img, sigma300): img: BGR 图像uint8 sigma: 高斯核标准差控制光照估计的平滑程度 img img.astype(np.float32) 1.0 gaussian cv2.GaussianBlur(img, (0, 0), sigma) gaussian[gaussian 0] 1e-6 retinex np.log(img) - np.log(gaussian) return retinex def multi_scale_retinex(img, sigmas[80, 200, 300]): msr np.zeros_like(img, dtypenp.float32) for sigma in sigmas: msr single_scale_retinex(img, sigma) msr / len(sigmas) return msr def color_restore(msr, img, alpha125.0, beta46.0): MSRCR的色彩恢复抑制灰度化现象 img img.astype(np.float32) 1.0 sum_img img.sum(axis2, keepdimsTrue) color_restoration alpha * (np.log(beta * img) - np.log(sum_img)) return color_restoration * msr这段代码一跑你会立刻发现Retinex的经典通病整体色调会偏灰、偏暗淡。原因是取对数后压缩了动态范围反射分量的数值分布被压缩到了一个很窄的区间看起来就像彩色图像褪了色。因此实际使用时一定要加色彩恢复和最终的线性拉伸也就是代码里MSRCR要做的事。从我的经验来看Retinex系列方法用在“目标检测前处理”这个场景里是称职的因为检测网络关心的通常是纹理、边缘、形状偏色对它影响有限。但如果你是要给用户看增强后的图片那还是得靠深度学习方案。2.3 轻量级SCI网络适合边缘设备的增强方案SCISpiking-Inspired? 不对这里指的是“A Text-Guided...”实际上SCI是“Sparse Coefficient Inference”或者更准确说是“Simple and efficient low-light image enhancement”的缩写2022年CVPR的论文是一个轻量级的低照度增强网络核心卖点是参数少、速度快在CPU上也能实时跑。它的思路和Retinex类似也是分解光照分量和反射分量但用了自校正模块让训练过程不需要成对数据这一点跟Zero-DCE的零参考思路是相通的。SCI最吸引我的地方是它把模型压到了不到300KB在树莓派这种级别的硬件上跑1080P视频还能保持稳定的帧率。如果你有边缘设备部署需求SCI基本是首选。核心结构是三个子网络级联每个子网络负责一个阶段的光照估计和校正。官方开源代码里给了基于PyTorch的训练和推理脚本下载之后改一下数据路径就能跑。实际用起来SCI的处理效果在亮度提升幅度上会稍微保守一些不像Zero-DCE那样激进但它的优势是色彩稳定很少出现局部过曝或颜色洗白的现象。对监控画质要求不是特别苛刻的场景SCI是一个省心的选择。3. 完整复现流程从环境配置到模型推理3.1 环境准备与依赖版本建议跑这些模型环境配置是第一道坎。我踩过最多的坑就是版本冲突。这里给出一套我验证过可以稳定运行的组合conda create -n lowlight python3.8 conda activate lowlight pip install torch1.13.1 torchvision0.14.1 --index-url https://download.pytorch.org/whl/cu117 pip install opencv-python4.8.1.78 numpy1.24.3 matplotlib pillow pip install scikit-image tensorboardPyTorch版本没有硬性要求2.x也能跑但如果你要复现Zero-DCE原版训练代码1.13.1和它配套的torchvision是兼容性最稳的。OpenCV别装太新的版本有些老的Retinex代码用到了cv2.xphoto.create_white_balance_braccon这类接口版本差距太大API会变。numpy必须盯着版本装因为很多老代码里还写着np.float和np.int新版numpy已经把这些属性移除了遇到报错需要手动改成np.float32和np.int32。3.2 用Zero-DCE跑通一张暗光图的完整流程Zero-DCE官方代码仓库里有三个主要脚本lowlight_test.py推理、lowlight_train.py训练、model.py网络定义。你拿到一张测试图最快的跑法是把图片路径配好直接执行lowlight_test.py。但官方脚本有时候会在环境细节上卡人我更推荐你直接读代码把核心推理流程抽出来写一个自己的脚本这样可控性更强。下面是我整理好的推理脚本import torch import cv2 import numpy as np from model import ZeroDCE # 这里导入你下载的model.py def load_and_preprocess(image_path, size256): img cv2.imread(image_path) img cv2.cvtColor(img, cv2.COLOR_BGR2RGB) h, w img.shape[:2] img_resized cv2.resize(img, (size, size)) img_normalized img_resized.astype(np.float32) / 255.0 tensor torch.from_numpy(img_normalized).permute(2, 0, 1).unsqueeze(0) return tensor, img, h, w def enhance(model, tensor): with torch.no_grad(): curve_params model(tensor) enhanced apply_curve(tensor, curve_params) return enhanced if __name__ __main__: model ZeroDCE() model.load_state_dict(torch.load(snapshots/Epoch99.pth, map_locationcpu)) model.eval() tensor, original, h, w load_preprocess(test_images/dark.jpg) enhanced_tensor enhance(model, tensor) enhanced_np enhanced_tensor.squeeze(0).permute(1, 2, 0).numpy() enhanced_np (enhanced_np * 255).clip(0, 255).astype(np.uint8) enhanced_np cv2.resize(enhanced_np, (w, h)) enhanced_bgr cv2.cvtColor(enhanced_np, cv2.COLOR_RGB2BGR) cv2.imwrite(test_images/enhanced.jpg, enhanced_bgr) print(保存成功: test_images/enhanced.jpg)推理流程里的关键参数有三个输入尺寸、曲线迭代次数和模型权重路径。输入尺寸建议固定为256×256原因有两个一是Zero-DCE训练时用的就是这个分辨率输入差异太大会影响曲线参数预测的准确性二是网络本身全卷积不要求特定输入尺寸但太高分辨率会显著增加推理耗时输出提升却有限。曲线迭代次数在网络结构里已经固定为3次这个值是作者调出来的基本上3次已经能让亮度变化收敛继续增加迭代次数效果提升不明显反而会引入更多计算量。这里补充一个实际使用中的小技巧处理完增强图之后可以把它和原图做一个简单的亮度融合比例大概三七开原图3、增强图7这样能在提升暗部的同时保住原图高光区的层次感。摄像头拍到的强光源区域如果被网络过度提亮会变成一片死白融合之后会自然很多。3.3 效果评估别只靠肉眼上指标说话很多同学跑完代码看一眼觉得“挺亮”就认为完事了这样在项目评审或者论文里是站不住脚的。低照度增强的评估一般分成两类全参考评价和无参考评价。全参考评价需要一张“正常光照”的ground truth常用指标是PSNR峰值信噪比和SSIM结构相似性。PSNR越高说明像素级误差越小SSIM越接近1说明结构保持得越好。无参考评价不需要真值常用的包括LOELightness Order Error亮度顺序误差、NIQENatural Image Quality Evaluator和BRISQUE。LOE在Zero-DCE论文里是主要指标用来衡量增强前后图像自然程度的变化值越低越好。下面这个脚本可以计算PSNR和SSIMfrom skimage.metrics import peak_signal_noise_ratio, structural_similarity def calculate_metrics(enhanced_path, reference_path): enhanced cv2.imread(enhanced_path) reference cv2.imread(reference_path) enhanced cv2.resize(enhanced, (reference.shape[1], reference.shape[0])) psnr peak_signal_noise_ratio(reference, enhanced) ssim structural_similarity(reference, enhanced, multichannelTrue) return psnr, ssim注意这个脚本仅在你有成对数据时才有意义。如果你只是拿一张网图做测试没有任何参考图可以用NIQE这类无参考指标或者干脆做一个直观的暗部可见度测试——把增强后的图片转成灰度统计像素值小于30的区域占比对比增强前后的变化。4. 常见问题与排查技巧实录4.1 复现过程中最常见的五个坑第一个坑模型权重下载链接失效。很多学术项目的权重都放在Google Drive上国内下载经常出问题。解决办法是先看GitHub的README里有没有百度网盘链接没有的话去HuggingFace搜同名模型很多热心人会搬运权重。另外为了保险起见我建议你在项目刚发布时就把权重文件保存一份到自己的本地硬盘上别等到要用那天才去下载那会儿链接大概率已经凉了。第二个坑算力不够但是还要跑大模型。Retinexformer这类基于Transformer的方案动辄要几个G的显存很多人的笔记本根本扛不住。两个解决办法一是用Google Colab免费GPU跑推理每天有几个小时的免费额度二是用ONNX把模型转成FP16精度显存占用可以下降一半以上。第三个坑图像色彩发灰或者饱和度下降。这几乎是所有低照度增强模型的通病因为网络优化时主要在看亮度损失颜色一致性做不好。排查方向有两个看训练损失里颜色恒常性损失的权重是不是设得太小了调大10倍再试试看输入图像是不是没有做白平衡可以在预处理里先接一个灰度世界白平衡模块。第四个坑增强之后噪声变大了。低照度图像本身ISO很高暗部噪声严重模型在提亮的同时会同步放大噪声。如果模型支持输入去噪预处理可以先用一个轻量级去噪网络比如FastDVDNet的轻量版或者传统的BM3D算法把噪声压一下再送进增强模型。另一种思路是在网络的损失函数里加一个总变差正则项或者频域损失让输出图像的高频分量不要过激。第五个坑视频增强时画面闪烁。逐帧推理增强的时候相邻两帧的亮度差异可能很大看起来就像画面在“呼吸”。这个问题的根源是模型对每一帧独立预测曲线参数帧间参数不连续。解决办法是给曲线参数做时序滤波比如用指数滑动平均EMA来平滑参数序列或者在视频流上引入光流对齐后的时间一致性损失。后一种方法实现复杂度高但效果最好。4.2 调参经验从效果一般到效果能打的进阶操作如果Zero-DCE默认参数跑出来的效果不理想优先调整这几个方向曝光控制损失的目标亮度值默认是0.6这是作者从大量照片统计出来的中间值。但你处理的图像整体偏暗时直接设成0.6会显得太亮暗部提上来了高光也爆了建议调整到0.45到0.55之间。空间一致性损失权重原版这个损失是防止局部过度增强的但权重过大时会压制暗部提亮的幅度。如果增强后图像看起来不够亮尝试把这个权重降到原来的0.1倍重新训练。输入图像预处理很多暗光图是16位Raw格式转来的直接用8位数据喂给网络会丢失暗部细节。如果能拿到Raw数据先做一次线性亮度映射比如把低于10%分位数的像素线性拉伸再转8位图效果会有质变。这些调参建议没有一个是玄学背后都是对网络优化目标的理解。你动手改了之后多跑几次实验对比指标慢慢就能形成自己的调参手感。4.3 真实项目中的部署建议最后说一点工程上的经验。低照度增强很少作为独立的交付物存在绝大多数时候它是某个视觉系统里的一个前处理模块。这时候你就要考虑整个链路的延迟预算。如果增强模块需要100ms而整个系统要求30FPS那这个方案再“好看”也不能上线。我通常的建议是优先评估轻量模型SCI、Zero-DCE的small版本控制在10ms以内。如果性能瓶颈在CPU推理考虑用NCNN或MNN做模型转换ARM平台上的加速效果非常明显。在算力吃紧的边缘盒子上可以分区域增强——只对亮度低于阈值的图像块做增强其他区域直接透传节省算力的同时避免高亮区域被无谓改动。这些思路不一定适用于所有项目但至少能帮你少走一些弯路。从我个人实际操作的经验来看低照度增强最忌讳“一套方案打天下”。不同环境下退化模式差异很大雨天夜间的噪声特性和晴天室内完全不同光源偏色的情况也千差万别。建议你在项目里先把数据样本收集齐全跑一遍本文提到的几个方案形成基线对比再基于结果选择主模型进行针对性调优。另外再分享一个小技巧增强结果和原图做差分往往能直观暴露出模型在哪些区域“脑补”了不存在的细节这比盯着增强图本身更容易发现问题。学会用这个方法审视输出你会少被很多看似惊艳、实际不可用的效果图迷惑。本文还有配套的精品资源点击获取
返回列表