[快速阅读八] 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 ...
随机推荐
- Fiddler使用界面介绍-左侧会话面板
左侧会话面板,是Fiddler抓取的请求数据展示
- 【JPA】01 快速上手
前言 Preface 本文的编写根据此视频参考:应该是非常好理解的JPA了 https://www.bilibili.com/video/BV1hE411s72B 小提示:[不想听概念直接上手从P8开 ...
- 【OracleDB】 08 子查询
什么是子查询? 子查询是一种常用计算机语言SELECT-SQL语言中嵌套查询下层的程序模块. 当一个查询是另一个查询的条件时,称之为子查询. Oracle的子查询语法公式: SELECT select ...
- kimchi – kvm虚拟机网页管理
参考: https://mangolassi.it/topic/15882/kimchi-kvm-updated-and-better-and-easy-guide-for-kvm-beginners ...
- java多线程-补充-面试
1.背景 在这个课程之前我们已经讲了2个关于多线程的课程 一个是主要是关于多线程基础的: 另一个主要是关于JUC的: 今天我们对之前课程中没有讲到的或者重要的或者是童鞋们反馈的技术点做一个补充讲解 当 ...
- 架构演化学习思考(4) --- IOC的学习认识
架构演化学习思考(4) IOC的学习认识[1] IOC相关概念认识 什么是IOC? IOC全称为 Inversion Of Control ,即控制反转.它是一种控制思想,可以解释为类和类之间的依赖关 ...
- WhaleStudio 分钟级构建 AI 模型,强大 Ops 能力简化模型调度与部署
什么是机器学习(ML)? 它有什么作用 机器学习(ML)是人工智能(AI)的一个子集,通过算法发现数据中的通用模式,并根据持续不断的训练来优化调整最终结果.ML模型从过去的经验中学习,并根据已有的经验 ...
- 花样玩转“所见即所得”的可视化开发UI
随着技术的发展,用户对软件的界面美观度和交互体验的要求越来越高.在这样的背景下,可视化开发UI(User Interface)成为了提升用户体验和开发效率的重要工具. 通过图形界面来设计和构建用户界面 ...
- BOM 相关知识总结
一:介绍BOM 1 1.什么是BOM? 2 DOM就是一套操作HTML标签的API(接口/方法/属性) 3 BOM就是一套操作浏览器的API(接口/方法/属性) 4 5 2.BOM中常见的对象 6 w ...
- 用whl文件安装Anaconda中的GDAL
本文介绍在Anaconda环境下,基于.whl文件安装Python中高级地理数据处理库GDAL的方法. 在之前的文章中,我们介绍了基于conda install命令直接联网安装GDAL库的方法 ...