2. 全局 Tone Mapping 算子:Gamma 校正、对数映射、指数映射与优缺点分析
全局 Tone Mapping,说白了就是用一个统一的映射函数,把整张图的像素从高动态范围压到低动态范围。它不考虑像素周围的邻居,一视同仁。好处是算法简单、速度快,坏处嘛……细节容易丢。我最早接触 HDR 显示时,用的就是全局算子,那时候硬件性能有限,没得选。
2.1 Gamma 校正:最基础的映射
Gamma 校正其实不是为 HDR 发明的。它最早是为了补偿 CRT 显示器的非线性响应。但后来发现,这东西天然就能做动态范围压缩。
公式很简单:
V_out = V_in ^ (1 / gamma)
其中 V_in 是归一化到 [0,1] 的输入亮度,gamma 是一个可调参数。gamma 值越大,暗部提亮越明显;gamma 值越小,整体对比度越高。
关键参数:
- gamma = 2.2:标准 sRGB 显示曲线,适合直接输出到普通显示器
- gamma = 1.0:线性映射,不做任何压缩
- gamma > 2.2:暗部细节更亮,但整体发灰
- gamma < 2.2:对比度增强,暗部更暗
我在项目中遇到过一个问题:用 Gamma 2.2 映射 HDR 夜景时,路灯周围的光晕被压得太暗,看起来像没处理过。后来我把 gamma 调到 1.8,暗部细节才出来。嗯,这里要注意——Gamma 校正对暗部敏感,但对亮部的压缩能力有限。
我的调试习惯:先固定 gamma=2.2 看整体效果,再根据暗部细节需求微调。如果暗部太黑,降低 gamma;如果亮部过曝,考虑换用对数映射。
2.2 对数映射:更自然的压缩
人眼对亮度的感知是对数的。所以对数映射在视觉上更自然。公式是:
V_out = log(1 + V_in * scale) / log(1 + scale)
scale 参数控制压缩强度。scale 越大,亮部压缩越狠;scale 越小,映射越接近线性。
我曾经用对数映射处理过一张日出场景的 HDR 图。太阳区域亮度是 10000 cd/m²,阴影区域只有 5 cd/m²。用 Gamma 校正,太阳周围一片惨白;换成对数映射,云层纹理就出来了。
| 参数 | 效果 | 适用场景 |
|---|---|---|
| scale = 1 | 弱压缩,接近线性 | 低动态范围场景 |
| scale = 10 | 中等压缩 | 一般 HDR 场景 |
| scale = 100 | 强压缩,亮部细节保留好 | 高动态范围场景(如逆光) |
避坑指南:我曾经把 scale 设到 1000,结果整张图灰蒙蒙的,像蒙了一层雾。对数映射的副作用是降低全局对比度,scale 太大时尤其明显。建议 scale 不超过 100。
2.3 指数映射:另一种思路
指数映射和对数映射正好相反。它用指数函数做压缩:
V_out = 1 - exp(-V_in * scale)
这个公式的好处是:输入无限大时,输出趋近于 1。所以它永远不会过曝。但代价是——暗部细节丢失严重。
你想想看,指数函数在输入接近 0 时斜率很大,稍微一点亮度变化就被放大;但输入大了之后,斜率趋近于 0,亮部细节几乎被抹平。所以指数映射适合亮部细节不重要、但需要防止过曝的场景。
三种映射的对比:
- Gamma 校正:暗部好,亮部一般,计算最快
- 对数映射:亮部好,暗部一般,视觉最自然
- 指数映射:永不过曝,但暗部和亮部细节都有限
2.4 全局算子的优缺点分析
做了这么多年图像处理,我总结一下全局算子的优缺点。先说优点:
- 速度快:每个像素独立计算,可以并行。在 GPU 上跑,一张 4K 图也就几毫秒。
- 实现简单:三五行代码就能搞定,适合嵌入式设备或实时渲染。
- 参数少:通常就 1-2 个参数,调试成本低。
缺点也很明显:
- 细节丢失:全局映射不考虑局部对比度,亮区和暗区的细节容易被压平。
- 光晕效应:高亮区域边缘会出现不自然的亮边,因为映射函数是非线性的。
- 适应性差:同一个参数无法同时适应场景中的亮部和暗部。调好亮部,暗部就废了;调好暗部,亮部又过曝。
我记得有一次做车载摄像头 HDR 处理,用全局对数映射。隧道入口的场景,外面阳光明媚,里面漆黑一片。全局映射要么外面过曝,要么里面死黑。最后不得不换成局部算子。所以全局算子适合动态范围不太极端的场景,比如室内、阴天、黄昏。
我的建议:如果你刚开始学 Tone Mapping,先从全局算子入手。Gamma 校正打基础,对数映射做主力,指数映射当备选。等理解了这些,再去看局部算子会轻松很多。