办公室今天停电, 幸好本本还有电, 同事们好多都去打麻将去了, 话说麻将这东西玩起来也还是有味的, 不过我感觉我是输了不舒服, 赢了替输的人不舒服, 所以干脆拜别麻坛四五年了, 在办公室一个人整理下好久前的一片论文的思想, 和万千世界里有缘人共同分享下资源了
论文的名字是 <Adaptive Logarithmic Mapping For Displaying High Contrast Scenes>, 相关的 PDF 文档可以在百度上下载到, 翻译成中文的意思是一种显示高对比度场景的自适应对数映射算法, 也是一篇很古老的算法文章的, 看了下好像是 2003 年的, 在 Opencv 3.0 中已经提供了这个算法的实现了, 但是其实现的细节我觉得写的真恶心(我觉得 Opencv 所有的算法写的都恶心, 饶了一堆弯才到算法的中点, 感觉大的工程都是这样的), 其算法位于 OpenCV3.0\opencv\sources\modules\photo\src\tonemap 文件中, 我在实现这个算法时时参考了另外一个非常有名的开源软件: luminance hdr, 这个软件最新的版本集成了 11 中用于 HDR 显示的算法过完年我要再次好好的看看这个软件的代码了
话说回本文的重点, 由于对论文的理解不是很深刻, 部分内容仅以翻译为主
一应用背景
简单的说, 就是我们认为显示给人眼看的亮度值 Ld 和场景亮度值 Lw 之间存在如下的关系:
其中 Lmax 为场景的最大亮度, 这个映射关系式能够保证无论真实亮度范围有多大, 最亮的部分总能映射为 1(白色), 而其他的亮度也能平缓的变化虽然这个算式对一些图能获得较为满意的结果, 但是我们也发现有些图的亮度压缩太过了, 一些高对比度的内容也丢失了
二自适应对数映射
那么论文提出的色调映射方案遵循了下面几条规则:(1)不管原始数据如何分散, 他必须都能输出连续的结果 (2) 它应该具有自适应性和可扩展性, 他必须能显示出场景的物理本质同时不得引入对比度反转和光晕总的亮度也必须忠实于实际的内容 (3) 算法也需要对用户友好, 也就是说在大部分情况下需要能自动实现, 少数情况需要调节一些比较直观的参数
1 把场景亮度映射到图像亮度
输出图像的整体亮度主要是由场景的亮度特性决定的, 所以找到一个从场景亮度到输出图像亮度之间的初始缩放因子很重要, 这就类似于在摄影中曝光设置决定了所拍摄的图片的最终效果一样现在的镜头都提供的不同的自动曝光选项, 比如 center- weighted, center-spot 以及 matrix-metering 等同样, 本文提出两种不同的方法适合于不同的用途对于静态图片或者当用户不直接和场景交互, 我们基于所有像素的亮度信息计算出场景的对数平均值作为初始缩放因子, 对于需要交互的场合, 缩放因子不是固定值, 而是使用亮度的对数信息的高斯模糊后的结果, 通常高斯模糊核能覆盖场景的 15% 范围即可以, 当然也可以调整这个范围
2 对比度调整
本文提出的核心的最具特色的色调映射函数就是根据每个像素的信息来自适应的调整函数中的对数基 (从 2 到 10) 这从本质上提供了很好的对比度和细节信息, 同时能对高亮度值进行很大程度上的压缩原则上, 一个更宽或者更窄的对数基也可以使用, 但是实际上, 我们没有任何理由去使用它当对数基小于 2 时, 其值迅速增加, 导致曝光调整很困难另一方面, 当对数基大于 10 时, 亮度压缩的量很小, 导致这个图片丢失了太多的对比度同时, 我们也观察到了高对数基时的一些颜色偏移现象
如下左图所示, 左图时基于 2 对数基的, 有图是基于 10 对数基的(全图), 很明显, 两幅图的对比度和亮度区别很明显, 但是他们都没有给出非常令人满意的结果
为了实现不同像素不同的对数基以及像素的连续性的要求, 我们参考了 Perlin 和 Hoffert 的偏置对数函数, 该函数是纹理分析的的标准工具并且在计算机视觉上广为应用偏置函数定义在单位区间 [0,1] 之间的 power 函数, 有一个参数 b, 直接决定了输出值的大小, 具体形式如下:
其曲线如上右图所示
2.3 算法细节
一般数据是基于 RGB 空间的, 我们首先将数据转换到 XYZ 空间, 其中的 Y 分量反应了每个像素的亮度值我们首先基于 Y 分量计算出对数的最大亮度 Lwmax 和平均亮度 Lwa 然后把公式 (3) 带入公式 (1) 并做适当的阔啊站, 得到最终的计算显示亮度的公式如下所示:
相比于论文, 上述公式后半部分是我自己添加上去的, 主要是为了解释方便
我们首先看下算式中的
, 很明显, 他的取值范围是 2 到 10 之间, 这和论文前面的描述是一致的然后底部的 Log10 也保证了整个算式的区间范围
然后我们知道, 对数计算式有如下特性:
因此把公式 (4) 的后半部分展开就到了论文的结果
式中有多了个参数 Ldmax, 这个值表示显示设备的最大显示能力, 对于普通的 CRT 显示器, 我们直接取值为 100
论文后面还有关于 Gamma 校正的内容, 那些都是辅助的了, 实际上没啥意思, 论文核心的就是上述公式
三: 具体实现即细节注意:
具体实现代码可以完美的参考 luminance hdr, 关键是要注意一些数据的范围要映射到 0 和 1 之间才能处理, 特别是论文有些地方其实没有讲的特别清晰, 比如在论文里有这样一句话: The XYZ luminance component Y of each pixel (Lw for world luminance) and the maximum luminance of the scene Lwmax are divided by the world adaptation luminance Lwa and eventually multiplied by an exposure factor set by the user 这里其实没有明确的说 Lwa 是什么 (整篇论文都没有说) 其实就是上面讲的对数平均值, 还有最后直接用公式计算得到的 Ld 一般情况下是很小的, 如何处理让其显示也是值得讲究的我这里贴出对公式 (4) 计算的核心代码:
- void ComputeScaleTable(float ScaleTable[], float Bias, float Saturation, float MaxLum, float AvLum)
- {
- float Divider, BiasP, NormalY, Interpol, NewLum;
- MaxLum = MaxLum / AvLum; // normalize maximum luminance by average luminance(divided by the world adaptation luminance Lwa)
- Divider = log10f(MaxLum + 1.0f); // 论文公式 (4) 左半部分的除数
- BiasP = logf(Bias) / log(0.5); // 公式 (3) 中的上指标
- for (int Index = 0; Index < 256; Index++) // Normal tone mapping of every pixel
- {
- // The XYZ luminance component Y of each pixel (Lw for world luminance) and the maximum luminance of the scene
- // Lwmax are divided by the world adaptation luminance Lwa and eventually multiplied by an exposure factor set by the user.
- NormalY = (Index / 255.0) / AvLum; // divided by the world adaptation luminance Lwa
- Interpol = logf(2.0f + powf(NormalY / MaxLum, BiasP) * 8.0f); // 论文公式 (4) 右半部分的除数
- NewLum = (logf(NormalY + 1.0f) / Interpol) / Divider; // 论文公式(4)
- ScaleTable[Index] = powf(NewLum / (Index / 255.0 + 1e-4), Saturation);
- }
- }
以上代码是针对 8 位图像的, 上面的 / 255.0 就是归一化到 [0,1] 范围的作用但是最后一行的 NewLum / (Index / 255.0 + 1e-4)你们能理解是什么意思吗?
最后一行代码的 Saturation 的作用见 < Gradient domain high dynamic range compression > 一文, 当大于 1 时, 图像越饱和也越亮, 小于 1 是图像变暗
虽然论文描述的算法本意是用到 HDR 这种高动态范围的图像的, 但是实际上我目前也只实现了 8 位的 LDR 的代码, 但是对于 8 位的图像, 特别是偏暗的图像还是有很好的增强的效果的, 对于正常的图像, 一般也不会出现特别不好的效果
对于常态的图片, 一般也能起到一定的视觉增强效果:
关于速度优化方面, 如果是针对 8 位图像, 则中间的很多浮点计算可以用查表代替, 而 XYZ 和 RGB 空间转化, 我前面一篇博客已经提到可以用 SSE 快速实现处理 1080P 的图大概需要 20ms
也曾尝试不转到 XYZ 空间, 直接提取出亮度信息, 然后直接在 RGB 空间处理, 似乎效果也还可以, 但是有可能会出现较多的偏色
明年有时间把这个算法扩展到 16 位图像上取看看, 有什么效果
8 位测试工程: https://files.cnblogs.com/files/Imageshop/TonemapDrago.rar
来源: https://www.cnblogs.com/Imageshop/p/8420828.html