热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

SSE图像算法优化系列三:超高速导向滤波实现过程纪要(欢迎挑战)

自从何凯明提出导向滤波后,因为其算法的简单性和有效性,该算法得到了广泛的应用,以至于新版的matlab都将其作为标准自带的函数之一了&#x

  自从何凯明提出导向滤波后,因为其算法的简单性和有效性,该算法得到了广泛的应用,以至于新版的matlab都将其作为标准自带的函数之一了,利用他可以解决的所有的保边滤波器的能解决的问题,比如细节增强、HDR压缩、细节羽化、去雾、风格化,而且由于其保边特性,如果很多传统函数中使用高斯滤波或者均值滤波的地方用他代替,能很好解决一些强边缘的过渡不自然问题,比如retinex、Highlight/shadow等应用中,因此,快速的实现该算法具有很强的适用意义。

  本文简要的记录了本人在优化导向滤波实现的过程中所适用的优化方式和一些细节,以免时间久了后自己都不记得了,但是请不要向我直接索取源代码。

      自认为目前我优化的速度在CPU版本中很难有人能超越了(仅仅使用CPU、不用多线程,下采样率0.2),如果谁有更快的算法,在第三方公证的情况下,我愿意提供1000元奖励^_^。

      何凯明在导向滤波一文的相关资料中提供了其matlab代码,或者用下面的流程也可以清晰的表达:

  我们看到了上面的6次取mean计算的过程,也就是浮点数的boxfilter,这个东西已经是老掉牙的一个算法了,我在几年前研究过opencv内部的这个算法,并且提出了一种比opencv实现更快的方法,详见解析opencv中Box Filter的实现并提出进一步加速的方案(源码共享) 一文。不过那里的处理时针对字节数据的,其内部用到了一些整形数据的SSE优化,如果原始数据是浮点数,那反而就更为简易了,因为SSE指令生来就是为浮点数服务的。

      但是即使是这样,由于6次计算以及中间的其他一些浮点运算,依然给整个算法带来了很大的运算开销和内存开销,在很多场合还是无法满足需求的,比如实时去雾等场景。在早期我的快速去雾实现中,都是先利用下采样图的导向滤波结果,然后再双线性插值放大得到大图的透射率图,虽然在视觉效果上能解决去雾算法的速度问题,但是如果是其他场景的导向滤波需求,还是会看到很多瑕疵的。

      何凯明在2015又发表了一篇《Fast Guided Filter》的文章,阐述了一种很实用的更快速的导向滤波流程:

     我刚刚提的在去雾中我实用的小Trick实际上就是第六步及第七步不同,我的方式可表达如下:

       6: q = meana. * + meanb

       7:   q = fupsample(q, s)

      很明显,由于I的参与计算,何的做法能更大程度上保持结果和原汁原味的类似,而我的方式则会产生较大的块状相似,所以人家大神就是大神。

      在何的论文中已经说明下采样比例 s 取4时,计算的结果和准确结果也还是非常靠近的,我在我的实现里s 取到了5。

      这样改动后,所有的boxfilter均是对下取样后的数据进行处理,当s=4时,计算量减少到原有的1/16,而s=5,则减少到了原有的1/25,当然这个时候多了2个下取样和2个上取样的算法,下取样由于是缩小,计算量很小,无需关注,而上采样,计算量和原图大小有关,根据我的测评,这个上采样的耗时可能占整个过程的一般时间左右的,是非常值得注意优化的。

     首先,第一,步骤6中的两个采样过程不要分开写,直接写到同一个for循环内部,这样可以节省很多坐标的计算过程,第二,这里一般的上采样通常采用双线性插值就OK了,网络上有很多关于双线性插值的SSE优化的代码,但是那些基本都是针对32位的图像做的优化,搬到24位和8位中是不适用的,而我们会在50%以上的概率中遇到24位图像,所以说啊,网络上的东西虽多,但精华太少。

      我采用的一个优化方式时,先进行水平方向的上采样到一个缓冲区中(Width  * SmallH),然后在从这个缓冲区中沿着高度方向缓冲到(Width * Height),如下图所示:

            ----------------->      ----------------->  

       由于这个上采样是针对浮点型的数据,所以中间的精度损失问题可以不用考虑,而如果是图像的字节数据,则要慎重了。

       由上面的第一个图到第二个图的大概代码如下:

for (int Y = 0; Y    //  ...................}

  这段代码用SSE去优化的伤害的脑细胞有点多,而且由于其计算量不是太大,意义可能有限。

  而由第二个图到第三个图的过程大概可有用下面的代码表述:

for (int Y = 0; Y

  注意最后的IM_ClampFHtoByte函数是将括号内的值限制在0和255之间的。

      有很多朋友可能不知道,如果把上面的IM_ClampFHtoByte这个函数去掉,直接使用括号内的代码,VS的编译器可以很好的对上面代码进行向量化编译(VS编译只要你没有把代码生成--》启用增强指令集设置成无增强指令/arch:IA32,哪怕设置为未设置,都会把浮点的代码编译为SIMD相关指令的),而如果我们对不同的Channel,比如3通道4通道在循环里展开后,很不幸,按照我们的展开循环的理论,速度应该加快,但事实却相反了。所以我们需要充分掌握编译器的向量化特性,就能写成更高效的代码。

     由于在计算过程中确实存在一些结果超出了0和255的范围,因此如果把IM_ClampFHtoByte函数去除,对有些图像会出现噪点,因此,我们不能完全依赖编译器的向量化优化了,这就必须自己写SIMD指令,由于SIMD自带了饱和处理的相关函数,而上述内部的X 的for循环是很容易用SSE处理的,唯一需要注意的就是需要把LinePS对应的字节数据转换为浮点数据,这里我简单的提示可以用如下指令将8个字节数据转换为8个浮点数:

__m128i SrcI = _mm_unpacklo_epi8(_mm_loadl_epi64((__m128i const *)(LinePS + X)), Zero); // Load the lower 64 bits of the value pointed to by p into the lower 64 bits of the result, zeroing the upper 64 bits of the result.
__m128 SrcFL = _mm_cvtepi32_ps(_mm_unpacklo_epi16(SrcI, Zero)); // 转换为浮点
__m128 SrcFH = _mm_cvtepi32_ps(_mm_unpackhi_epi16(SrcI, Zero));

      里面的浮点计算的过程的SSE代码就和普通的函数调用没什么却别,最后的写到LinePD这个字节数据的过程可以用_mm_storel_epi64以及有关移位搞定。

      这里这样做的另外一个好处是在Y循环中计算是独立的,因此都可以使用OPENMP加速。

      使用SSE优化能将上述过程提速2倍以上。

      另外一个问题,在上面的流程2的第一步中,对boxfilter的半径r也是进行了同比例的缩小的,注意到boxfilter的半径通常情况下我们都是用的整数,如果缩小后的r'也进行取整的话,举例来说,对于s =4的情况下,半径为8、9、10、11这四种情况最终得到的导向滤波结果就完全一样了,似乎这不符合我们对算法严谨性的要求,所以我们要支持一种浮点半径的boxfilter。

     普通意义的boxfilter肯定是无法支持浮点半径的(这不同于高斯模糊),一种变通的办法就是取浮点半径前后的两个整形半径值做模糊,然后再线性插值,举个例子,如果下取样后的半径为4.4,则分别计算R1 = boxfilter(4)以及R2 = boxfilter(5),最后合成得到结果R:

               R = R1 * (1 - 0.4) + R2 * 0.4;

     如此处理后,在大部分情况下(除了下取样后的半径为整数,比如原有半径为12,s=4,这是r'=3),计算量又会稍微增加一点,需要计算小图的12次boxfilter了,不过何必纠结这个了呢。

     关于上述浮点版本的Boxfilter,其实还有一种更好的实现方式。我在13行代码实现最快速最高效的积分图像算法中也提供了一段实现方框模糊的代码,当然那个代码还不是最优的,因为其中的pixlecount需要每个像素都重新计算,其实当半径较小时中间部分的像素的pixlecount为固定值,因此可以把边缘部分的像素特殊处理,对于本例,是需要进行的浮点版本的算法,那对于中间部分的 / pixlecount操作就应该可以变为 *Invpixlecount,其中Invpixlecount = 1.0f/pixlecount,变除法为乘法,而且这部分计算还可以很容易的用SSE实现。我测试过,改进后的实现和解析opencv中Box Filter的实现并提出进一步加速的方案(源码共享)  这篇文好章的速度不相上下,但这里有个优势就是可以并行的。另外,最重要的一点时,当要计算上述浮点版半径版本的boxfilter时,积分图是不需要再次重新计算的,而积分图的计算所占的耗时至少有一半左右。因此,这个场合使用积分图版本的盒子滤波会更有优势。

     在内存占用方面,也可以做大量的优化工作,哪怕是对下取样图进行处理,第一,导向前必须把图像的字节数据归一化为0到1之间的浮点数据,很明显,我们只要下采样大小的归一化数据,那么这个过程就应该很自然的直接由原始大小图像映射到下取样的浮点数据,而不要再在中间转来转去, 这个下采样的内存占用大小为(W * H )/(S * S) * channel * sizeof(float) .第二,导向的中间的各过程用到了大量的中间变量,像原作者使用matlab的代码为了参数算法清楚,就是为每个中间数据分配内存,但是实际操作中,为节省资源,必须加以优化,我们注意观察,就会发现有些变量用完就不会再次使用了,当导向图和原图不同时,我总结了只需要4 * (W * H )/(S * S) * channel * sizeof(float)大小的内存,如果导向图和原图相同,则只需要2 * (W * H )/(S * S) * channel * sizeof(float),这个数据还是包含下采样图的内存占用的呢。考虑在均值滤波里还需要一份额外大小的内存,以及最后混合时的为了提速的 2 * (H / S) * W * channel * sizeof(float)的内存,当S=4时加起来也就是原图多一点点的内存。
   
     在一台I5的笔记本上,采用默认设置,以自身为导向图处理3000*2000的24位图像需要约55ms,如果是灰度图大概是20ms,这个和优化后的 boxblur速度基本一致,如果开启动多线程,比如开两个线程,还能提速25%左右,再多也无帮助了。
     
     共享下一个C#做的Demo,以便供有兴趣的朋友参考比较: http://files.cnblogs.com/files/Imageshop/SSE_Optimization_Demo.rar
 
 
 
     本文纯属计流水账,未做详细分析。
 
 
     

转:https://www.cnblogs.com/Imageshop/p/6658977.html



推荐阅读
  • 基于KVM的SRIOV直通配置及性能测试
    SRIOV介绍、VF直通配置,以及包转发率性能测试小慢哥的原创文章,欢迎转载目录?1.SRIOV介绍?2.环境说明?3.开启SRIOV?4.生成VF?5.VF ... [详细]
  • 深入探讨CPU虚拟化与KVM内存管理
    本文详细介绍了现代服务器架构中的CPU虚拟化技术,包括SMP、NUMA和MPP三种多处理器结构,并深入探讨了KVM的内存虚拟化机制。通过对比不同架构的特点和应用场景,帮助读者理解如何选择最适合的架构以优化性能。 ... [详细]
  • 开发笔记:2020 BJDCTF Re encode
    开发笔记:2020 BJDCTF Re encode ... [详细]
  • 本文作者分享了在阿里巴巴获得实习offer的经历,包括五轮面试的详细内容和经验总结。其中四轮为技术面试,一轮为HR面试,涵盖了大量的Java技术和项目实践经验。 ... [详细]
  • 本文深入探讨了C++对象模型中的一些细节问题,特别是虚拟继承和析构函数的处理。通过具体代码示例和详细分析,揭示了书中某些观点的不足之处,并提供了更合理的解释。 ... [详细]
  • dotnet 通过 Elmish.WPF 使用 F# 编写 WPF 应用
    本文来安利大家一个有趣而且强大的库,通过F#和C#混合编程编写WPF应用,可以在WPF中使用到F#强大的数据处理能力在GitHub上完全开源Elmis ... [详细]
  • 深入解析 Apache Shiro 安全框架架构
    本文详细介绍了 Apache Shiro,一个强大且灵活的开源安全框架。Shiro 专注于简化身份验证、授权、会话管理和加密等复杂的安全操作,使开发者能够更轻松地保护应用程序。其核心目标是提供易于使用和理解的API,同时确保高度的安全性和灵活性。 ... [详细]
  • 深入理解Shell脚本编程
    本文详细介绍了Shell脚本编程的基础概念、语法结构及其在操作系统中的应用。通过具体的示例代码,帮助读者掌握如何编写和执行Shell脚本。 ... [详细]
  • Startup 类配置服务和应用的请求管道。Startup类ASP.NETCore应用使用 Startup 类,按照约定命名为 Startup。 Startup 类:可选择性地包括 ... [详细]
  • 本文深入探讨了HTTP请求和响应对象的使用,详细介绍了如何通过响应对象向客户端发送数据、处理中文乱码问题以及常见的HTTP状态码。此外,还涵盖了文件下载、请求重定向、请求转发等高级功能。 ... [详细]
  • 深入解析Redis内存对象模型
    本文详细介绍了Redis内存对象模型的关键知识点,包括内存统计、内存分配、数据存储细节及优化策略。通过实际案例和专业分析,帮助读者全面理解Redis内存管理机制。 ... [详细]
  • JavaScript 基础语法指南
    本文详细介绍了 JavaScript 的基础语法,包括变量、数据类型、运算符、语句和函数等内容,旨在为初学者提供全面的入门指导。 ... [详细]
  • 本文探讨了在使用Azure Active Directory进行用户身份验证时,结合AddAuthentication和RequireAuthenticatedUser的必要性及其潜在冗余问题。 ... [详细]
  • 作为一名专业的Web前端工程师,掌握HTML和CSS的命名规范是至关重要的。良好的命名习惯不仅有助于提高代码的可读性和维护性,还能促进团队协作。本文将详细介绍Web前端开发中常用的HTML和CSS命名规范,并提供实用的建议。 ... [详细]
  • 深入理解Redis的数据结构与对象系统
    本文详细探讨了Redis中的数据结构和对象系统的实现,包括字符串、列表、集合、哈希表和有序集合等五种核心对象类型,以及它们所使用的底层数据结构。通过分析源码和相关文献,帮助读者更好地理解Redis的设计原理。 ... [详细]
author-avatar
三哥是二姐的三哥
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有