拍了一组包围曝光的照片回到电脑前,面对三张亮暗截然不同的JPG,你脑子里想的是什么?拉曲线?堆栈平均值?还是直接扔进某个HDR合成软件里让“假HDR”的塑料感替你作决定?我一开始也是这么干的,直到被重影、光晕、灰蒙蒙的画面反复折磨了几次之后,才真正静下心去把“多曝光融合”这几个字的底细摸清楚。这个技术说起来并不复杂,但它背后涉及的权重设计、金字塔分解、色彩空间选择,能让同样一组原片产生完全不同的成片效果。这篇文章就围绕多曝光融合本身展开,把它和HDR色调映射的关系理顺,再把一条可以在本地完整复现的曝光融合流程从头到尾拆开讲清楚,重点关注那些说明书上不会写的参数经验和踩坑经历,适合正在研究计算摄影、图像融合,或者单纯想把夜景大光比照片处理好一点的读者。
1. 动态范围瓶颈:单张照片的宽容度装不下真实光影
要说清楚为什么需要多曝光,得先聊一个摄影里绕不开的概念:动态范围。通俗点讲,动态范围就是相机能同时记录下来的“最亮”和“最暗”之间的跨度。这个跨度用EV(曝光值)或者档来度量,每差一档,亮度就差一倍。人眼在适应某个场景时的动态范围大概有20档左右,而且人眼是“局部适应”的——你盯着亮的地方瞳孔会调整,盯着暗处又会再调整一次,相当于在脑海里自动做了好几张不同曝光的画面再拼起来。而相机传感器呢?主流机型单张RAW能记录的大概在12到15档之间,JPG直出往往还要更少。这就导致了一个很现实的结果:你能“看见”的场景,相机往往拍不下来。
我举个每个人都遇到过的场景。傍晚的城市天际线,天空还有晚霞,地面已经是华灯初上。你用测光表或者相机评价测光对准天空,地面几乎死黑;对准地面路灯,天空又整个过曝成白板。为什么?因为天空和地面的亮度差往往在6到8档开外,单张照片无论如何都装不下。直方图会告诉你最真实的答案:左边顶到柱子的阴影峰,右边顶到墙的高光峰,中间一大段空着,这张照片的信息就是“缺了一大块”。
这时候有两个基本思路。第一个是物理硬扛:上中灰渐变镜把天空压暗,让天空和地面之间的亮度差被滤镜强行拉回传感器能接受的范围。这个方法有效,但局限性也很明显:场景里有高楼、树、山脊等复杂轮廓时,渐变镜的过渡硬边界很容易穿帮。第二个思路就是拍多张:在不同曝光值下拍摄同一场景,每张照片只负责记录它最擅长的亮度区间,暗部细节交给长曝光那张,高光细节交给短曝光那张,然后在后期把几张照片的有用像素“挑”出来合成一张完整的画面。这就是“多曝光”的本质——用多次采样的方式扩展单帧的动态范围。
这个思路听起来很简单,但真正执行起来有个核心问题:到底怎么“挑”像素才能挑得自然、挑得没有接缝?这就派生出了两条技术路线,一条叫HDR合成后色调映射,另一条就是本文的主角——曝光融合。两条路线平时经常被混着叫,但在算法路径上完全不同。理解它们的差异,你才知道什么场景用什么方案最省事、效果最好。
2. 曝光融合与HDR的路线之争:不是一回事,但很多人混着用
先说说经典的多帧HDR合成流程。第一步,拍一组不同曝光的LDR(低动态范围)图像;第二步,利用相机响应函数的逆变换,把这些LDR图像映射到真实的辐射度空间,生成一张高动态范围的HDR图像,通常存储为浮点格式,比如Radiance RGBE;第三步,因为这张HDR图像超出了普通显示器的亮度范围,你需要用色调映射(Tone Mapping)算法把它压回8位的SDR图像。这个过程里,“合并成HDR”和“色调映射”是两个独立步骤,而且必须有一堆参数要调,比如响应曲线标定、映射曲线强度、局部对比度恢复程度等等。调得不好,画面就会呈现那种典型的“HDR感”——边缘光晕、对比度失真、颜色饱和度溢出,噪点被映射成砂砾质感。
曝光融合走的是另一条路。它直接对多张LDR图像做加权混合,根据每个像素在每张图里的“质量”来分配权重。一个像素如果在这张图里曝光良好、细节丰富,那它就在最终结果里拿走更大的比例;如果是那张更亮的图里曝光刚好合适,那它就去那边“借”像素。整个过程不构建浮点HDR辐射度图,也没有色调映射步骤,输入还是那几张8位JPG或者PNG,最后输出还是一张8位图。它的核心算法代表就是Mertens等在2007年发表的那篇曝光融合论文——这篇论文后来被OpenCV封装成了cv2.createMergeMertens(),也是我想在这篇文章里重点展开的东西。
这两条路线到底该怎么选?我给一个比较主观但很实用的判断标准:如果最终产物是做显示器或手机观看的视觉画面,而且场景相对静态、没有需要严格物理光度量化的需求,曝光融合通常比“HDR合成+色调映射”更稳、更快、更容易保持自然色彩;如果你要做光度量测、HDR显示内容制作、或者想基于辐射率做计算机视觉分析,那就老老实实走HDR合成路线,因为融合算法输出的LDR图像里曝光信息已经被“重新编码”了,拿去做定量分析会出问题。下面是两条路线在几个关键维度上的对比。
| 对比维度 | 多帧HDR合成 + 色调映射 | 曝光融合 |
|---|---|---|
| 处理流程 | LDR → 辐照度估计 → HDR → Tone Mapping → SDR | LDR → 权重计算 → 多尺度融合 → SDR |
| 中间数据 | 浮点HDR图,数据量较大 | 无,直接在LDR域操作 |
| 参数调校 | 响应曲线、映射曲线、局部对比度等多个环节 | 权重指数、金字塔层数等少量参数 |
| 视觉风格 | 可控性强,偏戏剧化需专业调校 | 更容易保持原始曝光氛围,风格自然 |
| 计算量 | 相对更重,尤其是局部色调映射 | 轻量,适合实时或低算力部署 |
| 动态对鬼影 | 需要单独做去鬼影对齐 | 同样需要,但融合失败模式更温和 |
| 适用场景 | 极极端光比、定量分析、HDR显示 | 日常空间摄影、手机HDR、快速出片 |
那我这篇文章下文讲的Mertens曝光融合,就是表里右边这条技术路线的标准实现。它有明确的数学骨架,可以直接从论文落到代码里。把它的内部机制和实现细节吃透之后,你再回头处理那些大光比照片,就不会再被“HDR是不是都要调出油画感”这个问题困住了。
3. Mertens曝光融合的机制:权重设计、金字塔分解、重建
曝光融合看起来只是做加权平均,但如果你直接把多张图按权重做逐像素混合会发现一个恼人的问题——融合出来不是一片死灰,就是在明暗交界处出现惨白色的“接缝”或者发虚的伪影。原因很简单:逐像素权重完全没有空间一致性,不同曝光图的权重大小变化太剧烈,尤其在场景边缘和高光轮廓线附近,像素级的权重切换就会导致色彩和亮度的突变。Mertens算法的巧妙之处,就在于引入了两个机制来解决这个问题:一个是从对比度、饱和度、曝光良好度三个维度设计权重,另一个是把普通像素级融合改成拉普拉斯金字塔域的多尺度融合。
3.1 三个决定权重的质量指标
Mertens权重公式的形式是:W(x,y) = C(x,y)^wc * S(x,y)^ws * E(x,y)^we,其中C是对比度、S是饱和度、E是曝光良好度,wc、ws、we是对应的三个指数参数。这三个指标分别干这么几件事:
- 对比度C:一般取灰度图的拉普拉斯滤波器响应或者梯度幅值。对比度高的地方纹理清晰、边缘锐利,天然应该优先从这张图里取像素。比如石墙的纹路、叶脉、建筑窗棂,这些位置如果某张图里对比度高,说明这个亮度区间在这张图里记录得最实在。
- 饱和度S:把RGB三个通道做标准差,离散程度越大表示颜色越饱和。常见做法是
(max(R,G,B)-min(R,G,B))或者各通道标准差的均值。彩色场景中,颜色浓郁的地方说明感光元件得到了足够的颜色信噪比,很少会出现“彩色噪点淹没颜色细节”的问题。 - 曝光良好度E:衡量一个像素离中间灰(值域中点0.5)有多近,通常用
exp(-(I-0.5)^2/(2σ^2))的高斯函数来描述,σ一般取0.2。为什么要离中间灰近?因为太暗的区域噪声大、色偏严重,太亮的区域高光容易剪切掉信息,而0.5附近的曝光对绝大多数相机来说都处于线性感光最理想、信噪比最高的区间。
把三个指标连乘起来,每张图在每个像素位置就有一个权重值。这三个指数wc、ws、we的取值决定了你更看重细节、色彩还是“正常曝光”。默认配置是wc=1、ws=1、we=1,但实际使用中有不少调优空间,我下面会专门讲。
3.2 权重图必须先归一化和平滑
计算完每张图的权重图之后,不能直接拿去做融合。你需要把三个权重归一化,让每个像素位置的权重总和为1。归一化公式是W_k'(x,y) = W_k(x,y) / Σ_j W_j(x,y),这里的j遍历所有输入曝光图。如果不做归一化,结果会被权重大小牵鼻子走,亮的图因为曝光良好度天然高,可能直接带偏整张画面的平均亮度,这显然不是我们想要的。
更关键的一步是:归一化后的权重图需要做低通滤波或者下采样平滑,让权重在空间上缓慢变化。这个操作的意义在于:普通像素级的权重切换太剧烈,但如果我们先把每一层的权重图降采样成低分辨率版,再逐层融合,就相当于强制权重在一个较大的空间范围内保持相对稳定,从而避免权重的生硬跳跃。这也是为什么Mertens算法要用金字塔实现多尺度融合的核心原因——金字塔天然给了你一层比一层更平滑的权重表示。
3.3 金字塔为什么能消除融合伪影
要理解金字塔融合的价值,先分清两个概念:高斯金字塔和拉普拉斯金字塔。
高斯金字塔比较好理解:把原图连续做高斯模糊加下采样,得到一组分辨率逐层减半的图像序列。每一层都是上一层的“模糊缩略图”,保留的是大尺度的低频结构信息。而拉普拉斯金字塔其实是相邻两层高斯金字塔之间的差值:第i层的拉普拉斯图像等于第i层高斯图像减去上采样后的第i+1层高斯图像。这么一减,拉普拉斯金字塔的每一层就只保留该尺度下的高频细节——比如边缘、纹理、线条,而不携带大尺度的整体亮度信息。
融合过程是这样的:对每一张输入曝光图,都计算其拉普拉斯金字塔;对每一张对应的权重图,计算其高斯金字塔。然后在每一层金字塔上,先把该层的所有拉普拉斯图像做权重加权融合,得到该层的融合拉普拉斯图像;最后从最顶层开始,逐层把融合结果上采样并与下一层细节累加,重建出最终的融合图像。
这相当于把“挑选像素”的任务分解到了多个尺度。大尺度的亮度过渡由金字塔高层决定,小尺度的细节过渡由金字塔低层决定。即使底层权重变化剧烈,因为高层已经确定了平滑的亮度基调,重建出来的图像也不会出现明显的断层或鬼影边缘。这个“多尺度重建”就是曝光融合比逐像素加权稳定得多的关键原因。
3.4 为什么这套机制在实际场景里就是稳
这套流程有一个现实好处:即使某一张曝光图的某个区域权重非常高,由于融合发生在金字塔不同尺度上,高权重区域在多个尺度上保持了连续渐变,最终输出的接缝是不可见的。换句话说,拉普拉斯金字塔的“可逆重建”特性保证了图像在亮度和颜色上不会有累计误差。更重要的是,整个过程没有经过浮点HDR辐射度编码,也没有色调映射曲线,所以颜色自然度保留得特别好。我经常跟朋友讲,曝光融合出来的片子有一种“所见即所得”的平和感,而HDR色调映射则更像“电影幕后调色师的手笔”,两种风格各有拥趸,但融合这种风格在建筑摄影和自然风光里更吃香。
4. 用Python和OpenCV手写一个多曝光融合工具
理论讲清楚之后,落到代码才是关键。我平时用Python比较多,OpenCV提供了完整的金字塔接口和MergeMertens封装,但我建议你至少自己手写一遍核心的权重和金字塔重建流程,因为只有亲手拆过一遍,你才能理解那些参数的物理含义,也才能在出问题时知道该调哪里。
4.1 环境准备与输入数据
依赖非常简单:opencv-python、numpy,如果要读入RAW可以先转成线性TIFF再做融合。拍摄输入建议使用三脚架,固定机位拍摄一连串不同曝光值的照片。如果你用相机自带AEB包围曝光功能,常见的是±1EV、±2EV三张或五张;如果手动拍,我建议至少拍三张:一张保证高光不溢出但暗部死黑,一张保证中间调正常,一张保证暗部有细节但高光彻底过曝。这样的“高-中-低”三张组合已经能让融合结果有足够的信息量。
读图的代码很简单:
import cv2 import numpy as np def load_images(paths): images = [] for p in paths: img = cv2.imread(p, cv2.IMREAD_COLOR) if img is None: raise FileNotFoundError(f"无法读取 {p}") # 统一转成float32,数值范围[0,1] img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB).astype(np.float32) / 255.0 images.append(img) return images有一个细节值得注意:cv2.imread读进来的是BGR通道顺序,而我们在做色彩相关计算时最好是在RGB顺序下进行,尤其是要计算饱和度和做Lab转换的时候。代码里先转成RGB再熬成float,后面所有计算都会更顺手。
4.2 权重图计算的实现
接下来实现三个权重指标。对比度我用拉普拉斯算子的响应绝对值,饱和度我用RGB三通道的标准差,曝光良好度用高斯函数评价距离0.5(中间灰)的程度。实现如下:
def compute_weight_maps(images, wc=1.0, ws=1.0, we=1.0, sigma=0.2): weight_maps = [] for img in images: gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY) # 对比度:拉普拉斯响应的绝对值 contrast = cv2.Laplacian(gray, cv2.CV_32F) contrast = np.abs(contrast) # 饱和度:通道标准差 mean = np.mean(img, axis=2, keepdims=True) sat = np.sqrt(np.mean((img - mean) ** 2, axis=2)) # 曝光良好度:离中间灰越近权重越大 expo = np.exp(-(gray - 0.5) ** 2 / (2 * sigma ** 2)) weight = (contrast ** wc) * (sat ** ws) * (expo ** we) weight = np.clip(weight, 1e-12, None) # 防止0权重的log问题 weight_maps.append(weight) # 归一化 weight_sum = np.sum(weight_maps, axis=0) weight_maps = [w / weight_sum for w in weight_maps] return weight_maps这里有一个常见误区:拉普拉斯响应会有一部分负值区域,如果你直接用负值结果参与乘方运算,会导致在高频细节区域权重反而偏低或出现负数,进而让权重图出现“边缘反转”。我的处理习惯是先取绝对值再用。这个细节很小,但直接决定你的权重图会不会在边缘处出现一圈“暗边”。
4.3 金字塔分解与融合重建
接下来是金字塔。高斯金字塔的构造,我用OpenCV的pyrDown逐层下采样;拉普拉斯金字塔则是每层高斯图与上采样后的上一层做差。为了控制代码长度,下面是一个紧凑但完整的实现:
def gaussian_pyramid(img, levels): gp = [img] for _ in range(levels - 1): gp.append(cv2.pyrDown(gp[-1])) return gp def laplacian_pyramid(img, levels): gp = gaussian_pyramid(img, levels) lp = [] for i in range(levels - 1): expanded = cv2.pyrUp(gp[i + 1]) if expanded.shape[0] > gp[i].shape[0] or expanded.shape[1] > gp[i].shape[1]: expanded = expanded[:gp[i].shape[0], :gp[i].shape[1]] lp.append(gp[i] - expanded) lp.append(gp[-1]) # 顶层直接用高斯残差 return lp def reconstruct_from_laplacian(lp): levels = len(lp) img = lp[-1] for i in range(levels - 2, -1, -1): img = cv2.pyrUp(img) if img.shape[0] > lp[i].shape[0] or img.shape[1] > lp[i].shape[1]: img = img[:lp[i].shape[0], :lp[i].shape[1]] img = cv2.add(img, lp[i]) return imgpyrDown和pyrUp在尺寸不是严格的2的倍数时会出现一点尺寸偏差,所以我在代码里做了裁切处理。这个坑如果不处理,重建出来的图像会有一圈像素错位,边缘发虚,很多人最后查了半天才发现是尺寸没对齐。
现在把权重图也转成高斯金字塔,然后逐层融合拉普拉斯金字塔:
def fuse_images(images, levels=5, wc=1.0, ws=1.0, we=1.0): weight_maps = compute_weight_maps(images, wc=wc, ws=ws, we=we) # 对每张图,构建权重高斯金字塔和图像拉普拉斯金字塔 fused_pyr = None for idx, img in enumerate(images): w = weight_maps[idx] wp = gaussian_pyramid(w, levels) lp = laplacian_pyramid(img, levels) if fused_pyr is None: fused_pyr = [np.zeros_like(l) for l in lp] for i in range(levels): fused_pyr[i] += lp[i] * wp[i] result = reconstruct_from_laplacian(fused_pyr) return np.clip(result, 0.0, 1.0)这段实现的核心逻辑就两件事:输入图像的拉普拉斯金字塔乘上权重图的高斯金字塔,逐层累加;然后用重建函数把累加后的金字塔恢复成图像。为什么权重要用高斯金字塔,而图像要用拉普拉斯金字塔?这个问题在3.3讲过了——权重需要平滑的低频信息来约束大尺度趋势,而图像需要把高频细节分离出来单独加权,这样才能避免细节处的权重突变产生伪影。
4.4 参数调节的真实经验
代码跑通之后,你会发现三个参数对成片质量的影响非常直观。wc(对比度指数)调大,画面细节更锐利,边缘更突出,但调过头会出现“嵌边”感,像过度锐化的那种白边;ws(饱和度指数)调大,色彩更浓郁,但暗部容易发灰,因为暗部本来饱和度就低,强行提权重反而得不偿失;we(曝光良好度指数)调大,画面更偏向“标准曝光平均”,但过大的we会让高光和阴影区域落在权重极低的位置,画面变平,缺少层次。
我个人的默认参数组合是这样的:常规场景wc=1.0, ws=1.0, we=1.0完全够用;遇到“天空高光快溢出+地面较暗”的日落场景,把we提到1.2左右,让曝光合适的区域拿更多权重;遇到纹理复杂的场景比如树林、砖墙,把wc提到1.2到1.5,细节会更扎实。金字塔层数levels,对于2000万像素左右的照片,5层足够;小图用4层,大尺寸长图可以用6层,但再往上收益就很微弱了,计算量反而明显增加。
5. 实践路上的坑:鬼影、光晕、白平衡与对齐
读到这里,你应该已经能跑出一个基本可用的多曝光融合工具了。但真拿它去处理现实拍摄的素材,马上就会撞上几个绕不开的“现实毒打”:动态物体的重影、高光区域的晕影、多帧之间的白平衡漂移,以及手持拍摄带来的普遍对齐误差。这些坑如果不处理,再完美的金字塔重建也救不回来。
5.1 场景静止是前提,鬼影到底怎么破
曝光融合非常依赖“场景内容不变”这个前提。你按了三张包围曝光,中间任何一张里的树叶被风吹动了、汽车开过去了、行人走路了,融合结果里就会出现半透明的“幽灵”——那辆汽车在画面里既是这个位置又是那个位置,树叶的边缘模糊成一片墨迹。这就是最常见的鬼影(ghosting)现象。
最直接的办法是物理上规避:拍摄时优先选无风、无车流、人少的时段,用三脚架,快门尽量用遥控器或者倒计时。但现实里总有避不开的情况。软件层面最简单的去鬼影思路是做一致性检测:计算每张图每个像素点相对参考帧(一般选中间曝光那张)的偏差,如果偏差超过一定阈值,就认为这个像素属于动态区域,这部分像素不再米用该图的权重,直接让其他帧来决定。用极值法的思路是在每个像素位置取所有帧的最小值和最大值,如果一个像素在某帧里超出了这个区间,说明它是异常像素,就把它的权重压低。
def detect_motion_masks(images, ref_idx=1, thresh=0.1): ref = images[ref_idx] masks = [] for img in images: diff = cv2.absdiff(cv2.cvtColor(img, cv2.COLOR_RGB2GRAY), cv2.cvtColor(ref, cv2.COLOR_RGB2GRAY)) masks.append((diff < thresh).astype(np.float32)) return masks把masks乘到对应权重图上做软掩码再归一化,就能在融合时自动忽略动态区域。注意掩码需要做一次高斯模糊,比如cv2.GaussianBlur(mask, (5,5), 0),让它从0到1之间平滑过渡,否则融合边界又会多出一条硬边。这个方案做不到商业级去鬼影水准,但对付树叶晃动和轻微人流足够了。
5.2 大光晕从哪里来:权重半径与金字塔层数
打开融合结果,在高光边缘附近出现一圈“发亮的边”,看起来像是物体周围涂抹了荧光,这是光晕伪影(halo)。光晕的来源主要有两个:一个来自权重图在空间上的急剧变化,另一个来自金字塔层数不足导致的高频信息混叠。如果你在权重图中使用了过大的高斯模糊半径,会让高权重的暗部区域“扩散”到高光区域的开外,重建后就会形成亮边。相反,如果金字塔层数太浅,低频结构没法被充分分离,融合时局部区域会被某一帧的大权重直接接管,边缘就会产生过冲。
解决光晕的实践要领是:权重图的模糊半径不要超过图像短边尺寸的1/50左右,金字塔层数至少4层,对于2000万像素级别的一般我会用5层甚至6层。另外,如果你发现光晕主要集中在明暗边界处,试着提高wc的权重让边缘细节更多地由对比度主导,这能在一定程度上把光晕压制下去。
5.3 白平衡漂移:三张图变成三种色调
多曝光拍摄有个隐蔽问题——如果相机白平衡设定在自动档,每次曝光之间相机会自适应调整白平衡,尤其隔着玻璃拍夜景或者拍夕阳时,三张图的色温可能完全不同。融合完成之后,你很可能得到一张色彩发灰、偏青或者偏品红的图。这种色偏不是融合算法的锅,而是输入数据本身不一致。
我的做法是在融合之前先做白平衡归一化。简单粗暴但有效的方法:选参考帧(一般选中间曝光那张),计算每张图在灰色区域的平均RGB比例,然后把其他帧的通道按比例缩放到与参考帧一致。如果不想这么麻烦,更稳妥的方式是拍RAW,在转换阶段统一白平衡后再进融合流程。RAW工作流下,你完全可以让三张图的白平衡一模一样,色彩一致性从源头解决。
5.4 手持拍摄怎么办:对齐是融合的前置条件
三角架不是每次都有的,手持拍三张包围曝光,每张之间手会有轻微位移。如果没有对齐直接融合,结果必然发虚,细节处还有叠影。简单有效的对齐方案有几种:相位相关可以估计全局平移,但不能处理旋转和透视变化;ECC增强相关系数可以处理小角度旋转加平移;对于更复杂的手持抖动,光流对齐是更普适的选择。
我平时最快的方法是:把三张图缩成小尺寸,比如宽400像素,然后对亮度分量用cv2.findTransformECC算一个2x3的仿射变换矩阵,再把矩阵放大回原图尺寸做cv2.warpAffine。跑一次实测下来,普通手持轻微抖动基本都能对齐到亚像素级,足以撑起曝光融合。如果抖动太大,比如你按快门时人还晃了一下,仿射变换就顶不住了,那种情况下除非上光流,否则还是建议老老实实上三脚架。
5.5 色彩空间:RGB直接融合,色偏警告
另一个容易被忽略的坑在色彩空间。如果你直接对RGB三通道做拉普拉斯金字塔融合,偶尔会在饱和色区域发现色彩从红过渡到紫、从蓝过渡到绿之类的异常。原因很简单:RGB三个通道之间高度相关,融合时三通道各自的权重并不一致,亮度差异被三通道独立放大,色彩平衡就被打破了。
我建议的替代方案是把图像从RGB转到Lab空间,L通道代表亮度,a和b代表色度。融合只在L通道上做,a、b通道直接取参考帧的值(或者做一个简单的线性混合)。这样亮度细节完全由融合金字塔决定,色彩则保持参考帧的一致性,色偏问题基本可以避免。代价是色彩层次会略“平”一点,但对绝大多数场景来说这个取舍非常划算。
6. 曝光融合和现代成像的配合:手机HDR、RAW工作流与选型思路
讲到这儿,你应该已经发现多曝光融合其实不只是电脑上一门“后期技术”,它完整地映射了现代成像系统里各种“多帧增强”的底层逻辑。手机上的夜景模式、HDR模式,本质上就是实时版的曝光融合:多帧快速连拍、对齐、权重融合,然后在ISP里做后续调色。虽然手机端还有去模糊、超分辨率等更多花活,但金字塔多尺度加权融合这个框架依然是核心。
如果你经常拍RAW,我强烈建议你试试把RAW先转成线性TIFF,再做曝光融合。这么做的好处有两个:第一,线性色彩空间下权重计算更接近物理一致,高光区域不再因为JPG的S曲线而被压缩失真;第二,RGB的数值范围在融合时能得到更好的物理意义,实验结果通常更细腻。代价是线性图直方图看起来“发灰发暗”,这是正常的,融合完成后加一条适当的色调曲线做对比度恢复就好。
关于“曝光融合”和“多帧HDR合成”的选型,我的最后建议是这样一张决策表:
- 最终用途是发社交媒体、冲洗打印、做普通修图素材 → 直接曝光融合,流程短、风格自然、不容易出假HDR效果。
- 最终用途是做高动态范围显示器内容、光度量测、基于辐射度图像做视觉算法分析 → 走“HDR合成+色调映射”或直接存储浮点HDR格式。
- 场景里有大量树叶、水面反光、人流等复杂动态 → 优先考虑带运动掩码的曝光融合,或者干脆用更重的去鬼影流程。
- 合成速度很重要,比如移动端处理 → 降低金字塔层数、简化权重层次,用轻量化融合框架。
我最近在做的一个模拟项目里,需要把一组五张包围曝光的户外场景融合成一张同时保留高光细节和阴影层次的教学图片素材。一开始用的就是cv2.createMergeMertens(),三分钟出片,效果已经不错;后来手动把权重里的对比度指数换成拉普拉斯希伯尔特空间度量的变体,细节保留和边缘自然度又上了一个小台阶。这些细节差异在像素级对比上也许不明显,但在放大检视时非常能说明问题。曝光融合是个很成熟的领域,但它留给个人去调校和玩花样的空间依然大得很——把基础原理吃透之后,上面说的权重设计、金字塔层数、色彩空间选择,每一项都可以往里继续深挖。对做计算摄影或者图像融合方向的人来说,这套方法值得花一个下午从头到尾亲手实现一遍。