当前位置:   article > 正文

C++数字图像处理(4)—均值滤波_c++分别采用3*3和5*5的窗口对噪声污染图像进行滤波

c++分别采用3*3和5*5的窗口对噪声污染图像进行滤波

1、算法原理

数字图像处理中,滤波是一个很重要的操作,许多算法其本质都是滤波操作。使用白话的形式对滤波定义:对于一个像素点,使用其领域像素(可以包含自身,也可不包含)的相关特性,计算出一个值,代替当前像素值。举个例子,3X3均值滤波,就是计算每个像素对应的3X3领域所有像素值的平均值,代替当前像素。滤波操作可分为线性滤波和非线性滤波两大种类,其中,常见的线性滤波操作有:均值滤波、高斯滤波、方差滤波(局部方差)等等,常见的非线性滤波操作有:中值滤波、最大值滤波、最小值滤波等等。

2、算法实现

(1)、边界处理

一个完善的滤波算法,边界处理是必要的。对于图像的边界像素,其领域是不存在的,比如坐标为(0,0)(y,x形式)的像素点,其3X3的领域坐标集合为

其中,所有坐标包含-1的像素点都是不存在的,这个时候滤波操作就需要对这些不存在的点进行处理。有几种边界处理方式:固定值填充法、镜像法、重叠法。所谓的固定值填充法即为:所有不存在的像素点的像素值认为是某一个固定的值(一般由算法调用者去设定,或者默认为0);镜像法:以边界像素为对称轴,使用对称像素点的像素值代替;重叠法:使用边界像素值代替。示意图如下:

固定值填充法(0)
边界重叠法
镜像法

一般来说,如无特殊的应用场景,推荐大家使用边界重叠法进行边界处理。C++代码实现边界重叠法代码如下:

  1. //函数名:makeRepeatBorder
  2. //作用:边界填充(边界像素重叠法)
  3. //参数:
  4. //matInput:输入图像
  5. //matOutput : 输出图像
  6. //cnBorder : 边界尺寸
  7. //返回值:无
  8. //注:支持单通道8位灰度图像
  9. void makeRepeatBorder(cv::Mat& matInput, cv::Mat& matOutput, const int& cnBorder)
  10. {
  11. //构造输出图像
  12. matOutput = cv::Mat::zeros(matInput.rows + cnBorder * 2, matInput.cols + cnBorder * 2, matInput.type());
  13. unsigned char *pSrc = NULL, *pDst = NULL;
  14. //拷贝原始图像数据
  15. for (int m = 0; m < matInput.rows; m++)
  16. {
  17. //源地址
  18. pSrc = matInput.data + m * matInput.step[0];
  19. //目的地址
  20. pDst = matOutput.data + (m + cnBorder) * matOutput.step[0] + cnBorder;
  21. memcpy(pDst, pSrc, matInput.step[0]);
  22. }
  23. //边界处理
  24. for (int m = 0; m < cnBorder; m++)
  25. {
  26. //顶部
  27. pSrc = matOutput.data + cnBorder * matOutput.step[0];
  28. pDst = matOutput.data + m * matOutput.step[0];
  29. memcpy(pDst, pSrc, matOutput.step[0]);
  30. //底部
  31. pSrc = matOutput.data + (cnBorder + matInput.rows - 1) * matOutput.step[0];
  32. pDst = matOutput.data + (cnBorder + matInput.rows + m) * matOutput.step[0];
  33. memcpy(pDst, pSrc, matOutput.step[0]);
  34. }
  35. for (int m = 0; m < matOutput.rows; m++)
  36. for (int n = 0; n < cnBorder; n++)
  37. {
  38. //左
  39. matOutput.at<unsigned char>(m, n) = matOutput.at<unsigned char>(m, cnBorder);
  40. //右
  41. matOutput.at<unsigned char>(m, n + matInput.cols + cnBorder) = matOutput.at<unsigned char>(m, matOutput.cols - cnBorder - 1);
  42. }
  43. }

效果如下:

原始图像
边界填充20像素(边界像素重叠法)

其余两种方法,大家可以自行编码实现。

(2)、均值滤波

经过边界处理后,图像尺寸增大了。假如进行5*5的均值滤波,图像的尺寸增大了4,那么,在边界处理后的图像进行滤波操作的时候,滤波的起始、停止下标同样需要进行改变。一般我们滤波的窗口尺寸设为奇数,主要是考虑到窗口存在滤波中心(锚点),便于计算(个人理解,未经证实)。C++实现代码如下:

  1. //函数名:meanFilter
  2. //作用:均值滤波实现
  3. //参数:
  4. //matInput:输入图像
  5. //matOutput : 输出图像
  6. //cnWinSize : 滤波窗口尺寸
  7. //返回值:无
  8. //注:支持单通道8位灰度图像
  9. void meanFilter(cv::Mat& matInput, cv::Mat& matOutput, const int& cnWinSize)
  10. {
  11. //滤波窗口为奇数
  12. assert(cnWinSize % 2 == 1);
  13. //构造输出图像
  14. matOutput = cv::Mat::zeros(matInput.rows, matInput.cols, matInput.type());
  15. int nAnchor = cnWinSize / 2;
  16. //边界处理
  17. cv::Mat matMakeBorder;
  18. makeRepeatBorder(matInput, matMakeBorder, nAnchor);
  19. //使用行首地址存储法遍历滤波
  20. unsigned char ** pRow = new unsigned char*[matMakeBorder.rows];
  21. for (int r = 0; r < matMakeBorder.rows; r++)
  22. pRow[r] = matMakeBorder.data + r * matMakeBorder.step[0];
  23. //乘法代替除法
  24. float fNormal = 1.0f / (cnWinSize * cnWinSize);
  25. //滤波操作
  26. for (int r = 0; r < matMakeBorder.rows - cnWinSize + 1; r++)
  27. {
  28. unsigned char* pOut = matOutput.data + r * matOutput.step[0];
  29. for (int c = 0; c < matMakeBorder.cols - cnWinSize + 1; c++)
  30. {
  31. //求和
  32. float fSum = 0;
  33. for (int m = 0; m < cnWinSize; m++)
  34. for (int n = 0; n < cnWinSize; n++)
  35. {
  36. fSum += pRow[m + r][n + c];
  37. }
  38. //求均值输出
  39. pOut[c] = (unsigned char)(fSum * fNormal);
  40. }
  41. }
  42. delete[] pRow;
  43. }

滤波结果下:

滤波尺寸3*3
滤波尺寸7*7

3、均值滤波的加速

经过上面的编码实现,我们可以粗略的计算下,该种方式实现均值滤波的算法复杂度。假设滤波图像尺寸为W*H,滤波窗口为M*N,不考虑边界处理的情况下,内存访问的次数为W*H*M*N。也就是说,算法的耗时与滤波窗口的尺寸相关。算法耗时情况如下表:

无加速均值滤波耗时(640*480灰度图像)
滤波尺寸357911
耗时(ms)2.4315.2969.94917.0627.37

那么,是否有更加优雅的方式实现呢?我这里提供一种基于积分图加速办法(查看OpenCV源码可知,cpu处理下,还存在行列滤波加速、频域加速等的方法):

(1)、积分图原理

积分图的首次出现在基于HAAR特征的人脸检测的框架中,原始论文为《Rapid object detection using a boosted cascade of simple features》。Viola 提出了一种利用积分图(integral image)快速计算 Haar 特征的方法, 这个方法使得图像的局部矩形求和运算的复杂度从 O(mn) 下降到了 O(4) 。图像是由一系列的离散像素点组成,因此图像的积分其实就是求和。 图像积分图中每个点的值是原图像中该点左上角的所有像素值之和。示意图如下:

积分图示意

 

在积分图像中,I(x,y)存储的是SAT(x,y)矩形区域对应原始图像的像素和值。那么,在给定积分图后,计算任意矩形区域(x,y,w,h)的像素的和值的公式为:

也就是说,得到积分图后,仅仅需要四个点就可以计算任意矩形区域的像素值之和。

(2)、积分图快速实现

积分图的C++实现有许多种方法,网络上也有很多博主写出了。我查看不少的博文,我向大家推荐这位博主的原理讲解https://blog.csdn.net/xiaowei_cqu/article/details/17928733。其实,大家去查看OpenCV的积分图实现源码就可以发现,积分图的尺寸是比原始图像的尺寸大1的。

这是为什么呢?某个点的积分图反应的是原图中此位置左上角所有像素之和,这里是的累加和是不包括这个点像素本身的,那么这样,对于原图的第一行和第一列的所有像素,其对应位置的积分图就应该是0,这样考虑到所有的像素,为了能容纳最后一列和最后一行的情况,最终的积分图就应该是 (W + 1) X (H + 1)大小。C++实现代码如下:

  1. //函数名:integral
  2. //作用:快速积分图计算实现
  3. //参数:
  4. //matInput:输入图像
  5. //matOutput : 输出积分图(32位整型数据)
  6. //返回值:无
  7. //注:支持单通道8位灰度图像
  8. void integral(cv::Mat& matInput, cv::Mat& matOutput)
  9. {
  10. //构造积分图
  11. matOutput = cv::Mat::zeros(matInput.rows + 1, matInput.cols + 1, CV_32SC1);
  12. for (int r = 0; r < matInput.rows; r++)
  13. {
  14. int nLineACC = 0;
  15. for (int c = 0; c < matInput.cols; c++)
  16. {
  17. nLineACC += matInput.at<unsigned char>(r, c);
  18. matOutput.at<int>(r + 1, c + 1) = matOutput.at<int>(r, c + 1) + nLineACC;
  19. }
  20. }
  21. }

(3)、基于积分图的快速均值滤波

  1. //函数名:fastMeanFilter
  2. //作用:基于积分图的快速均值滤波实现
  3. //参数:
  4. //matInput:输入图像
  5. //matOutput : 输出图像
  6. //cnWinSize : 滤波窗口尺寸
  7. //返回值:无
  8. //注:支持单通道8位灰度图像
  9. void fastMeanFilter(cv::Mat& matInput, cv::Mat& matOutput, const int& cnWinSize)
  10. {
  11. //滤波窗口为奇数
  12. assert(cnWinSize % 2 == 1);
  13. //构造输出图像
  14. matOutput = cv::Mat::zeros(matInput.rows, matInput.cols, matInput.type());
  15. int nAnchor = cnWinSize / 2;
  16. //边界处理
  17. cv::Mat matMakeBorder;
  18. makeRepeatBorder(matInput, matMakeBorder, nAnchor);
  19. //计算积分图
  20. cv::Mat matIntegral;
  21. integral(matMakeBorder, matIntegral);
  22. //使用行首地址存储法遍历滤波
  23. int ** pRow = new int*[matIntegral.rows];
  24. for (int r = 0; r < matIntegral.rows; r++)
  25. pRow[r] = (int*)(matIntegral.data + r * matIntegral.step[0]);
  26. float fNormal = 1.0f / (cnWinSize * cnWinSize);
  27. //滤波操作
  28. for (int r = 0; r < matMakeBorder.rows - cnWinSize + 1; r++)
  29. {
  30. unsigned char* pOut = matOutput.data + r * matOutput.step[0];
  31. for (int c = 0; c < matMakeBorder.cols - cnWinSize + 1; c++)
  32. {
  33. //求和
  34. float fSum = pRow[r][c] + pRow[r + cnWinSize][c + cnWinSize] - \
  35. pRow[r + cnWinSize][c] - pRow[r][c + cnWinSize];
  36. //求均值输出
  37. pOut[c] = (unsigned char)(fSum * fNormal);
  38. }
  39. }
  40. delete[] pRow;
  41. }

经过积分图加速后,均值滤波算法耗时如下:

基于积分图加速的均值滤波耗时
滤波尺寸357911
耗时(ms)2.2462.4562.6582.9543.155

4、总结

(1)、均值滤波优势在于计算量小、有多种加速的方法,对于随机噪声、点噪声有一定的抑制作用;缺陷在于,算法没有保护图像细节,滤波的同时也把图像边缘细节进行了平滑。

(2)、使用积分图进行均值滤波加速,能够使算法耗时基本与滤波窗口尺寸无关。

(3)、博文中的算法绝对耗时的值是与计算机配置、编译器等相关的,但是,耗时的趋势基本是可信的。

技术交流合作QQ:3355138068

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/笔触狂放9/article/detail/78954
推荐阅读
相关标签
  

闽ICP备14008679号