[快速阅读八] 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 ...
随机推荐
- 关于SpringBoot中事务回滚没有生效
在SpringBoot中,事务回滚可以用注解@Transactional标识. Spring声明式事务管理默认对非检查型异常和运行时异常进行事务回滚,而对检查型异常则不进行回滚操作. 1.非检查型异常 ...
- ceph 001 存储类型 传统存储与分布式存储 分布式文件系统 集群与分布式
ceph 存储类型 块存储:裸磁盘 未被格式化的磁盘 DAS(直连存储,usb,硬盘插到电脑):scsi接口 接口数量有限 传输距离有限 SAN(存储区域网络):ip-san 网络(iscsi) 以太 ...
- 再测python3.13a —— python3.13是否移除了GIL的限制(续)
前文: python3.13是否移除了GIL的限制 x86_64 ubuntu22.04环境下编译版本python3.13.0 alpha 0源码--python3.13.0 alpha 0的源码编译 ...
- AQS源码深度解析之cancelAcquire方法解读
1.背景 2.源码解读 调用该方法的地方 方法源码解读 /** * 取消获取资源(异常处理时都需要用到) * 方法主要功能: * 1.处理当前取消节点的状态: * 2.将当前取消节点的前置非取消节点和 ...
- js map方法处理返回数据,获取指定数据简写方法
map方法处理返回数据,获取指定数据简写方法 前言 后端返回数据为数组列表时,通常比较全面,包含了很多不需要的数据,可以通过 map 方法处理返回数据,筛选出想要的数据 例如 // 返回数据 res ...
- 记一次 .NET某环境监测系统 崩溃分析
一:背景 1. 讲故事 前些天有位朋友找到我,说他们的程序崩溃了,也自己分析了下初步结果,让我帮忙再确认下,既然让我确认,那就开始dump分析之旅吧. 二:WinDbg 分析 1. 为什么会崩溃 wi ...
- Unix、Linux、GNU 关系梳理
之前写了一篇 MSYS2.MinGW 和 Cygwin 关系梳理的博客,但是要讲清它们几个的关系最好还是先了解一下操作系统的发展历程.遂补充了这篇博客. UNIX:现代操作系统的始祖 Operatin ...
- C#必备技能—项目打包
C#项目打包 开发好一个软件后,交给客户去使用,这时需要对项目进行打包成一个.exe文件,怎么去做?(共三步) 前提 安装扩展:状态栏[扩展]-[管理扩展],搜索setup,点击安装(安装比较慢,等待 ...
- docker系列教程:docker图形化工具安装及docker系列教程总结
通过前面的学习,我们已经掌握了docker-compose容器编排及实战了.高级篇也算快完了.有没有相关,我们前面学习的时候,都是通过命令行来操作docker的,难道docker就没有图形化工具吗?答 ...
- docker with GPU support
自己总结的: nvidia-docker, 不支持windows,2019.10, nvidia-docker过时了,从docker 1903开始,安装一个nvidia-container-runti ...