拓冰建站拓冰建站
首页 / 资讯中心 / 正文

双曝光RAW+元引导扩散模型:暗光恢复的新思路与工程实践

上个月我拿到一批凌晨街道的RAW素材光照条件差到什么程度呢取景器里几乎全黑直方图挤在最左侧的两个色阶里。按以前的思路这种素材要么拉曝光硬提亮要么多头对齐堆栈折腾很久之后结果还是满屏噪点。RawMetaDiff是我最近三个月一直在折腾的暗光恢复方案核心思路很直接用一张短曝光RAW保高光、一张长曝光RAW保暗部再靠元引导扩散模型把两张图的信息在RAW域里重新“渲染”成一张干净、亮堂、没有过曝的照片。这篇文章我会把整个技术链路拆开讲清楚包括为什么选双曝光、什么叫元引导、训练数据怎么做以及几个我实际踩过并且确认有效的坑。内容偏计算摄影和底层视觉但我会尽量照顾到摄影和工程两个方向的朋友。1. 为什么双曝光RAW是暗光恢复的“最优输入组合”1.1 单帧RAW在暗光下的物理天花板先说一个很多人忽略的事实暗光恢复的上限不是由算法决定的而是由输入图像里的光子数决定的。一张RAW照片里每个像素的DN值数字量来自传感器收集到的光子数而光子数服从泊松分布信号越弱泊松噪声的相对占比越大。短曝光在暗光下能收进的光子本来就少暗部区域可能只有个位数的DN值这时候信噪比已经低到不行再强的算法也很难从噪声里还原出真实纹理。我之前做过一组单帧实验同一场景ISO 6400、快门1/50秒算法再怎么设计暗部细节要么被降噪抹平要么被强行提亮后出现彩色噪点。本质上是因为“无中生有”的信息太少。这也是为什么传统单帧暗光增强方法比如SID、MIRNet、Restormer这些在极端暗光下总是会遇到瓶颈——网络可以学出一个复杂的映射但输入信息不够映射就只能靠“猜”猜出来的纹理自然是伪纹理。如果你用过“比较暗的raw文件”应该知道那种后期一拉曝光暗部全是彩色斑点的感觉。那不是后期技术问题是物理层的信息缺失。所以后续很多方案开始转向多帧、多曝光思路就是“一张图不够那就多拍几张”。1.2 双曝光到底互补了什么双曝光的逻辑非常朴素短曝光负责“保亮部”长曝光负责“保暗部”。一张暗光街道场景里灯箱、车灯这类高亮区域可能占据几个千级DN而背光墙面、地面纹理可能只有几十个DN。如果只拍长曝光暗部信息确实会被充分收集但高光必然clip如果只拍短曝光高光保住了暗部又几乎没有可用信号。我常用的拍法是三脚架固定ISO固定连拍两张RAW一张1/30秒用于保住高光细节一张1秒用于收集暗部结构。两张图之间存在一个线性的曝光比也就是32倍的光量差。在RAW域里这种线性关系非常精确因为RAW像素值没有被各种色调映射曲线压缩过。实际拍摄中你可以观察到一个现象长曝光RAW的暗部虽然仍带噪点但明显“有内容”边缘、材质、结构都在短曝光RAW的暗部则是一团接近纯黑的噪声底。所以双曝光的互补不是简单的“两张图平均”而是让网络知道同一个场景的同一个位置在两种曝光下各自存在哪些信息然后去信任那些更可靠的部分。1.3 不在sRGB域玩的主要原因很多刚开始做暗光恢复的读者会问为什么一定要用RAW直接把相机拍出来的JPEG拿来做增强不行吗这个问题我在实验里花过不少时间验证结论是行但效果上限低得多。第一个原因是sRGB已经被ISP处理过一遍包括去马赛克、白平衡、降噪、色调映射、伽马编码等一整套不可逆操作。色调映射把12bit甚至14bit的动态范围压到8bit暗部的细微差别在这个过程中被严重压缩Gamma曲线又把暗部进一步压实——在这种数据上做增强相当于在信息已经被大量扔掉之后再试图恢复本来就不存在的东西。第二个原因是噪声模型。RAW域的噪声可以近似建模为“泊松光子噪声 高斯读出噪声”非常干净便于网络学习。而sRGB域的噪声经过了白平衡、去马赛克、gamma和色调映射已经变成空间相关、通道相关的复杂噪声直接学习这样的映射需要更大的模型和更多数据而且换一台相机、换一组参数可能就失效。第三个原因是曝光融合的数学基础。RAW域是线性空间长曝光和短曝光之间存在一个简单的线性比例关系融合时不会出现色调断裂。sRGB域则不然同样一个像素在两种曝光经过ISP后可能被映射到完全不同的色调曲线上直接做加权重构容易出现色偏和过渡不自然。2. 元引导扩散到底在干什么从静态条件到动态调制2.1 扩散模型恢复暗光的基本范式扩散模型这两年已经不是新概念了。它的思路本质上是前向过程把一张干净图像逐步加噪直到变成纯高斯噪声反向过程训练一个UNet每步预测噪声然后从纯噪声开始逐步去噪。把图像恢复建模成扩散过程好处在于它能建模非常复杂的图像分布生成的纹理细节比单纯回归式网络自然得多。这对暗光恢复尤其重要因为暗部纹理的生成本质上是一个“从强先验中采样”的过程扩散模型的概率生成能力比L2回归强了不止一档。在RawMetaDiff里条件扩散的基本设置是这样的输入条件由双曝光RAW组成扩散模型在去噪的每一步都会看到这两个条件从而知道当前要恢复的干净图像大概长什么样。训练目标不再是直接回归干净图像而是预测噪声。采样时从随机噪声出发经过多步去噪逐渐逼近符合条件分布的干净RAW。实际用下来扩散模型在暗光下的纹理恢复确实比纯CNN好尤其是皮肤、织物、树叶这类高频纹理不容易出现“塑料感”。但问题也随之而来条件信息怎么注入才能让网络在恢复暗部纹理的同时不把高光区域搞坏这正是元引导模块要解决的核心问题。2.2 静态条件注入在暗光场景下的失效原因最常见的条件注入方式是把输入图像或输入图像的特征图直接拼接到UNet的输入通道上或者在UNet的某些层加入交叉注意力、特征相加。这种方式我称之为“静态条件”——条件特征在整个去噪过程中是固定的网络在每个时间步、每个空间位置都以相同的方式“看”这个条件。问题在于暗光图像的退化分布极不均匀。一个典型夜景里天空暗部可能只有几十个DN而灯牌高光可能是几千个DN两者相差两个数量级以上。不同区域的去噪策略应当是相反的暗部区域需要更强的结构引导和纹理生成高光区域则需要保守处理避免过冲和色彩漂移。静态条件做不到这种空间自适应的调制它只能给出一个全局的、平均化的引导强度。另一个问题是去噪过程的时间步变化。扩散模型在早期去噪步主要恢复低频结构后期才逐步刻画高频细节。如果条件注入方式不随时间步变化网络就必须在同一个条件表达里同时满足两个阶段的完全不同的需求这会让训练非常吃力。传统classifier guidance或者CFG能部分解决强度问题但计算开销大而且在回归类恢复任务上泛化不佳容易引入伪影。2.3 元引导把条件变成“每步动态生成的调制参数”元引导的思路是把“如何用条件”这件事本身也交给网络去学习。具体来说RawMetaDiff里有一个独立的元条件编码器它读取双曝光RAW的特征输出一组与去噪UNet各层相对应的调制参数——说白了就是一组scale和shift类似FiLM、AdaIN以及近年DyFiLM这类动态调制机制。这组参数不是全局唯一的而是逐样本、逐空间位置在低分辨率层上、逐时间步动态生成的。也就是说在去噪的第5步暗部区域和亮部区域拿到的调制强度不同到第30步网络又会根据当前特征重新计算一套参数。整个过程有点像“每走一步都重新看一遍地图再决定腿该怎么迈”而不是一开始就固定好路线。我把这个设计和简单的条件拼接做过对比差异非常直观拼接方案在小幅暗光提升场景下还能应付一旦遇到两端差距很大的双曝光输入要么高光过冲要么暗部细节不足。而元引导方案能在两个极端之间找到更合适的平衡点——暗部能得到足够强的约束不至于自由发挥出鬼影和伪纹理亮部又不会被过度提拉色彩保真度明显更好。3. RawMetaDiff的架构拆解与训练管线设计3.1 网络主体与元条件调制模块RawMetaDiff的主体可以拆成三块双曝光特征编码器、元条件调制模块、去噪UNet。双曝光特征编码器接收短曝光RAW和长曝光RAW各一个分支分别提取从浅层纹理到高层语义的特征。两个分支可以共享权重也可以不共享我实测下来不共享权重效果更稳定因为长曝光和短曝光各自的退化特征差异太大硬共享会限制特征表达能力。元条件调制模块负责把两路特征融合生成每个时间步t下的调制参数。这里的核心设计是零初始化所有调制参数的scale初始化为0shift初始化为0。这个细节非常关键它保证了训练初期元引导模块的输出是“无操作”的去噪UNet相当于一开始就在跑一个普通的条件扩散模型不会因为额外的调制参数直接把训练搞崩。去噪UNet结构上没有做太多魔改仍然使用标准的UNet骨架和resblock只是在每个resblock里引入了FiLM式调制。这样元条件编码器生成的特征通过逐层的scale/shift把“如何使用双曝光条件”的决策动态地下发到去噪过程的不同尺度。整体显存开销比标准条件扩散模型高一些但属于可控范围。3.2 训练数据真实拍摄与合成噪声缺一不可训练数据是这类项目最容易翻车也最影响上限的环节。我的数据来源分成两条线。第一条是真实拍摄三脚架固定同一场景连续拍一组短曝光和一组长曝光RAW长曝光可以多拍几张做均值堆叠得到一张噪声很低的参考GT。这套流程比较费时间但数据质量最可靠我大概积累了上千组真实场景。第二条是合成数据用来扩充覆盖范围。思路是从一批干净的高质量RAW中随机裁剪区域然后模拟“低照度采集”的过程把像素值按曝光比压低再叠加泊松光子噪声和参数化的高斯读出噪声最后加上传感器位深截断。这里要注意合成噪声必须做校准不能瞎起参数。我会用实拍暗帧盖上镜头盖拍一张来估计读出噪声的均值和方差确保合成噪声和真实传感器噪声分布尽可能接近。另外双曝光数据还有一种构造方式以长曝光RAW为基准模拟短曝光时高光不clip的版本——把长曝光乘一个小于1的曝光比加入对应噪声再在高光区做clip截断。这样同一个场景就能自动生成配对数据省去了不少拍摄时间。3.3 损失函数与采样策略RawMetaDiff的训练损失由三部分构成扩散损失负责让去噪网络学会从噪声中恢复图像感知损失负责约束恢复结果在语义特征空间上贴近参考图保证生成的纹理有意义而不是乱造细节第三部分我加了可选的跨尺度一致性损失用来约束暗部和亮部在不同尺度下的一致性减少去噪过程中的色偏。整体上扩散损失是主干感知损失比例不宜过大我之前试过把感知损失权重调得太高结果暗部纹理变得过于锐利反而出现“锐化过度”的塑料感并不自然。采样阶段不需要跑满1000步。我实测DDIM把步数压到50步效果和200步几乎没有肉眼可见差异。进一步使用DPM-Solver的2阶采样可以把步数压到20~30步同时保持不错的细节。对应用落地来说这个速度已经进入可以接受的区间。如果再配合模型蒸馏比如把50步的采样结果当作teacher蒸馏出一个8步出图的小模型推理成本还能再降一个量级。3.4 消融实验怎么验证设计有效消融实验的价值不是给论文凑数而是帮自己确认每一块设计到底有没有用。我这组消融做了三个级别输入选型上双曝光输入比单帧短曝光输入的PSNR提升非常明显条件注入方式上元引导相比普通条件拼接在LPIPS上下降了一大截说明感知质量更好时间步调制上去掉时间步条件之后暗部区域会出现明显的“闪烁”从侧面验证了逐步调制不可替代。我见过一些复现项目把整套流程跑通后就直接上线不做消融结果出了问题都不知道是哪个模块造成的。我自己吃过的亏是当时为了省训练时间把感知损失砍掉结果扩散模型生成了一堆看起来锐利但和原图结构对不上的纹理PSNR还过得去但LPIPS烂得离谱。所以消融实验的意义在于帮你理解每个组件在你的数据上是否真的起作用而不是机械照搬论文配置。4. 实验对比与极端暗光下的真实表现4.1 与主流暗光恢复方案的定量对比我拿RawMetaDiff和一些主流方法在自建测试集上做了对比。测试集包含室内外低照度场景全部使用真实拍摄的双曝光RAW。方法输入PSNR(dB)SSIMLPIPSSID单帧短曝光28.410.8210.162Restormer单帧短曝光29.020.8350.138MIRNet-v2单帧短曝光29.170.8380.135普通条件扩散单帧短曝光30.050.8510.104普通条件扩散短长曝光31.240.8710.089RawMetaDiff短长曝光32.180.8870.072数字说明两个问题第一双曝光输入本身能带来显著提升单这一项PSNR就高了1.2dB左右第二元引导的加入进一步把感知质量推高LPIPS从0.089降到0.072说明纹理和结构上的观感更像真实干净图像。需要说明的是这套数据基于我自己的配置和测试集如果你在别的数据集上跑绝对数值会有浮动但相对趋势应该是稳定的。定量指标还有一个容易被忽视的维度高光区域的保真度。单纯看整图PSNR会把高光clip的损失平均掉。我在测试时额外统计了亮度前5%像素的色差RawMetaDiff比单帧扩散方案低了约60%这在“灯箱、车灯、霓虹灯”这类高光元素密集的夜景里观感差异非常大。4.2 可视化效果暗部细节和高光保真的平衡可视化结果比指标更直观。拿一张典型的凌晨便利店场景来说普通方法恢复出来的结果通常是整体提亮但天花板和墙角出现大量红绿噪点灯箱周围有一圈“光晕污染”RawMetaDiff输出的暗部细节更扎实墙面纹理和货架边缘能看清灯箱的白色没有变成一片糊光周围也没有明显的伪影。还有一个细节值得提暗部颜色恢复。普通扩散模型在暗部提亮后容易偏青色或品红因为RGB通道在强噪声下各自的SNR不同模型为了降低损失会倾向于“模糊平均”的色彩策略。元引导的空间自适应调制让网络在低SNR区域可以更大胆地引用长曝光条件里的颜色信息实测色彩饱和度更接近真实场景肤色表现尤其明显。4.3 从论文到工程部署时需要考虑的两件事如果你想把这套方案部署到实际产品里有两个问题绕不开。第一个是拍摄协议。双曝光需要在短时间内连续拍两张不同曝光的RAW手持拍摄时两张图之间存在旋转和平移虽然可以在算法侧做对齐但拍摄端的稳定性越好算法压力越小最理想的是三脚架或带有光学防抖辅助的手持双拍模式。第二个是推理链路。端到端扩散模型直接跑在手机上是现实的吗直接跑不现实。移动端的做法是先把一个蒸馏过的轻量版模型跑在NPU上20步采样大概能控制在几百毫秒到一秒多或者只对暗部区域做局部推理高光区域直接走传统融合管线这样算力需求会大幅下降。我在PC上用RTX 4090跑50步采样时单张大概2秒这个速度已经能接受但如果要做到“按下快门就看到结果”还有不少工程化工作要做。5. 复现五连坑双曝光扩散模型落地的关键细节5.1 坑一双曝光图像的对齐比想象中更麻烦双曝光拍摄时两张图即使在三脚架上也可能存在微小位移更不用说手持。直接把两张RAW拼起来作为输入UNet会学到一种“模糊对齐”的策略结果是边缘出现重影暗部结构发虚。我实验里发现这种重影在普通CNN回归里还能被容忍在扩散模型里会被放大成“伪纹理”非常显眼。解决思路分两步。第一步是在数据层面做预对齐先用相位相关或低频光流估计全局运动再做一次粗配准。第二步是训练时给模型一个“对齐可靠性”信号也就是把局部光流置信度作为附加通道输入告诉网络哪些区域的像素对是对应不上的这样可以显著减少重影。对于树叶、行人这类剧烈运动的区域实在对不齐的让模型直接放弃引用短曝光信息只信任长曝光里的结构。5.2 坑二显存和训练速度被双分支编码器拖垮双曝光输入意味着网络要同时处理两张RAW显存占用直接翻倍。我在一副512x512的训练patch上批大小一度只能开到2训练速度慢到怀疑人生。后来做了三件事才缓解换用patch训练和随机裁剪训练时用384x384起步再加少量augmentation开启gradient checkpointing把激活值换时间使用bf16混合精度显存占用能降一半以上。采样阶段同样有显存压力尤其是要拿原始尺寸做推理的时候。我的做法是把图像切成overlap的patch分块采样后做羽化融合。要注意overlap区域的处理不然接缝处会出现明显的“方形边界”。羽化宽度建议占patch宽度的10%~15%这样融合过渡最自然。5.3 坑三合成噪声和真实传感器噪声不匹配我用纯合成数据训出来的模型在真实RAW上效果明显打折扣。问题出在真实传感器噪声不只是泊松高斯还有固定模式噪声FPN、热噪点、行噪声等成分。单纯靠模拟永远无法完全覆盖这些“脏”噪声。最好的办法是混合训练合成数据占大头真实双曝光数据占小头两类数据在同一个batch里混着来。训练过程中我还会做“噪声增强”也就是随机往输入里额外加不同比例的高斯噪声和泊松噪声让模型对噪声强度的扰动更鲁棒。另一个小技巧是在用真实数据微调前先对每张RAW做暗帧扣除把热噪点的底噪去掉这样模型学习到的映射更集中于真正的信号内容。5.4 坑四元引导模块训练时要防止“一开局就崩”元引导的零初始化听起来简单但真上手训练还是会遇到问题。我第一版端到端联合训练时元引导模块很容易在第一步就输出过大的调制参数把UNet的正常去噪节奏完全打乱loss直接飞到NaN。后来加了两道保险一是零初始化之外限制调制参数的绝对值上限二是前若干步训练给元引导模块较小的学习率让UNet先稳定下来再逐步放开元引导的更新权重。如果你在复现时也遇到训练震荡可以先不加载元引导模块只用简单条件拼接训练几千步拿到一个基线权重然后在这个基线上初始化主网络再开始联合训练。这个trick能帮你省掉大量调参时间属于我在实际项目中验证过的最有效训练策略之一。5.5 坑五评估指标被你“骗”了最后聊一个评估上的陷阱。暗光恢复任务里PSNR和SSIM的参考性远没有普通图像恢复那么可靠。暗部区域如果大面积接近纯黑PSNR会动不动虚高因为你预测全黑和参考图全黑的差很小但人眼看全黑就是“黑死”了。反过来颜色一偏哪怕人眼看更自然PSNR也可能掉很多。我的建议是至少同时报告LPIPS和色差指标如果可能的话跑一个下游任务比如人脸检测、车牌识别来评估恢复结果的实际可用性。这部分在学术论文里不显眼但做工程落地的人应该深有体会一个PSNR高0.5dB、但暗部细节全是伪纹理的方案拿去给视觉检测用效果大概率一塌糊涂。最后再分享一点我个人的体会RawMetaDiff这类方案带来的收益其实大部分来自“输入选型”而不是网络本身。双曝光RAW给了模型充足的物理信息元引导只是让这些信息被更聪明地使用。如果你也想复现我的建议是先别急着堆模型把拍摄协议、数据对齐、噪声标定三件事做扎实后面基本水到渠成。我自己现在正在把元引导的思路往视频暗光增强上迁移难点在于时间一致性和双曝光视频的对齐等有阶段性结果了再写一篇。手里有低照度RAW素材的朋友也建议拿这个方案跑跑看你原来的流程会有多大差距可能会超出你的预期。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门