[快速阅读八] HDR->LDR:Matlab中tonemapfarbman函数的解析和自我实现。
最近受朋友的委托,想自己实现Matlab里的一个HDR转LDR的函数,函数名是tonemapfarbman,乘着十一假期,稍微浏览下这个函数,并做了一点C++的实现和优化。
为了看到这个函数的效果,需要至少matlab R2018b及其以上的版本。
首先,我们下载了matlab帮助文档中提到的该算法对应的论文:Edge-Preserving Decompositions for Multi-Scale Tone and Detail Manipulation.
通读了下这个论文,感觉他并没有提出一个什么具体的算法,而是一个大的框架,其最有用的部分为 3.1 Multi-scale edge-preserving decompositions。

这个意思呢,就是用某一种保边滤波器进行K+1层次的分解,但是分解是依次的,这K+1次保边滤波器使用某一种逐渐增强的参数,使得细节信息呢越来越少,但是边缘信息还是尽量保存。这个时候,前一层的结果减去后一层结果即为细节层,最后依次的保边滤波结果即为基础层,此时,由基础层再反向加上每一层的细节层即可以得到原始一模一样的数据。
这个呢,和早期的16位RAW图像处理一】:基于Fast Bilateral Filtering 算法的 High-Dynamic Range(HDR) 图像显示技术 相比,其实最大的区别就在于用了多层保边滤波器,而这个文章只用了一层。
同一些基于金字塔的的HDR相比呢,其核心区别是没有进行任何的下采样,而都是全图进行的。
那么要进行HDR到LDR的操作,总的来说就是一个要点,想办法调节细节层的信息,一个最简单的方法就是每个细节层乘以一个系数,在文章后续还描述了一些过程,针对不同的需求有不同的实现方式,不过我们去翻看matlab针对这个函数的核心代码,发现m处理的是相对来说非常简单的,我们摘取下他的核心部分代码:
1 % Compute log luminance
2 logLum = log(lum);
3
4 % Apply diffusion filter for multi-scale decomposition
5 % Compute detail layers
6 % Recombine the layers together while moderately boosting multiple scale detail
7 uPre = logLum;
8 comLogLum = zeros(size(HDR,1),size(HDR,2), 'like', HDR);
9 numItr = 5; % No. of iterations ('NumberOfIterations' of anisotropic diffusion) for the first level decomposition.
10 logLum(~isfinite(logLum)) = (realmax('single')/100); % replaced Inf with a high value.
11 rangeLum = sum([max(logLum(:)) - min(logLum(:)),eps('single')],'omitnan'); % range of the image
12 for scaleInd = 1:numScale
13 % gradThresh is the 'GradientThreshold' parameter of the anisotropic diffusion.
14 % Here, it is taken as 5% of the dynamic range of the image.
15 % gradThresh is increased for each decomposition level (multiplied by scaleInd (No. of iterations))
16 gradThresh = scaleInd*5*(rangeLum)/100;
17 uCurr = imdiffusefilt(logLum,'NumberOfIterations',numItr*scaleInd,'GradientThreshold',gradThresh);
18 detail = uPre - uCurr; % multi-scale decomposition (detail extraction)
19 comLogLum = comLogLum + weight(scaleInd).*detail; % weighted summation
20 uPre = uCurr;
21 end
22 comLogLum = comLogLum + uCurr;
23
24 % Convert back to RGB
25 % The 99.9% intensities of the compressed image are remapped to give the output image a consistent look
26 newI = exp(comLogLum);
27 sI = sort(newI(:));
28 mx = sI(round(length(sI) * (99.9/100)));
29 newI = newI/mx;
30
31 % Exposure, RangeCompression and saturation correction
32 expoCorrect = exposure*newI;
33 if size(HDR,3) == 1
34 LDR = expoCorrect.^gamma;
35 else
36 LDR = (expoCorrect .* (rgb .^ sat)).^gamma;
37 end
38 LDR = im2uint8(LDR);
39 end
第二行代码,将图像数据转换到log空间,这基本上是HDR算法第一步的标准做法。
从第12行到第22行是算法的核心部分,在这个循环里,使用了imdiffusefilt这个函数作为保边滤波器,他实际上是多次各向异性滤波器的迭代版本呢,这个滤波器具有梯度阈值和迭代次数两个参数,循环中,迭代次数随着循环的增加线性增加,梯度阈值也在每次迭代时做相应的调整,从而得到一个逐渐模糊且保边图像,如下图所示:


原图 GradientThreshold = 12,NumberOfIterations = 5 GradientThreshold = 24,NumberOfIterations = 10 GradientThreshold = 36,NumberOfIterations = 15
第18行使用detail = uPre - uCurr,即前一次的保边结果-本次的保边滤波结果得到这一层的细节信息,然后第19行
comLogLum = comLogLum + weight(scaleInd).*detail;
把detail信息乘以用户指定的增强系数(大于1,增加本层的细节,小于1,减少本层的细节),在累加到之前的细节中去。
第22行 comLogLum = comLogLum + uCurr; 中,此时的uCurr中保存了最后一次保边滤波器的结算结果,所以把他加入到前面的细节信息中接得到我们处理后的结果。
第26行把对数空间的数据通过exp指令再次恢复到正常的空间,其实此时配合上im2uint8就应该能得到最后的LDR图像了,但是实际上这个时候图像的细节信息基本已经得到了增强,但是整体的可视度或者视觉效果是很一般的,所以后面再通过曝光度和Gamma校正两个参数适当调整输出的效果。
第27到29行主要是取恢复后的数据前99.9%的值作为阈值,把那些过渡曝光的点消除掉,后续代码再进行exposure和gamma调整。
整个流程也没有什么特别复杂的地方。
翻译这个函数成C++并不是一件很复杂的事情,主要是imdiffusefilt的翻译,对于二维的数据,这个函数调用了anisotropicDiffusion2D函数,具体查看这个函数呢,他有4领域和8领域的方式,以领域为例,其核心代码如下:
1 % DiffusionRate is fixed to 1/4 because we considered nearest neighbour
2 % differences in 4 directions(East,West,North,South)
3 diffusionRate = 1/4;
4 diffImgNorth = paddedImg(1:end-1,2:end-1) - paddedImg(2:end,2:end-1);
5 diffImgEast = paddedImg(2:end-1,2:end) - paddedImg(2:end-1,1:end-1);
6 switch conductionMethod
7 % Conduction coefficients
8 case 'exponential'
9 conductCoeffNorth = exp(-(abs(diffImgNorth)/gradientThreshold).^2);
10 conductCoeffEast = exp(-(abs(diffImgEast)/gradientThreshold).^2);
11 case 'quadratic'
12 conductCoeffNorth = 1./(1+(abs(diffImgNorth)/gradientThreshold).^2);
13 conductCoeffEast = 1./(1+(abs(diffImgEast)/gradientThreshold).^2);
14 end
15 fluxNorth = conductCoeffNorth .* diffImgNorth;
16 fluxEast = conductCoeffEast .* diffImgEast;
17
18 % Discrete PDE solution
19 I = I + diffusionRate * (fluxNorth(1:end-1,:) - fluxNorth(2:end,:) + ...
20 fluxEast(:,2:end) - fluxEast(:,1:end-1));
里面的大部分计算还是exp函数,然后就是涉及到了3*3的临域,这个建议不要直接翻译,因为matlab代码的向量化很厉害,要先理解他的意思,然后再重新写。
我加载一副1700*3700左右的单通道16位图像,在matlab中测试,使用默认参数(3层),处理的时间大概需要0.6s,个人认为这个速度相对来说是非常快的,因为这个算法内部涉及到了太多浮点计算,特别是exp函数,我初步的C++版本的速度要比matlab的慢很多,后面经过SSE指令优化后,也需要1100ms,后续测试发现matlab里的代码使用了多线程,而我这个是单线程的版本,如果统计用多线程,我这个可以做到300ms。
进一步的优化手段有,修改exp的实现,用近似的版本,比如使用快速exp算法这里的迭代版本,可以由如下的代码实现:
inline __m128 _mm_myexp_ps(__m128 x)
{
__m128 T = _mm_add_ps(_mm_set1_ps(1.0f), _mm_mul_ps(x, _mm_set1_ps(1.0f / 256)));
__m128 TT = _mm_mul_ps(T, T);
TT = _mm_mul_ps(TT, TT);
TT = _mm_mul_ps(TT, TT);
TT = _mm_mul_ps(TT, TT);
TT = _mm_mul_ps(TT, TT);
TT = _mm_mul_ps(TT, TT);
TT = _mm_mul_ps(TT, TT);
TT = _mm_mul_ps(TT, TT);
return TT;
}
这个要比标准的exp快很多,而精度基本差不多,但是单线程情况速度基本就可以做到250ms了。
我把这个算法也集成到我的DEMO中了,参数界面如下所示:

个人感觉这个函数也不是特别通用,对部分图还是要仔细调整参数才能得到较为合理的结果。
如果想时刻关注本人的最新文章,也可关注公众号:

翻译
搜索
复制
[快速阅读八] HDR->LDR:Matlab中tonemapfarbman函数的解析和自我实现。的更多相关文章
- Matlab中plot函数参数解析
功能 二维曲线绘图 语法 plot(Y) plot(X1,Y1,...) plot(X1,Y1,LineSpec,...) plot(...,'PropertyName',PropertyValue, ...
- Matlab中unifrnd函数使用解析
1.生成N阶[a,b]均匀分布数组 >> unifrnd(3,5,5,5) ans = 3.8651 4.6677 4.8115 4.3456 4.8560 4.0241 3.4079 3 ...
- matlab中patch函数的用法
http://blog.sina.com.cn/s/blog_707b64550100z1nz.html matlab中patch函数的用法——emily (2011-11-18 17:20:33) ...
- matlab中subplot函数的功能
转载自http://wenku.baidu.com/link?url=UkbSbQd3cxpT7sFrDw7_BO8zJDCUvPKrmsrbITk-7n7fP8g0Vhvq3QTC0DrwwrXfa ...
- 【原创】Matlab中plot函数全功能解析
[原创]Matlab中plot函数全功能解析 该帖由Matlab技术论(http://www.matlabsky.com)坛原创,更多精彩内容参见http://www.matlabsky.com 功能 ...
- matlab 中max函数用法
Matlab中max函数在矩阵中求函数大小的实例如下:(1)C = max(A)返回一个数组各不同维中的最大元素.如果A是一个向量,max(A)返回A中的最大元素.如果A是一个矩阵,max(A)将A的 ...
- Matlab中plot函数全功能解析
Matlab中plot函数全功能解析 功能 二维曲线绘图 语法 plot(Y)plot(X1,Y1,...)plot(X1,Y1,LineSpec,...)plot(...,'PropertyName ...
- matlab中cumsum函数
matlab中cumsum函数通常用于计算一个数组各行的累加值.在matlab的命令窗口中输入doc cumsum或者help cumsum即可获得该函数的帮助信息. 格式一:B = cumsum(A ...
- 『转载』Matlab中fmincon函数获取乘子
Matlab中fmincon函数获取乘子 一.输出结构 [x,fval,exitflag,output,lambda] = fmincon(......) 二.结构说明 lambda结构 说 ...
- matlab中norm函数的用法
格式:n=norm(A,p) 功能:norm函数可计算几种不同类型的矩阵范数,根据p的不同可得到不同的范数 以下是Matlab中help norm 的解释 NORM Matrix or vecto ...
随机推荐
- 如何使用ventoy安装操作系统
使用ventoy安装操作系统 vrntoy简介 简单来说,Ventoy是一个制作可启动U盘的开源工具. 有了Ventoy你就无需反复地格式化U盘,你只需要把 ISO/WIM/IMG/VHD(x)/EF ...
- 【H5】01 入门 & 概述
前言 看了很多教程资料,很难受,东西讲不全,一些属性就是简单的解释就没了,不能追根问底的了解这个东西,所以在后面, 越是学习就越是费解,出现的问题也越来越多.什么快速学完都是不存在的,培训机构的东西也 ...
- 三.mysql问答合集
目录 三.MySQL 3.1 关系型和非关系型数据库的区别 关系型数据库 非关系型数据库 3.2 登录数据库的方式,如何远程登录 3.3 MySQL的服务结构,当客户端发起请求后,处理过程 3.4 如 ...
- 循环神经网络 —— LSTM 图片
- 阿里提供的免费DNS服务器
阿里提供的免费DNS服务器的介绍网页: https://developer.aliyun.com/mirror/DNS nameserver 223.5.5.5 nameserver 223.6.6. ...
- 并行化强化学习 —— 初探 —— 并行reinforce算法的尝试 (上篇:强化学习在多仿真环境下单步交互并行化设计的可行性)
强化学习由于难收敛所以训练周期较长,同时由于强化学习在训练过程中起训练数据一般都为实时生成的,因此在训练的同时算法还需要生成待训练的数据,强化学习算法的基本架构可以视作下图:(取自:深度学习中使用Te ...
- Analysis of Set Union Algorithms 题解
题意简述 有一个集合,初始为空,你需要写一个数据结构,支持: 0 x 表示将 \(x\) 加入该集合,其中 \(x\) 为一由 \(\texttt{0} \sim \texttt{9}\) 组成的数字 ...
- 神经网络之卷积篇:详解边缘检测示例(Edge detection example)
详解边缘检测示例 卷积运算是卷积神经网络最基本的组成部分,使用边缘检测作为入门样例.在这个博客中,会看到卷积是如何进行运算的. 在之前的博客中,说过神经网络的前几层是如何检测边缘的,然后,后面的层有可 ...
- 如何快速在 Apache DolphinScheduler 新扩展一个任务插件?
作者 | 代立冬 编辑 | Debra Chen Apache DolphinScheduler 是现代数据工作流编排平台,具有非常强大的可视化能力,DolphinScheduler 致力于使数据工程 ...
- k8s中controller-runtime并发Reconcile分析
§ 0x01 起因 开发控制器时,团队内一直在讨论是否需要为单个控制器对象添加并发控制(即加锁),最终把 controller-runtime 框架中并发数改为1,同时启用了 k8s 的 leader ...