[快速阅读八] 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 ...
随机推荐
- Vite本地构建:手写核心原理
前言 接上篇文章,我们了解到vite的本地构建原理主要是:启动一个 connect 服务器拦截由浏览器请求 ESM的请求.通过请求的路径找到目录下对应的文件做一下编译最终以 ESM的格式返回给浏览器. ...
- 02 IO口的操作
目录 前言 一.IO的概念 1.IO接口 2.IO端口 二.CPU和外设进行数据传输的方法 1.程序控制方式 1.1 无条件 1.2 查询方式 2.中断方式 3.DMA方式 一.方法介绍和代码编写 1 ...
- 对比python学julia(第一章)--(第三节)山巅一寺一壶酒
在小学阶段背过圆周率的同学对这节的标题应该不陌生.π(3.14159-)是大家熟悉的普通无理数,但也是非常神秘的一组数字,例如几个世纪以来,埃及考古学家和神秘主义追随者一直痴迷于胡夫金字塔暗藏的圆周率 ...
- 【Java】讲讲StreamAPI
预设场景: 从Mybatis调用Mapper得到的用户集合 List<UserDTO> userList = new ArrayList<>(); 常用的几种API用法示例: ...
- 【Hibernate】06 查询API
三种查询API - Query 不需要SQL语句,但是要HQL语句 - Criteria 不需要任何QL语句,直接调用即可 - SQLQuery 调用底层的SQL语句实现 什么是HQL? Hibern ...
- 【YAML】非标记语言的标记语言
什么是YAML? YAML是"YAML Ain't a Markup Language"(YAML不是一种标记语言)的递归缩写. 在开发的这种语言时,YAML 的意思其实是:&qu ...
- baselines算法库common/vec_env/vec_env.py模块分析
common/vec_env/vec_env.py模块内容: import contextlib import os from abc import ABC, abstractmethod from ...
- Apache DolphinScheduler-3.2.0集群部署教程
集群部署方案(2 Master + 3 Worker) Apache DolphinScheduler官网:https://dolphinscheduler.apache.org/zh-cn Apac ...
- flex布局被内容被撑开及flex布局下定宽元素被压缩
实现效果使用flex进行左右布局,左边定宽200px,右边自适应,当右边内容过多,造成右边盒子被撑开,会造成两种问题 左边定宽盒子被压缩解决办法: flex-grow:0;//是否自动增长空间 fle ...
- Atcoder ABC364 D-F
Atcoder ABC364 D-F D - K-th Nearest 链接: D - K-th Nearest (atcoder.jp) 简要题意: 问题陈述 在一条数线上有 \(N+Q\) 个点 ...