首页 > 其他分享 >[快速阅读八] HDR->LDR:Matlab中tonemapfarbman函数的解析和自我实现。

[快速阅读八] HDR->LDR:Matlab中tonemapfarbman函数的解析和自我实现。

时间:2024-10-09 12:44:26浏览次数:1  
标签:ps end HDR tonemapfarbman mm TT LDR comLogLum

  最近受朋友的委托,想自己实现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中了,参数界面如下所示:

        

  个人感觉这个函数也不是特别通用,对部分图还是要仔细调整参数才能得到较为合理的结果。  

  如果想时刻关注本人的最新文章,也可关注公众号:

                             

翻译

搜索

复制

<iframe height="240" width="320"></iframe>

标签:ps,end,HDR,tonemapfarbman,mm,TT,LDR,comLogLum
From: https://www.cnblogs.com/Imageshop/p/18453683

相关文章

  • HDR解释
    HDR,全称为HighDynamicRange,即高动态范围。HDR显示器与标准:了解HDR显示器的工作原理,以及常见的HDR标准,如HDR10、DolbyVision、HLG等。这些标准定义了HDR内容的制作、分发和显示方式。当HDR内容在HDR电视上播放时,具有更宽的动态范围(即对比度),以及更多亮度分级(过渡更平滑并得......
  • 迅为RK3588开发板支持Android13和12版本系统还有Debian11、Buildroot、Ubuntu20与22版
    我们已经在RK3588上开发了稳定又好用的Android13和12版本系统Debian11、Buildroot、Ubuntu20与22版本、银河麒麟、开放麒、统信系统、openEuler24.03系统,内核Linux5.10版本。......
  • rk3568系统buildroot开发笔记
    编译异常infrom_bz2importBZ2Compressor,BZ2DecompressorModuleNotFoundError:Nomodulenamed‘_bz2’sudoapt-getinstalllibbz2-dev然后删掉rk356x_bsp_bak/rk356x_bsp/build-iot/buildroot_output/rockchip_rk3568_iot/build/host-python3-3.10.5重新编......
  • 在虚幻引擎(UE5)中使用HDR贴图_UE5教程
    一共有两种文件格式,推荐使用hdr格式,先讲hdr格式先创建一个材质,名称随意双击打开,把着色模型改为无光照拖入hdr贴图,此时会报错创建三维向量转换为参数,此时已经可以正常显示了,但是增加一些可调节参数一个对比度一个光照强度,转换为参数创建材质实例,双击打开打开双面材质创建......
  • react 学习之从diff children看key的合理使用
    大部分优化环节react都自己在内部处理了,但有一种情况也值得开发者注意,那就是列表中key的使用,合理的使用key有助于能精确的找到用于新旧节点复用的老节点。那么我们这里来学习下react是如何diffchildren的,从源码的角度看。用几个案例来描述ReactdiffChild核心流程,react在一次更......
  • 配置 Buildroot 的命令行提示符显示用户名和主机名
    配置Buildroot的命令行提示符显示用户名和主机名在Buildroot构建根文件系统之前,如果你想预先配置命令行提示符,使其在系统启动时显示用户名和主机名,可以通过以下几种方法在构建过程中设置这些配置文件。你可以通过Buildroot的配置选项或修改根文件系统的文件内容来实......
  • root 添加登录密码(基于buildroot)
    通过以前这篇文章Linux启动后自动登录root进入控制台-f1engmin11-博客园(cnblogs.com) 我们可以知道,Linux启动后,是否自动登录root用户,可以通过修改/etc/inittab文件的 respawn动作来实现。下面记录一下如何添加root登录密码:1.首先 /etc/inittab文件设置为ro......
  • WPF UniformGrid contain children auto resize
    //xaml<Windowx:Class="WpfApp332.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d="http://schemas.mi......
  • 迅为电子RK3588S开发板第三章Buildroot系统功能测试
      iTOP-3588S开发板采用瑞芯微RK3588S处理器,是全新一代AloT高端应用芯片,采用8nmLP制程,搭载八核64位CPU(四核Cortex-A76+四核Cortex-A55架构),集成MaliG610MP4四核GPU,内置AI加速器NPU,算力达6Tops,支持8K视频硬件编码器和硬件解码器,提供了许多功能强大的嵌入式硬件引擎,性能更强......
  • HDR与色调映射
    HDR高动态范围(HDR)技术能够产生比标准动态范围(SDR)图像更高的亮度动态范围的图像,使得图像中的亮部和暗部细节都能得到更好的保留和展现,黑色更加深邃,亮部更加明亮,同时图像的色彩也更加丰富、饱满。在标准渲染中,像素的红色、绿色和蓝色值均使用一个0到1范围内的8位值......