2008 年在一个 PS 讨论群里,有网友不解 Photoshop 的高斯模糊中的半径是什么含义,因此当时我写了这篇文章:

  对Photoshop高斯模糊滤镜的算法总结

  在那篇文章中,主要讲解了高斯模糊中的半径的含义,是二维正态分布的方差的平方根,并且给出了算法的理论描述。现在我又打算把该算法用 c++ 实现出来,于是有了下面的这个 DEMO。

  起初我是按照算法理论直接实现,即使用了二维高斯模板,结果发现处理时间很长,对一个图片竟然能达到大约数分钟之久。这样肯定是不对的,所以我百度了一下,发现这个问题应该采用分别进行两次一维高斯模糊就可以了[1],这样算法的时间复杂度的一个系数,就从 O ( σ ^2 ) 降低到了 O ( σ )。这样算法于是速度提高到了毫秒级。下表给出分别用二维模糊的原始方法,和两次一维模糊累加的方法的算法成本比较:

算法 时间复杂度 空间复杂度
(1) 二维高斯模糊 O(σ ^ 2) * O(n) (慢) O(σ ^ 2) (较小)
(2) 两次一维高斯模糊的累加 O(σ) * O(n) (快) O(n) + O(σ) ≈ O(n) (较大)

  其中:σ :方差平方根(Photoshop 中的高斯模糊半径);n = w * h (图片的像素数量)。具体时间和图片大小和高斯半径的大小有关,一个粗略的大概情况为,算法(1)的耗时为分钟级,算法(2)的耗时为毫秒到秒级。可见算法(2)比算法(1)速度更快,但相比算法(1)来说算法(2)具有较高的空间需求。

  注:当然上面的空间复杂度并不是绝对的,例如,可以通过对图像进行串行的切片处理,既可减小算法(2)的空间需求。

  两种算法在高斯半径为常数条件下,都是关于图片大小的线性算法,区别在于常数系数的大小不同,前者是高斯半径(模板尺寸)的平方级,后者是高斯半径(模板尺寸)的线性级别。这个改进,非常类似于我此前有一篇博客中给出的,对一个油画效果滤镜的算法改进,也是通过把常数系数,从模板尺寸的平方级别降低到线性级别,使算法速度获得提高的。

  在理论上,高斯模板是无边界和无限扩展的一个二维曲面,在实现时,就必须对这个曲面截断为有限大的二维模板。那么在哪里截断呢?根据下图所示的一维正态分布贡献:

  

  图1. 正态分布的贡献比

  此图来自参考资料 [1],根据资料文中叙述,此图实际来源于(Maybe blocked by the GFW)

  http://zh.wikipedia.org/wiki/File:Standard_deviation_diagram.svg

  从图 1 中可以看到,在 3σ 以外的贡献比例非常小,为 0.1 %,因此我们截断模板时,对模板边界定义为 3σ ;

  int r = ( int ) ( sigma * + 0.5 );  // 完整模板的逻辑尺寸:( 2 * r + 1 ) * ( 2 * r + 1 );

  二维高斯模板的计算公式是:

  

  下图给出了二维模板的可视化结果。采用的可视化方法是,根据上面的公式和模板边界,生成二维高斯模板,然后取一个缩放因子 f = 255 / 模板中心点的数据,以此缩放因子把模板数据等比缩放,然后绘制成灰度图片,这样中心点的亮度就被提高到最亮。可视化效果中,每个单元格对应着一个模板数据,单元格大小为 8 * 8 或者 16 * 16 像素。

  

  图 2. 二维模板的可视化结果

  图 2 中,左侧是人们常见的 3 x 3 模板(σ ≈  0.849),围绕中心点的 3 x 3 的浮点数据为:

sigma = 0.849:

0.055 0.110 0.055
0.110 0.221 0.110
0.055 0.110 0.055

  采用算法(2),要完成高斯模糊,对图片分别进行两个方向的一维高斯模糊即可。例如,先对图片进行水平方向的模糊,得到中间结果,然后再对这个中间结果进行垂直方向的模糊,即得到最终结果。下图是一个演示图,给出了原图在两个方向上分别单独进行一维高斯模糊的结果,以及最终的结果:

  

  图3. 算法(2)中一维模糊的中间结果

  仅在这个图片的例子中,我把我写的算法的处理结果,在 Photoshop 中打开和 Photoshop 自带的高斯模糊的处理结果做差值对比,发现两者是相同的。

  我实现的 DEMO 程序(Windows 平台)的界面如下所示:

  

  图4. DEMO 程序的主窗口 UI

  通过点击菜单 - 可视化 - 二维高斯模板可以在右侧的视图中生成一个灰度图片,即二维高斯模板的可视化结果。

  

  在程序界面的客户区下方有一个控制面板,可以选择高斯模糊的算法参数,高斯半径的意义和 Photoshop 中的高斯模糊半径的意义相同,都是算法中的 σ。

  算法参数中:

  (1)支持多线程处理。根据我的观察,线程数设置为和 CPU 核心数相同是比较合适的。线程数比 CPU 核心数更多,也是没有什么意义的,因为算法执行时,CPU 已经满负荷运转了。开启更多线程,也不能再提高速度了。

  假设 CPU 核数为 p,开启的多线程数量 >= p,则算法速度大约为单线程处理的 p 倍。(当 CPU 满负荷时,线程数量取得更大,也没有提高速度的意义了)

  注:此处的 CPU 核心数应该为 CPU 的物理核心数,而非模拟出来的多核数目。

  (2)浮点类型:支持 float 和 double。它是高斯模板的数据的类型,也是进行像素加权累加时的数据类型,根据我的观察,float 和 double 的速度相差不大。基本相同。

  (3)高斯半径:即 σ。算法的常数系数为 O(σ)。很显然,σ 的值取得越大,算法耗时将会越长。在 DEMO 中,其允许范围和 Photoshop 的要求一致,是 [0.1, 250]。

  在实现算法时,我也尝试了对 255 个灰度值 * 模板数据的结果进行缓存和查表处理,但是发现不能有效提高速度,所以最终我放弃了这种方法。这可能是因为,算法的计算只是一个浮点乘法,对数据的读取动作,并不能做到比浮点乘法更快。所以这里采用缓存也就显得没有必要了。

  在本 DEMO 中,滤镜处理是放在 UI 线程中进行的,这使得在滤镜处理时间较长时(例如高斯半径取值很大,图片也很大),界面会有些卡,可以把滤镜处理动作放在一个新建的后台线程中执行。这是比较容易实现的。

  对算法的使用方法:

  在 C++ 程序中,使用我写的这个算法是非常简单的,例如:

#include "GaussBlurFilter.hpp"

CGaussBlurFilter<double> _filter;
_filter.SetSigma(3.5); // 设置高斯半径
_filter.SetMultiThreads(true, ); // 开启多线程,用户建议的线程数为 4; // lpSrcBits / lpDestBits: 像素数据的起始地址,必须以 4 bytes 对齐,
// 注意:不论高度为正或者负,lpBits 都必须为所有像素中地址值最低的那个像素的地址。
// bmWidth, bmHeight: 图像宽度和高度(像素),高度允许为负值;
// bpp: 位深度,支持 8(灰度), 24(真彩色), 32
_filter.Filter(lpSrcBits, lpDestBits, bmWidth, bmHeight, bpp);

  需要注意的是,在多线程处理中,我使用了 Windows API (例如 CreateThread)等,这使得 GaussBlurFilter.hpp 目前只能用在 Windows 平台,如果要在其他平台使用,应当修改和多线程有关的 API 函数调用。

  高度值可以为正也可以为负,但像素数据的地址 lpBits 都必须是所有像素中,地址值最小的那个像素的地址。即,假设图片左上角点的坐标为原点,如果图片高度为正数(bottom - up),则 lpBits 是左下角像素 (col = 0,row = height - 1)的地址。如果图片高度为负数(top-down),则 lpBits 是左上角像素(col = 0,row = 0) 的地址。图像数据的扫描行宽度必须以 4 Bytes 对齐,即通过下面的公式计算扫描行宽度:

  int stride = ( bmWidth * bpp + 31 ) / 32 * 4; //扫描行宽度,对齐到 4 Bytes

  (上式为编程语言表达,非数学表达,即利用了整数除法对小数部分的截断性。)

  bpp:图像的像素位深度。只支持 8 (灰度索引图像),24,32 这几个值。对于 32 bpp 的图像来说,最后一个像素通道是表征像素的不透明度,也就是 alpha,对于 alpha 如何参与到算法中,我想了下,有多种处理方法,但都好像没有什么容易理解的物理意义,所以在代码里我忽略了 alpha 通道。

  【相关下载】:

  (1)Demo 可执行文件(包含 GaussBlurFilter.hpp):GaussBlurDemo_Bin.zip

  (2)Demo 完整源码(包含 GaussBlurFilter.hpp 和 可执行文件):GaussBlurDemo_Src.zip

  

  【参考资料】

  [1]. 高斯模糊算法的实现和优化;


[注] 文中的公式,采用如下网址生成:http://www.codecogs.com/latex/eqneditor.php

参考自:博客中插入公式——之在线数学公式生成;

高斯模糊算法的 C++ 实现的更多相关文章

  1. .net版高斯模糊算法

    最近挺多人找高斯算法,本人贴上一个高斯模糊算法类,希望可以帮助到大家.算法的效率还是可以接受的. #region 高斯模糊算法 /// <summary> /// 高斯模糊算法 /// & ...

  2. 简单的java高斯模糊算法

    import java.awt.Color; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOEx ...

  3. SSE图像算法优化系列二:高斯模糊算法的全面优化过程分享(一)。

    这里的高斯模糊采用的是论文<Recursive implementation of the Gaussian filter>里描述的递归算法. 仔细观察和理解上述公式,在forward过程 ...

  4. canvas高斯模糊算法

    对于模糊图片这个效果的实现,其实css3中的filter属性也能够实现,但是这个属性的兼容性不是很好,所以我们通常不用这种方法实现,而使用canvas配合JS实现. <span style=&q ...

  5. 传统高斯模糊与优化算法(附完整C++代码)

    高斯模糊(英语:Gaussian Blur),也叫高斯平滑,是在Adobe Photoshop.GIMP以及Paint.NET等图像处理软件中广泛使用的处理效果,通常用它来减少图像噪声以及降低细节层次 ...

  6. webgl智慧楼宇发光效果算法系列之高斯模糊

    webgl智慧楼宇发光效果算法系列之高斯模糊 如果使用过PS之类的图像处理软件,相信对于模糊滤镜不会陌生,图像处理软件提供了众多的模糊算法.高斯模糊是其中的一种. 在我们的智慧楼宇的项目中,要求对楼宇 ...

  7. Android开发学习之路-动态高斯模糊怎么做

    什么是高斯模糊? 高斯模糊(英语:Gaussian Blur),也叫高斯平滑,是在Adobe Photoshop.GIMP以及Paint.NET等图像处理软件中广泛使用的处理效果,通常用它来减少图像噪 ...

  8. o(1)复杂度之双边滤波算法的原理、流程、实现及效果。

    一.引言     双边滤波在图像处理领域中有着广泛的应用,比如去噪.去马赛克.光流估计等等,最近,比较流行的Non-Local算法也可以看成是双边滤波的一种扩展.自从Tomasi et al等人提出该 ...

  9. 半径无关单核单线程最快速高斯模糊实现(附完整C代码)

    之前,俺也发过不少快速高斯模糊算法. 俺一般认为,只要处理一千六百万像素彩色图片,在2.2GHz的CPU上单核单线程超过1秒的算法,都是不快的. 之前发的几个算法,在俺2.2GHz的CPU上耗时都会超 ...

随机推荐

  1. Eclipse/IDEA使用小技巧

    使用IDEA,先将keymap改为eclipse形式 1.搜索技巧: f4:列举所有类树状结构 Ctrl+F:搜索特定词 Ctrl+T:列举所有子类 Ctrl+O:快速检索想要的方法 Ctrl+Shi ...

  2. weblogic启动错误

    一 .weblogic启动错误:java.lang.AccessertionError:java.lang.reflect.InvocationTargetException <unable t ...

  3. Android开发教程:shape和selector的结合使用

    shape和selector是Android UI设计中经常用到的,比如我们要自定义一个圆角Button,点击Button有些效果的变化,就要用到shape和selector.可以这样说,shape和 ...

  4. XSS跨站脚本小结

    XSS漏洞验证经常遇到一些过滤,如何进行有效验证和绕过过滤呢,这里小结一下常见的一些标签,如<a><img>等. 参考链接:http://www.jb51.net/tools/ ...

  5. HTML5视频Video 音频Audio

    视频协议 视频格式 Flash HTML5 HTTP flv HTTP f4v HTTP mp4 HTTP m3u8 HTTP webm HTTP ogg RTMP flv RTMP f4v RTMP ...

  6. jstl 标签库的使用

    JSTL 核心标签库 使用   JSTL 核心标签库标签共有13个,功能上分为4类: 1.表达式控制标签:out.set.remove.catch 2.流程控制标签:if.choose.when.ot ...

  7. LeetCode-Search in Rotated Sorted Array

    Suppose a sorted array is rotated at some pivot unknown to you beforehand. (i.e., 0 1 2 4 5 6 7 migh ...

  8. [原创]cocos2d-x研习录-第三阶 特性之按键与虚拟键盘

    Cocos2D-x引擎支持按键事件,它能检测设备的键盘输入并处理相应的事件.而基于不同操作系统的移动设备,可供用户操作的按键数量和功能都存在差异.   Cocos2D-x使用CCKeypadDeleg ...

  9. Redis -- 02 配置文件解析

    redis的配置文件为 redis.conf, 使用 ./redis-server /path/to/redis.conf 可以根据自定义的配置启动redis实例 include // 引入其他配置文 ...

  10. KnockoutJS中父元素有click绑定引起checked绑定时失效

    KnockoutJS中的checked绑定如果父元素有click绑定,会出现状态点击没反应,实际KO的值已经变化的情况. 这种情况下应该在checked绑定的元素上除了阻止事件冒泡,另外还需要额外加上 ...