3、局部 Tone Mapping 算子:基于 Retinex 理论、双边滤波分解、局部自适应映射、光晕伪影问题
全局映射搞定了整体亮度,但细节呢?
我刚开始做 HDR 显示时,发现全局曲线一拉,暗部细节是亮了,可亮部纹理却糊成一片。说白了,全局映射是「一刀切」——它不管像素周围是什么情况。于是我们得请出局部算子。
3.1 Retinex 理论:把光照和反射拆开
局部映射的核心思想,其实来自一个很老的视觉理论——Retinex。Land 在 70 年代提出:人眼看到的图像 = 光照 × 反射。
- 光照分量(Illumination):场景中的光源分布,变化缓慢,低频。
- 反射分量(Reflectance):物体本身的纹理和颜色,高频细节。
我们做 Tone Mapping,本质上就是压缩光照,保留反射。我在项目中遇到过一种情况:直接用高斯模糊提取光照,结果边缘全糊了,光晕严重。为什么?因为高斯滤波不分边界,把边缘也当成了光照。
核心公式:
I(x, y) = L(x, y) × R(x, y)
取对数后:log(I) = log(L) + log(R)
我们只需要压缩 log(L),保留 log(R)。
3.2 双边滤波分解:边缘保留的关键
要避免光晕,就得用边缘保留滤波。双边滤波(Bilateral Filter)就是最经典的选择。
它比高斯滤波多了一个「值域权重」——不仅看空间距离,还看像素值差异。边缘两侧的像素值差异大,权重就低,所以边缘不会被模糊。
| 滤波方式 | 空间权重 | 值域权重 | 边缘保留 | 光晕风险 |
|---|---|---|---|---|
| 高斯滤波 | ✓ | ✗ | ✗ | 高 |
| 双边滤波 | ✓ | ✓ | ✓ | 低 |
| 引导滤波 | ✓ | ✓(线性) | ✓ | 极低 |
我个人习惯用双边滤波做基础层提取。参数有两个:σs(空间标准差)和 σr(值域标准差)。σs 控制模糊范围,σr 控制边缘敏感度。
调参经验:
σr 设得太小,滤波效果弱,光照分量不干净;设得太大,边缘保留失效,光晕又回来了。我一般从 0.1 开始调(归一化后),配合图像内容微调。
3.3 局部自适应映射:细节层单独处理
分解完成后,我们得到:
- 基础层(Base Layer):光照分量,低频,需要压缩。
- 细节层(Detail Layer):反射分量,高频,需要增强或保留。
映射策略很简单:
- 对基础层做全局压缩(比如用前面的 Reinhard 或对数映射)。
- 细节层乘以一个增益系数(通常 > 1),补偿压缩后的细节损失。
- 合成:输出 = 压缩后的基础层 × 细节层。
// 伪代码示例
float sigma_s = 16.0f; // 空间半径
float sigma_r = 0.1f; // 值域标准差
Mat base, detail;
bilateralFilter(logImage, base, -1, sigma_r, sigma_s);
detail = logImage - base;
// 压缩基础层
Mat compressedBase = reinhardToneMap(base);
// 增强细节
float detailGain = 1.5f;
Mat output = exp(compressedBase + detailGain * detail);
你想想看,细节增益设到 2.0 以上会怎样?嗯,纹理是清晰了,但噪声也被放大了。我在做夜景 HDR 时吃过这个亏——暗部噪声全出来了,画面脏得不行。
避坑指南:
我曾经把细节增益调到 3.0,结果天空出现明显的颗粒感。后来我加了一个噪声掩膜:只在纹理丰富区域增强,平滑区域保持原样。效果好了很多。
3.4 光晕伪影问题:成因与对策
光晕(Halo)是局部映射最常见的 artifacts。表现为高对比边缘附近出现亮边或暗边。
成因:
- 滤波不够边缘保留:高斯滤波必然导致光晕。
- σr 设置过大:双边滤波退化为高斯滤波。
- 基础层压缩过强:边缘两侧的亮度差被放大。
对策:
- 改用引导滤波(Guided Filter)或加权最小二乘滤波(WLS)。
- 多尺度分解:把图像分成多个频带,分别处理,减少单层压缩压力。
- 边缘感知权重:在映射时检测边缘,降低边缘处的压缩强度。
我的经验:
如果双边滤波已经调得很好了还有轻微光晕,试试在合成前对基础层做一次「边缘遮罩」——用 Sobel 检测边缘,在边缘附近降低细节增益。这招我用了好几年,效果稳定。
3.5 知识体系图:局部映射流程
下面这张图总结了局部 Tone Mapping 的完整流程,从输入到输出,每一步都绕不开。
3.6 实战建议:参数调试顺序
如果你刚开始调局部映射,我建议按这个顺序来:
- 先固定 σs:设为图像宽度的 1/50 左右,比如 1920 宽的图用 38。
- 再调 σr:从 0.05 开始,观察边缘保留情况。如果光晕明显,降低 σr。
- 最后调细节增益:从 1.2 起步,每次加 0.1,直到细节满意但噪声不显。
- 检查高对比边缘:找天空与树枝、窗户与墙壁的交界处,看有没有亮边。
小技巧:
如果双边滤波太慢(尤其大图),可以用引导滤波替代。效果接近,但速度快一个数量级。我在移动端项目里全换成了引导滤波。