图像滤波算法——均值、中值、高斯

注:本文为博主参考书籍和他人文章并加上自己的理解所编,作为学习笔记使用并将其分享出去供大家学习。若涉及到引用您的文章内容请评论区告知!如有错误欢迎指正!

一、均值滤波

    最简单的滤波器是移动平均或方框滤波器,他将K*K窗口中像素值的平均值作为输出。这种滤波器等价于图像与全部元素值为1的核函数先进性卷积再进行尺度缩放。对于尺寸较大的核函数,一个有效的实现策略如下:在扫描行上用一个移动的窗口进行滑动(在可分离滤波器中),新的窗口的和值等于上一个窗口的和值加上新的窗口中增加的像素的值并减去离开上一个窗口的像素的值。

二、中值滤波

1、中值滤波的实质

    对受到噪声污染的图像可以采用线性滤波的方法来处理,但是很多线性滤波有低通性,在去噪声的同时也使得边缘模糊了,中值滤波在某些情况下可以做到既去除噪声又保护图像的边缘,它是一种非线性的去除噪声的方法。

    中值滤波的实现原理是把数字图像中一点的值用该点的一个区域的各个点的值的中值代替。我们将一个点的特定长度或形状的邻域称为窗口,那么对于二维图像的中值滤波,一般采用3*3或5*5的窗口进行滤波。

1、中值滤波的实现

   以3*3的窗口为例,对于灰度图像,有输入和输出两个矩阵。从bmp_in[1][1]开始算起,通过该像素点领域内的8个像素再加上该像素本身共9个像素计算出中值并映射到输出矩阵bmp_out[1][1]。接着整个窗口右移到[1][2],以此类推直到该行扫完,开始下一行的扫描。如果对于3通道的彩色图像,则需要对每个通道分别进行中值计算并映射到输出矩阵,接着进行整合显示。

三、高斯滤波

1、高斯滤波的实质

    高斯滤波是一种线性滤波。就是对整幅图像进行加权平均的过程,每个像素点的值都由其本身和邻域内的其它像素值经过加权平均后得到。高斯平滑滤波器对于仰制服从正态分布的噪声非常有效。

    既然是线性滤波,那么就会存在低通性,那么为什么还要使用这种滤波方法呢?实际上我们知道如果取模板中心的系数比较大,而模板周围的系数比较小,则可以在保持图像原有信息特征的情况下消除部分噪声,而高斯滤波的模板恰恰符合这个条件。还有一个问题,如果我们直接通过计算得到高斯模板,会存在大量浮点数的运算,故还需要进行取整变换使得模板周围的最小系数为1,中心点系数取最大值。

2、高斯滤波器模板的生成

    那么高斯滤波的模板怎么确定呢?将系数值按高斯分布(正态分布)来确定,高斯滤波器的方法可以通过使用二项式展开法和计算高斯的掩码权重矩阵来得到一组高斯滤波器模板。

    首先我们先来考虑一维线阵图像的情况,高中时我们学过正态分布,其密度函数为:

    其中μ和σ都为常数,μ∈R,σ>0;记做X~N(μ,,σ²)。其中μ决定了曲线的中心位置(x=μ时,f(x)最大),σ决定了曲线的中锋的陡峭程度(σ越大,曲线越平缓),而且可以看出f(x)总是大于0的。

    μ=0,σ=1的正态分布称为标准正态分布,其密度函数为:

   

   对于一维图像, 在计算模板时,我们以最中心的像素点作为基准,所以该像素点要取最大值,我们记做f(0)。我们发现x=0时f(x)需要取最大值,那就刚好对应到标准正态分布x=μ=0时f(x)最大。而我们并不希望高斯模板一成不变,而是希望它可以通过一些参数进行调整以适应不同的情况,故我们不采用标准正态分布来计算模板,而是保留σ的可变性,用于调整模板的频带宽度进而影响图像被处理后的平滑(模糊)程度。此时还没有完,我们发现,高斯模板里的成员是用来作为系数使用的,那么当然可以对每个系数除以它们的公约数啦。

    此时我们拓展到二维图像,一般情况下我们使x轴和y轴的σ相等,此时我们可以得到高斯函数的表达式为:

    其中(x,y)为点坐标。要得到一个高斯滤波器模板,应先对高斯函数进行离散化,将得到的值作为模板的系数。

    例如:要产生一个3*3的高斯滤波器模板,以模板的中心位置为坐标原点进行取样。模板在各个位置的坐标,如下图所示(x轴水平向右,y轴竖直向下)

    这样,将各个坐标带入到上面的表达式便可以计算出我们需要的高斯滤波器模板了。

    然而此时计算得到的模板仍然是浮点类型的,为了计算方便可以对模板中的系数值进行取整即将模板各角位置的值即最小值通过一个系数α转换为1,其它位置均乘上这个系数也进行转换,转换后可通过近似取整得到最终的高斯滤波器模板。

    以上方式计算出来的模板在进行实际图像滤波时还需要进行归一化处理,实际上就是在加权后进行平均。

    下面给出一个常用的3*3高斯滤波器模板

       和一个常用的5*5 高斯滤波器模板

3、高斯滤波器的性质

    (1)可分离滤波器:由于高斯函数可以写成可分离的形式,因此可以采用可分离滤波器实现来加速。所谓的可分离滤波器,就是可以把多维的卷积化成多个一维卷积。具体到二维的高斯滤波,就是指先对行做一维卷积,再对列做一维卷积。这样就可以将计算复杂度从O(M*M*N*N)降到O(2*M*M*N),M,N分别是图像和滤波器的窗口大小。

    (2)二维高斯函数具有旋转对称性,即滤波器在各个方向上的平滑程度是相同的.一般来说,一幅图像的边缘方向是事先不知道的,因此,在滤波前是无法确定一个方向上比另一方向上需要更多的平滑.旋转对称性意味着高斯平滑滤波器在后续边缘检测中不会偏向任一方向。

    (3)高斯函数是单值函数.这表明,高斯滤波器用像素邻域的加权均值来代替该点的像素值,而每一邻域像素点权值是随该点与中心点的距离单调增减的.这一性质是很重要的,因为边缘是一种图像局部特征,如果平滑运算对离算子中心很远的像素点仍然有很大作用,则平滑运算会使图像失真。而高斯函数只有一个波峰,对图像边缘回油加强而不会减弱。

    (4)高斯滤波器宽度(决定着平滑程度)是由参数σ表征的,而且σ和平滑程度的关系是非常简单的.σ越大,高斯滤波器的频带就越宽,平滑程度就越好.通过调节平滑程度参数σ,可在图像特征过分模糊(过平滑)与平滑图像中由于噪声和细纹理所引起的过多的不希望突变量(欠平滑)之间取得折衷。

    (5)高斯函数的傅立叶变换频谱是单瓣的.正如下面所示,这一性质是高斯函数付立叶变换等于高斯函数本身这一事实的直接推论,这样可以在空域和频域上做同样的变换而达到同样的效果。图像常被不希望的高频信号所污染(噪声和细纹理).而所希望的图像特征(如边缘),既含有低频分量,又含有高频分量.高斯函数付立叶变换的单瓣意味着平滑图像不会被不需要的高频信号所污染,同时保留了大部分所需信号。

4、高斯滤波的实现

    在图像处理中,高斯滤波一般有两种实现方式,一是用离散化窗口滑窗卷积,另一种通过傅里叶变换。最常见的就是第一种滑窗实现,只有当离散化的窗口非常大,用滑窗计算量非常大(即使用可分离滤波器的实现)的情况下,可能会考虑基于傅里叶变换的实现方法。

    以下具体阐述第一种实现方法,傅里叶变换的方法可能会在后期加上去。

    首先是常规计算方法,以3*3的模板为例,对于灰度图像,有输入和输出两个矩阵。从bmp_in[1][1]开始算起,通过该像素点以及邻域内的像素点与模板中对应位置的系数进行加权然后平均计算出并映射到输出矩阵bmp_out[1][1]。接着整个窗口右移到[1][2],以此类推直到该行扫完,开始下一行的扫描。如果对于3通道的彩色图像,则需要对每个通道分别进行计算并映射到输出矩阵,接着进行整合显示。

01 void gaussianFilter(uchar* data, int width, int height)
02 {
03     int i, j, index, sum;
04     int templates[9] = { 1, 2, 1,
05                          2, 4, 2,
06                          1, 2, 1 };
07     sum = height * width * sizeof(uchar);
08     uchar *tmpdata = (uchar*)malloc(sum);
09     memcpy((char*)tmpdata,(char*)data, sum);
10     for(i = 1;i < height - 1;i++)
11     {
12         for(j = 1;j < width - 1;j++)
13         {
14             index = sum = 0;
15             for(int m = i - 1;m < i + 2;m++)
16             {
17                 for(int n = j - 1; n < j + 2;n++)
18                 {
19                     sum +=
20                         tmpdata[m * width + n] *
21                         templates[index++];
22                 }
23             }
24             data[i * width + j] = sum / 16;
25         }
26     }
27     free(tmpdata);
28 }

    由于高斯滤波器的可分离的性质,我们可以先对原图像的行做一维卷积,即同一行方向上的邻域与一维高斯模板进行卷积,结果保存在bmp_out1中。接着对bmp_out1同一列方向上的邻域与该一维高斯模板矩阵进行卷积得到最终输出图像bmp_out2

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

猜你喜欢

转载自blog.csdn.net/qq_37568748/article/details/80344709