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

高斯模糊算法的C++实现

转自:http:www.cnblogs.comhoodlum1980p4528486.html2008年在一个PS讨论群里,有网友不解Photoshop的高斯模糊中的半径是什么含

 转自:http://www.cnblogs.com/hoodlum1980/p/4528486.html

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 * 3 + 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); // 开启多线程,用户建议的线程数为 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

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


推荐阅读
  • 基于移动平台的会展导游系统APP设计与实现的技术介绍与需求分析
    本文介绍了基于移动平台的会展导游系统APP的设计与实现过程。首先,对会展经济和移动互联网的概念进行了简要介绍,并阐述了将会展引入移动互联网的意义。接着,对基础技术进行了介绍,包括百度云开发环境、安卓系统和近场通讯技术。然后,进行了用户需求分析和系统需求分析,并提出了系统界面运行流畅和第三方授权等需求。最后,对系统的概要设计进行了详细阐述,包括系统前端设计和交互与原型设计。本文对基于移动平台的会展导游系统APP的设计与实现提供了技术支持和需求分析。 ... [详细]
  • 线程漫谈——线程基础
    本系列意在记录Windwos线程的相关知识点,包括线程基础、线程调度、线程同步、TLS、线程池等。进程与线程理解线程是至关重要的,每个进程至少有一个线程,进程是线程的容器,线程才是真正的执行体,线程必 ... [详细]
  • Skywalking系列博客1安装单机版 Skywalking的快速安装方法
    本文介绍了如何快速安装单机版的Skywalking,包括下载、环境需求和端口检查等步骤。同时提供了百度盘下载地址和查询端口是否被占用的命令。 ... [详细]
  • 嵌入式处理器的架构与内核发展历程
    本文主要介绍了嵌入式处理器的架构与内核发展历程,包括不同架构的指令集的变化,以及内核的流水线和结构。通过对ARM架构的分析,可以更好地理解嵌入式处理器的架构与内核的关系。 ... [详细]
  • 本文介绍了操作系统的定义和功能,包括操作系统的本质、用户界面以及系统调用的分类。同时还介绍了进程和线程的区别,包括进程和线程的定义和作用。 ... [详细]
  • 如何使用PLEX播放组播、抓取信号源以及设置路由器
    本文介绍了如何使用PLEX播放组播、抓取信号源以及设置路由器。通过使用xTeve软件和M3U源,用户可以在PLEX上实现直播功能,并且可以自动匹配EPG信息和定时录制节目。同时,本文还提供了从华为itv盒子提取组播地址的方法以及如何在ASUS固件路由器上设置IPTV。在使用PLEX之前,建议先使用VLC测试是否可以正常播放UDPXY转发的iptv流。最后,本文还介绍了docker版xTeve的设置方法。 ... [详细]
  • 玩转直播系列之消息模块演进(3)
    一、背景即时消息(IM)系统是直播系统重要的组成部分,一个稳定的,有容错的,灵活的,支持高并发的消息模块是影响直播系统用户体验的重要因素。IM长连接服务在直播系统有发挥着举足轻重的 ... [详细]
  • 翻译 | 编写SVG的口袋指南(上)
    作者:DDU(沪江前端开发工程师)本文是原文翻译,转载请注明作者及出处。简介ScalableVectorGraphics(SVG)是在XML中描述二维图形的语言。这些图形由路径,图 ... [详细]
  • 今天周六,原则上要休息,但想到下周还有一堆任务,还是先做一部分工作吧,就把之前做的票面设计器改了改,增加了上传图片和更换背景底图的功能。现在打算整理下这个设计器,也算对齐一个总结。不过这属于我们部门的 ... [详细]
  • Metasploit攻击渗透实践
    本文介绍了Metasploit攻击渗透实践的内容和要求,包括主动攻击、针对浏览器和客户端的攻击,以及成功应用辅助模块的实践过程。其中涉及使用Hydra在不知道密码的情况下攻击metsploit2靶机获取密码,以及攻击浏览器中的tomcat服务的具体步骤。同时还讲解了爆破密码的方法和设置攻击目标主机的相关参数。 ... [详细]
  • 本文介绍了一些好用的搜索引擎的替代品,包括网盘搜索工具、百度网盘搜索引擎等。同时还介绍了一些笑话大全、GIF笑话图片、动态图等资源的搜索引擎。此外,还推荐了一些迅雷快传搜索和360云盘资源搜索的网盘搜索引擎。 ... [详细]
  • Python瓦片图下载、合并、绘图、标记的代码示例
    本文提供了Python瓦片图下载、合并、绘图、标记的代码示例,包括下载代码、多线程下载、图像处理等功能。通过参考geoserver,使用PIL、cv2、numpy、gdal、osr等库实现了瓦片图的下载、合并、绘图和标记功能。代码示例详细介绍了各个功能的实现方法,供读者参考使用。 ... [详细]
  • 基于Socket的多个客户端之间的聊天功能实现方法
    本文介绍了基于Socket的多个客户端之间实现聊天功能的方法,包括服务器端的实现和客户端的实现。服务器端通过每个用户的输出流向特定用户发送消息,而客户端通过输入流接收消息。同时,还介绍了相关的实体类和Socket的基本概念。 ... [详细]
  • Window10+anaconda+python3.5.4+ tensorflow1.5+ keras(GPU版本)安装教程 ... [详细]
  • linux qt打开常用文件格式,设置Linux Qt文件默认打开方式为QtCreator
    Linux自定义文件打开方式也可参照文本抱歉,本文前段时间写的ubuntu下的Qt工程文件默认打开方式是不好用的,因为其他的文本文件也会受到影响,强迫症患者,每次打开Qt工程都是先 ... [详细]
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社区 版权所有