转自:高斯模糊实现小结

转自:http://blog.csdn.net/zddblog/article/details/7450033

高斯模糊是一种图像滤波器,它使用正态分布(高斯函数)计算模糊模板,并使用该模板与原图像做卷积运算,达到模糊图像的目的。

N维空间正态分布方程为:

其中,σ是正态分布的标准差,σ值越大,图像越模糊(平滑)。r为模糊半径,模糊半径是指模板元素到模板中心的距离。如二维模板大小为m*n,则模板上的元素(x,y)对应的高斯计算公式为:

在二维空间中,这个公式生成的曲面的等高线是从中心开始呈正态分布的同心圆。分布不为 零的像素组成的卷积矩阵与原始图像做变换。每个像素的值都是周围相邻像素值的加权平均。原始像素的值有最大的高斯分布值,所以有最大的权重,相邻像素随着 距离原始像素越来越远,其权重也越来越小。这样进行模糊处理比其它的均衡模糊滤波器更高地保留了边缘效果。

理论上来讲,图像中每点的分布都不为零,这也就是说每个像素的计算都需要包含整幅图 像。在实际应用中,在计算高斯函数的离散近似时,在大概3σ距离之外的像素都可以看作不起作用,这些像素的计算也就可以忽略。通常,图像处理程序只需要计 算(6σ+1)*(6σ+1)的矩阵就可以保证相关像素影响。

1、使用给定高斯模板平滑图像函数

σ=0.84089642的7行7列高斯模糊矩阵为:

现使用该模板对源图像做模糊处理,其函数如下:

[cpp] view plaincopy

  1. //高斯平滑
  2. //未使用sigma,边缘无处理
  3. void GaussianTemplateSmooth(const Mat &src, Mat &dst, double sigma)
  4. {
  5. //高斯模板(7*7),sigma = 0.84089642,归一化后得到
  6. static const double gaussianTemplate[7][7] =
  7. {
  8. {0.00000067, 0.00002292, 0.00019117, 0.00038771, 0.00019117, 0.00002292, 0.00000067},
  9. {0.00002292, 0.00078633, 0.00655965, 0.01330373, 0.00655965, 0.00078633, 0.00002292},
  10. {0.00019117, 0.00655965, 0.05472157, 0.11098164, 0.05472157, 0.00655965, 0.00019117},
  11. {0.00038771, 0.01330373, 0.11098164, 0.22508352, 0.11098164, 0.01330373, 0.00038771},
  12. {0.00019117, 0.00655965, 0.05472157, 0.11098164, 0.05472157, 0.00655965, 0.00019117},
  13. {0.00002292, 0.00078633, 0.00655965, 0.01330373, 0.00655965, 0.00078633, 0.00002292},
  14. {0.00000067, 0.00002292, 0.00019117, 0.00038771, 0.00019117, 0.00002292, 0.00000067}
  15. };
  16. dst.create(src.size(), src.type());
  17. uchar* srcData = src.data;
  18. uchar* dstData = dst.data;
  19. for(int j = 0; j < src.cols-7; j++)
  20. {
  21. for(int i = 0; i < src.rows-7; i++)
  22. {
  23. double acc = 0;
  24. double accb = 0, accg = 0, accr = 0;
  25. for(int m = 0; m < 7; m++)
  26. {
  27. for(int n = 0; n < 7; n++)
  28. {
  29. if(src.channels() == 1)
  30. acc += *(srcData + src.step * (i+n) + src.channels() * (j+m)) * gaussianTemplate[m][n];
  31. else
  32. {
  33. accb += *(srcData + src.step * (i+n) + src.channels() * (j+m) + 0) * gaussianTemplate[m][n];
  34. accg += *(srcData + src.step * (i+n) + src.channels() * (j+m) + 1) * gaussianTemplate[m][n];
  35. accr += *(srcData + src.step * (i+n) + src.channels() * (j+m) + 2) * gaussianTemplate[m][n];
  36. }
  37. }
  38. }
  39. if(src.channels() == 1)
  40. *(dstData + dst.step * (i+3) + dst.channels() * (j+3))=(int)acc;
  41. else
  42. {
  43. *(dstData + dst.step * (i+3) + dst.channels() * (j+3) + 0)=(int)accb;
  44. *(dstData + dst.step * (i+3) + dst.channels() * (j+3) + 1)=(int)accg;
  45. *(dstData + dst.step * (i+3) + dst.channels() * (j+3) + 2)=(int)accr;
  46. }
  47. }
  48. }
  49. }

其效果如图1所示,7*7的高斯模板与源图像做卷积运算时,会产生半径为3的边缘,在不精确的图像处理中,可用源图像像素填充,或者去掉边缘。

2、二维高斯模糊函数

上述的例子中,如何求得高斯模糊矩阵是高斯模糊的关键。根据高斯函数的性质,图像处理程序只需要计算(6σ+1)*(6σ+1)的矩阵就可以保证相关像素影响。因此,可根据σ的值确定高斯模糊矩阵的大小。高斯矩阵可利用公式(1-2)计算,并归一化得到。归一化是保证高斯矩阵的值在[0,1]之间,

其处理函数如下:

[cpp] view plaincopy

  1. void GaussianSmooth2D(const Mat &src, Mat &dst, double sigma)
  2. {
  3. if(src.channels() != 1)
  4. return;
  5. //确保sigma为正数
  6. sigma = sigma > 0 ? sigma : 0;
  7. //高斯核矩阵的大小为(6*sigma+1)*(6*sigma+1)
  8. //ksize为奇数
  9. int ksize = cvRound(sigma * 3) * 2 + 1;
  10. //  dst.create(src.size(), src.type());
  11. if(ksize == 1)
  12. {
  13. src.copyTo(dst);
  14. return;
  15. }
  16. dst.create(src.size(), src.type());
  17. //计算高斯核矩阵
  18. double *kernel = new double[ksize*ksize];
  19. double scale = -0.5/(sigma*sigma);
  20. const double PI = 3.141592653;
  21. double cons = -scale/PI;
  22. double sum = 0;
  23. for(int i = 0; i < ksize; i++)
  24. {
  25. for(int j = 0; j < ksize; j++)
  26. {
  27. int x = i-(ksize-1)/2;
  28. int y = j-(ksize-1)/2;
  29. kernel[i*ksize + j] = cons * exp(scale * (x*x + y*y));
  30. sum += kernel[i*ksize+j];
  31. //          cout << " " << kernel[i*ksize + j];
  32. }
  33. //      cout <<endl;
  34. }
  35. //归一化
  36. for(int i = ksize*ksize-1; i >=0; i--)
  37. {
  38. *(kernel+i) /= sum;
  39. }
  40. //图像卷积运算,无边缘处理
  41. for(int j = 0; j < src.cols-ksize; j++)
  42. {
  43. for(int i = 0; i < src.rows-ksize; i++)
  44. {
  45. double acc = 0;
  46. for(int m = 0; m < ksize; m++)
  47. {
  48. for(int n = 0; n < ksize; n++)
  49. {
  50. acc += *(srcData + src.step * (i+n) + src.channels() * (j+m)) * kernel[m*ksize+n];
  51. }
  52. }
  53. *(dstData + dst.step * (i + (ksize - 1)/2) + (j + (ksize -1)/2)) = (int)acc;
  54. }
  55. }
  56. delete []kernel;
  57. }

利用该函数,取σ=0.84089642即可得到上例中7*7的模板,该模板数据存在kernel中。利用该函数计算的归一化后的3*3,5*5阶高斯模板如表2,3所示:

由上表可以看出,高斯模板是中心对称的。

模糊效果如图2所示。

对图2中边缘的处理:

[cpp] view plaincopy

  1. ...

[cpp] view plaincopy

  1. int center = (ksize-1) /2;
  2. //图像卷积运算,处理边缘
  3. for(int j = 0; j < src.cols; j++)
  4. {
  5. for(int i = 0; i < src.rows; i++)
  6. {
  7. double acc = 0;
  8. for(int m = -center, c = 0; m <= center; m++, c++)
  9. {
  10. for(int n = -center, r = 0; n <= center; n++, r++)
  11. {
  12. if((i+n) >=0 && (i+n) < src.rows && (j+m) >=0 && (j+m) < src.cols)
  13. {
  14. acc += *(srcData + src.step * (i+n) + src.channels() * (j+m)) * kernel[r*ksize+c];
  15. }
  16. }
  17. }
  18. *(dstData + dst.step * (i) + (j)) = (int)acc;
  19. }
  20. }

[cpp] view plaincopy

  1. ...

结果图为:

如上图所示,边缘明显变窄,但是存在黑边。

3、改进的高斯模糊函数

上述的二维高斯模糊函数GaussianSmooth2D达到了高斯模糊图像的目的,但是如图2所示,会因模板的关系而造成边缘图像缺失,σ越大,缺失像素越多,额外的边缘处理会增加计算量。并且当σ变大时,高斯模板(高斯核)和卷积运算量将大幅度提高。根据高斯函数的可分离性,可对二维高斯模糊函数进行改进。

高斯函数的可分离性是指使用二维矩阵变换得到的效果也可以通过在水平方向进行一维高斯
矩阵变换加上竖直方向的一维高斯矩阵变换得到。从计算的角度来看,这是一项有用的特性,因为这样只需要O(n*M*n)+O(m*M*N)次计算,而二维
不可分的矩阵则需要O(m*n*M*n)次计算,其中,m,n为高斯矩阵的维数,M,N为二维图像的维数。

另外,两次一维的高斯卷积将消除二维高斯矩阵所产生的边缘。

(关于消除边缘的论述如下图2.4所示, 对用模板矩阵超出边界的部分——虚线框,将不做卷积计算。如图2.4中x方向的第一个模板1*5,将退化成1*3的模板,只在图像之内的部分做卷积。)

改进的高斯模糊函数如下:

[cpp] view plaincopy

  1. void GaussianSmooth(const Mat &src, Mat &dst, double sigma)
  2. {
  3. if(src.channels() != 1 && src.channels() != 3)
  4. return;
  5. //
  6. sigma = sigma > 0 ? sigma : -sigma;
  7. //高斯核矩阵的大小为(6*sigma+1)*(6*sigma+1)
  8. //ksize为奇数
  9. int ksize = ceil(sigma * 3) * 2 + 1;
  10. //cout << "ksize=" <<ksize<<endl;
  11. //  dst.create(src.size(), src.type());
  12. if(ksize == 1)
  13. {
  14. src.copyTo(dst);
  15. return;
  16. }
  17. //计算一维高斯核
  18. double *kernel = new double[ksize];
  19. double scale = -0.5/(sigma*sigma);
  20. const double PI = 3.141592653;
  21. double cons = 1/sqrt(-scale / PI);
  22. double sum = 0;
  23. int kcenter = ksize/2;
  24. int i = 0, j = 0;
  25. for(i = 0; i < ksize; i++)
  26. {
  27. int x = i - kcenter;
  28. *(kernel+i) = cons * exp(x * x * scale);//一维高斯函数
  29. sum += *(kernel+i);
  30. //      cout << " " << *(kernel+i);
  31. }
  32. //  cout << endl;
  33. //归一化,确保高斯权值在[0,1]之间
  34. for(i = 0; i < ksize; i++)
  35. {
  36. *(kernel+i) /= sum;
  37. //      cout << " " << *(kernel+i);
  38. }
  39. //  cout << endl;
  40. dst.create(src.size(), src.type());
  41. Mat temp;
  42. temp.create(src.size(), src.type());
  43. uchar* srcData = src.data;
  44. uchar* dstData = dst.data;
  45. uchar* tempData = temp.data;
  46. //x方向一维高斯模糊
  47. for(int y = 0; y < src.rows; y++)
  48. {
  49. for(int x = 0; x < src.cols; x++)
  50. {
  51. double mul = 0;
  52. sum = 0;
  53. double bmul = 0, gmul = 0, rmul = 0;
  54. for(i = -kcenter; i <= kcenter; i++)
  55. {
  56. if((x+i) >= 0 && (x+i) < src.cols)
  57. {
  58. if(src.channels() == 1)
  59. {
  60. mul += *(srcData+y*src.step+(x+i))*(*(kernel+kcenter+i));
  61. }
  62. else
  63. {
  64. bmul += *(srcData+y*src.step+(x+i)*src.channels() + 0)*(*(kernel+kcenter+i));
  65. gmul += *(srcData+y*src.step+(x+i)*src.channels() + 1)*(*(kernel+kcenter+i));
  66. rmul += *(srcData+y*src.step+(x+i)*src.channels() + 2)*(*(kernel+kcenter+i));
  67. }
  68. sum += (*(kernel+kcenter+i));
  69. }
  70. }
  71. if(src.channels() == 1)
  72. {
  73. *(tempData+y*temp.step+x) = mul/sum;
  74. }
  75. else
  76. {
  77. *(tempData+y*temp.step+x*temp.channels()+0) = bmul/sum;
  78. *(tempData+y*temp.step+x*temp.channels()+1) = gmul/sum;
  79. *(tempData+y*temp.step+x*temp.channels()+2) = rmul/sum;
  80. }
  81. }
  82. }
  83. //y方向一维高斯模糊
  84. for(int x = 0; x < temp.cols; x++)
  85. {
  86. for(int y = 0; y < temp.rows; y++)
  87. {
  88. double mul = 0;
  89. sum = 0;
  90. double bmul = 0, gmul = 0, rmul = 0;
  91. for(i = -kcenter; i <= kcenter; i++)
  92. {
  93. if((y+i) >= 0 && (y+i) < temp.rows)
  94. {
  95. if(temp.channels() == 1)
  96. {
  97. mul += *(tempData+(y+i)*temp.step+x)*(*(kernel+kcenter+i));
  98. }
  99. else
  100. {
  101. bmul += *(tempData+(y+i)*temp.step+x*temp.channels() + 0)*(*(kernel+kcenter+i));
  102. gmul += *(tempData+(y+i)*temp.step+x*temp.channels() + 1)*(*(kernel+kcenter+i));
  103. rmul += *(tempData+(y+i)*temp.step+x*temp.channels() + 2)*(*(kernel+kcenter+i));
  104. }
  105. sum += (*(kernel+kcenter+i));
  106. }
  107. }
  108. if(temp.channels() == 1)
  109. {
  110. *(dstData+y*dst.step+x) = mul/sum;
  111. }
  112. else
  113. {
  114. *(dstData+y*dst.step+x*dst.channels()+0) = bmul/sum;
  115. *(dstData+y*dst.step+x*dst.channels()+1) = gmul/sum;
  116. *(dstData+y*dst.step+x*dst.channels()+2) = rmul/sum;
  117. }
  118. }
  119. }
  120. delete[] kernel;
  121. }

该函数中使用的水平方向和垂直方向的高斯矩阵为同一矩阵,实际计算时可根据需要取不同。

模糊效果如图3所示:

比较

使用GetTickCount()进行比较,GetTickCount()函数的精度为1ms。

以下表格中的数据均为作者机器上的某两次运行结果取均值,编程环境为vs2010+opencv2.2

上表中Debug版本的GaussianTemplateSmooth竟然比GaussianSmooth2D运行时间长,难道是二维数组比不上一维指针,或者是Debug版本的问题?实验结果确实如上。

将本文所写函数与opencv2.2提供的高斯模糊函数GaussianBlur一起进行比较。

结论

如上表4,5所示,对GaussianSmooth2D的改进函数GaussianSmooth,当越大时,提速效果越明显,这种速度的改进在Debug模式下尤为明显。无论是在Debug,还是在Release模式下,Opencv2.2提供的GaussianBlur完胜本文所用的函数。建议在学习算法时参考本文内容,实际项目中使用GaussianBlur。

本例代码键连接:http://download.csdn.net/detail/zddmail/4217704

时间: 2024-11-02 16:27:26

转自:高斯模糊实现小结的相关文章

SVG图片技术小结

今天在公司没事,研究了一下最近流行的SVG技术,发现,随着css3的不断流行,和浏览器技术的发展,SVG将会取代网站大量图片,成为网站图片展现的主流. AI是我们常用的矢量图编辑器,现在AI可以直接另存SVG图片,SVG图片相比传统图片,占位更小,浏览更方便!而且可扩展性更强! 下面看一个SVG的例子: <?xml version="1.0" standalone="no"?> <!DOCTYPE svg PUBLIC "-//W3C//

Unity3d 经验小结

Unity3d 经验小结 文本教程 你是第2541个围观者 0条评论 供稿者:Jamesgary 标签:unity3d教程 Fbx.贴图导入Unity时的注意事项: 在导出Fbx之前,Maya中已经对物体连接了正确的贴图,并且贴图文件名不能更改. 把Fbx和贴图放在同一文件夹内,直接把此文件加拖入Unity内.此时Diffuse贴图应能自动识别. 此时,对贴图修改后,在Unity中更新贴图(删除老贴图,导入新贴图,并保持命名,路经一致),模型能够自动更新. 假如Unity中删除贴图,关闭Unit

使用Apache POI导出Excel小结--导出XLS格式文档

使用Apache POI导出Excel小结 关于使用Apache POI导出Excel我大概会分三篇文章去写 使用Apache POI导出Excel小结--导出XLS格式文档 使用Apache POI导出Excel小结--导出XLSX格式文档 使用Apache POI导出Excel--大数量导出 导出XLS格式文档 做企业应用项目难免会有数据导出到Excel的需求,最近在使用其,并对导出Excel封装成工具类开放出来供大家参考.关于Apache POI Excel基本的概念与操作我在这里就不啰嗦

【转载】小结一下linux 2.6内核的四种IO调度算法

在LINUX 2.6中,有四种关于IO的调度算法,下面综合小结一下: 1) NOOP NOOP算法的全写为No Operation.该算法实现了最最简单的FIFO队列,所有IO请求大致按照先来后到的顺序进行操作.之所以说“大致”,原因是NOOP在FIFO的基础上还做了相邻IO请求的合并,并不是完完全全按照先进先出的规则满足IO请求.NOOP假定I/O请求由驱动程序或者设备做了优化或者重排了顺序(就像一个智能控制器完成的工作那样).在有些SAN环境下,这个选择可能是最好选择.Noop 对于 IO

一种高斯模糊渐变动画的实现-b

关于高斯模糊的方式有很多种,但是如果需要模糊渐变,那么对这种高斯模糊算法的性能要求是比较高的,今天这里重点不讨论算法,只是提供一个动画实现的思路.动画效果如下: 高斯模糊渐变动画 //高斯模糊 -(UIImage *)boxblurImage:(UIImage *)image withBlurNumber:(CGFloat)blur {        if (blur < 0.f || blur > 1.f) {              blur = 0.5f;        }      

Android基础入门教程——8.1.3 Android中的13种Drawable小结 Part 3

Android基础入门教程--8.1.3 Android中的13种Drawable小结 Part 3 标签(空格分隔): Android基础入门教程 本节引言: 本节我们来把剩下的四种Drawable也学完,他们分别是: LayerDrawable,TransitionDrawable,LevelListDrawable和StateListDrawable, 依旧贴下13种Drawable的导图: 1.LayerDrawable 层图形对象,包含一个Drawable数组,然后按照数组对应的顺序来

Android基础入门教程——8.1.2 Android中的13种Drawable小结 Part 2

Android基础入门教程--8.1.2 Android中的13种Drawable小结 Part 2 标签(空格分隔): Android基础入门教程 本节引言: 本节我们继续来学习Android中的Drawable资源,上一节我们学习了: ColorDrawable:NinePatchDrawable: ShapeDrawable:GradientDrawable!这四个Drawable~ 而本节我们继续来学习接下来的五个Drawable,他们分别是: BitmapDrawable:Insert

安卓小结《1》

Activity的生命周期和启动模式的知识点小结: 1.如果Activity切换的时候,新Activity是透明,旧的不会走onStop方法. 2.新的Activity切换的时候,旧Activity  会先执行,onpause,然后才会启动新的activity. 3. Activity在异常情况下被回收时,onSaveInstanceState方法会被回调,回调时机是在onStop之前,当Activity被重新创建的时 候,onRestoreInstanceState方法会被回调,时序在onSt

date命令小结

在写linux shell脚本时,date是经常要用到的一个命令,这篇文章就此做个小结,以防自己用到时到处找 1.最基本的,显示当前的具体时期:直接敲入 date即可,如下, [email protected]:~/scripts$ date 2015年 01月 03日 星期六 21:46:49 CST 2.显示某个文件上次修改的时间:date -r file [email protected]:~/scripts$ date -r save.sh 2015年 01月 02日 星期五 23:29