4. 色调映射基础:全局映射与局部映射的噪声特性

说到色调映射,很多初学者第一反应就是「把HDR图像压到SDR显示出来」。这个说法没错,但太笼统了。真正决定一张HDR照片最终观感的,是你选择用全局映射还是局部映射。这两种方法在噪声处理上,完全是两条不同的路。

我个人习惯把全局映射比作「一刀切」,局部映射则是「看人下菜碟」。你想想看,一刀切简单粗暴,但容易伤到不该伤的地方;看人下菜碟精细灵活,可稍不留神就会弄巧成拙。噪声问题,恰恰就藏在这两种策略的取舍之间。

4.1 全局映射:简单但「一视同仁」

全局映射的核心思路,是对整张图像的所有像素使用同一个映射曲线。最常见的例子就是 Reinhard 全局算子:

// Reinhard 全局色调映射
float3 ReinhardGlobal(float3 hdrColor, float keyValue) {
    float luminance = dot(hdrColor, float3(0.2126, 0.7152, 0.0722));
    float scaledLum = luminance * keyValue;
    float mappedLum = scaledLum / (1.0 + scaledLum);
    return hdrColor * (mappedLum / luminance);
}

这段代码看起来干净利落,对吧?但我在项目中遇到过一个问题:当场景中存在大面积暗部区域时,全局映射会把暗部的噪声同等放大。因为映射曲线对低亮度区域的增益是固定的,噪声信号和真实细节信号被一视同仁地拉伸了。

关键点: 全局映射的噪声特性是「均匀放大」。暗部噪声和亮部细节使用同一套增益规则,导致暗部信噪比急剧下降。

4.2 全局映射的噪声放大模型

我们可以用一个简单的数学模型来理解这个问题。假设输入亮度为 L,映射后的亮度为 L',那么噪声的传递可以表示为:

σ(L') ≈ |dL'/dL| × σ(L)

其中 σ 表示噪声标准差,|dL'/dL| 是映射曲线的导数(即局部增益)。对于 Reinhard 算子,导数在低亮度区域接近 1.0,在高亮度区域趋近于 0。这意味着:

  • 暗部区域: 增益 ≈ 1.0,噪声被完整保留甚至放大
  • 中间调区域: 增益适中,噪声与细节的平衡尚可
  • 高亮区域: 增益趋近于 0,噪声被压缩,但细节也可能丢失

说白了,全局映射在暗部区域「不干活」——它没有区分噪声和细节的能力。我曾经调试过一段夜景 HDR 视频,全局映射出来的结果,暗部全是颗粒感,像蒙了一层砂纸。

4.3 局部映射:精细但「顾此失彼」

局部映射的思路就聪明多了。它根据每个像素周围邻域的亮度分布,动态调整映射参数。经典的 Durand 双边滤波分解法就是代表:

// Durand 局部色调映射(简化版)
float3 DurandLocal(float3 hdrColor, float sigmaSpatial, float sigmaRange) {
    float luminance = dot(hdrColor, float3(0.2126, 0.7152, 0.0722));
    float baseLayer = BilateralFilter(luminance, sigmaSpatial, sigmaRange);
    float detailLayer = luminance / baseLayer;
    float compressedBase = ReinhardGlobal(baseLayer, 0.18);
    return hdrColor * (compressedBase * detailLayer / luminance);
}

局部映射把图像分解成基础层(大尺度结构)和细节层(小尺度纹理)。基础层做压缩,细节层保留。听起来完美,对吧?但坑就在这里。

注意: 局部映射在分解过程中,噪声会被错误地归类到「细节层」中。结果就是:基础层压缩了,但细节层里的噪声被完整保留甚至增强,最终出现「噪声光晕」或「梯度反转」。

我记得有一次处理一张室内 HDR 照片,窗户边缘出现了明显的「鬼影」——其实就是双边滤波把窗框附近的噪声当成了细节,导致映射后出现了不自然的亮边。嗯,从那以后我再也不敢盲目调大双边滤波的范围参数了。

4.4 两种映射的噪声特性对比

我把两种方法的噪声特性整理成了一张表,方便你对照:

对比维度 全局映射 局部映射
噪声放大模式 均匀放大,暗部最严重 非均匀放大,集中在边缘和纹理区域
噪声空间分布 全图一致 集中在分解边界附近
细节保留能力 弱,细节随亮度压缩而丢失 强,但噪声也被当作细节保留
计算复杂度 低,单次查表即可 高,需要滤波和分解
典型伪影 暗部颗粒感、整体发灰 光晕、梯度反转、噪声纹理化

你可能会问:「那到底该用哪个?」我的建议是:先看场景,再看算力。如果场景光照均匀、动态范围不大,全局映射完全够用,噪声问题也不明显。但如果场景有强烈的明暗对比(比如逆光、夜景),局部映射的细节优势就体现出来了——代价是你必须额外处理噪声。

4.5 知识结构图:全局 vs 局部映射的噪声传导路径

下面这张图展示了两种映射方法从输入到输出的噪声传导路径。我建议你仔细看看「噪声分流」这个环节——它是局部映射噪声问题的根源。

全局映射 vs 局部映射:噪声传导路径 HDR 输入图像 全局映射:统一映射曲线 噪声均匀放大(暗部增益≈1.0) 输出:暗部颗粒感 局部映射:双边滤波分解 基础层 细节层 压缩后基础层 噪声+细节混合 输出:边缘光晕 + 噪声纹理化 ⚠ 噪声分流点 噪声被错误归入细节层
我的经验: 如果你必须用局部映射,建议在分解之前先做一次轻量级的去噪。我常用的做法是:在双边滤波之前,对亮度通道做一次 3×3 的中值滤波。这样能有效减少噪声被错误归入细节层的概率,而且对边缘的模糊影响很小。

4.6 实战建议:如何根据噪声特性选择映射策略

说了这么多理论,最后给几条可以直接用的建议:

  1. 低噪声场景(ISO ≤ 400,良好光照): 全局映射完全够用。用 Reinhard 或简单的对数映射,速度快,噪声问题不明显。
  2. 中等噪声场景(ISO 800-1600,室内/黄昏): 优先考虑局部映射,但必须配合去噪预处理。我建议用 Durand 分解 + 细节层阈值裁剪——把低于某个幅度的细节视为噪声,直接丢弃。
  3. 高噪声场景(ISO ≥ 3200,夜景): 别用纯局部映射了,你会后悔的。改用自适应全局映射:根据局部亮度统计动态调整映射曲线参数,但保持全局映射的结构。这样既有局部映射的适应性,又避免了分解带来的噪声放大。