28.图像滤波

1、均值滤波

代码清单5-8 blur()函数原型
void cv::blur(InputArray  src,
                 OutputArray  dst,
                 Size  ksize,
                 Point  anchor = Point(-1,-1),
                 int  borderType = BORDER_DEFAULT 
                 )
  • 待均值滤波的图像,图像的数据类型必须是CV_8U、CV_16U、CV_16S、CV_32F和CV_64F这五种数据类型之一。

  • dst:均值滤波后的图像,与输入图像具有相同的尺寸和数据类型。
  • ksize:卷积核尺寸。
  • anchor:内核的基准点(锚点),其默认值为(-1,-1)代表内核基准点位于kernel的中心位置。基准点即卷积核中与进行处理的像素点重合的点,其位置必须在卷积核的内部。
  • borderType:像素外推法选择标志,取值范围在表3-5中给出,默认参数为BORDER_DEFAULT,表示不包含边界值倒序填充。

  该函数的第一个参数为待滤波图像,可以是彩色图像也可以是灰度图像,甚至可以是保存成Mat类型的多维矩阵数据。第二个参数滤波后的图像,保持与输入图像相同的数据类型、尺寸以及通道数。第三个参数是滤波器的尺寸,输入滤波器的尺寸后函数会自动确定滤波器,其形式如式所示。

代码清单5-9 myBlur.cpp图像均值滤波
#include <opencv2\opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;

int main()
{
  Mat equalLena = imread("equalLena.png", IMREAD_ANYDEPTH);
  Mat equalLena_gauss = imread("equalLena_gauss.png", IMREAD_ANYDEPTH);
  Mat equalLena_salt = imread("equalLena_salt.png", IMREAD_ANYDEPTH);
  if (equalLena.empty() || equalLena_gauss.empty() || equalLena_salt.empty())
  {
    cout << "请确认图像文件名称是否正确" << endl;
    return -1;
  }
  Mat result_3, result_9;  //存放不含噪声滤波结果,后面数字代表滤波器尺寸
  Mat result_3gauss, result_9gauss;  //存放含有高斯噪声滤波结果,后面数字代表滤波器尺寸
  Mat result_3salt, result_9salt;  //存放含有椒盐噪声滤波结果,后面数字代表滤波器尺寸
  //调用均值滤波函数blur()进行滤波
  blur(equalLena, result_3, Size(3, 3));
  blur(equalLena, result_9, Size(9, 9));
  blur(equalLena_gauss, result_3gauss, Size(3, 3));
  blur(equalLena_gauss, result_9gauss, Size(9, 9));
  blur(equalLena_salt, result_3salt, Size(3, 3));
  blur(equalLena_salt, result_9salt, Size(9, 9));
  //显示不含噪声图像
  imshow("equalLena ", equalLena);
  imshow("result_3", result_3);
  imshow("result_9", result_9);
  //显示含有高斯噪声图像
  imshow("equalLena_gauss", equalLena_gauss);
  imshow("result_3gauss", result_3gauss);
  imshow("result_9gauss", result_9gauss);
  //显示含有椒盐噪声图像
  imshow("equalLena_salt", equalLena_salt);
  imshow("result_3salt", result_3salt);
  imshow("result_9salt", result_9salt);
  waitKey(0);
  return 0;
}

2、方框滤波

  方框滤波是均值滤波的一般形式,在均值滤波中,将滤波器中所有的像素值求和后的平均值作为滤波后结果,方框滤波也是求滤波器内所有像素值的之和,但是方框滤波可以选择不进行归一化,就是将所有像素值的和作为滤波结果,而不是所有像素值的平均值。

代码清单5-10 boxFilter()函数原型
void cv::boxFilter(InputArray  src,
                       OutputArray  dst,
                       int  ddepth,
                       Size  ksize,
                       Point  anchor = Point(-1,-1),
                       bool  normalize = true,
                       int  borderType = BORDER_DEFAULT 
                       )
  • src:输入图像。
  • dst:输出图像,与输入图像具有相同的尺寸和通道数。
  • ddepth:输出图像的数据类型(深度),根据输入图像的数据类型不同拥有不同的取值范围,具体的取值范围在表5-1给出,当赋值为-1时,输出图像的数据类型自动选择。
  • ksize:卷积核尺寸。
  • anchor:内核的基准点(锚点),其默认值为(-1,-1)代表内核基准点位于kernel的中心位置。基准点即卷积核中与进行处理的像素点重合的点,其位置必须在卷积核的内部。
  • normalize:是否将卷积核进行归一化的标志,默认参数为true,表示进行归一化。
  • borderType:像素外推法选择标志,取值范围在表3-5中给出,默认参数为BORDER_DEFAULT,表示不包含边界值倒序填充。

  该函数的使用方式与均值滤波函数blur()几乎一样,但是该函数可以选择输出图像的数据类型,除此之外,该函数的第六个参数表示是否对滤波器内所有的数值进行归一化操作,参数默认状态下需要对滤波器内所有的数值进行归一化。此时,在不考虑数据类型的情况下,框滤波函数boxFilter()和均值滤波函数blur()会具有相同的滤波结果。

  除了对滤波器内每个像素值直接求和外,OpenCV 4还提供了sqrBoxFilter()函数实现对滤波器内每个像数值的平方求和,之后根据输入参数选择是否进行归一化操作,该函数的函数原型在代码清单5-11中给出。

代码清单5-11 sqrBoxFilter()函数原型
void cv::sqrBoxFilter(InputArray  src,
                          OutputArray  dst,
                          int  ddepth,
                          Size  ksize,
                          Point  anchor = Point(-1, -1),
                          bool  normalize = true,
                          int  borderType = BORDER_DEFAULT 
                          )

  该函数是在boxFilter()函数功能基础上进行扩展功能,因此两者具有相同的输入参数需求,这里对函数的参数不再进行逐一解释。CV_8U数据类型的图像像素值从0到255,计算平方后数据会变得更大,即使归一化操作也不能保证像素值不会超过最大值,但是CV_32F数据类型的图像像素值是从0到1之间的小数,在0到1之间的数计算平方会变得更小,但是始终保持在0到1之间。因此该函数在处理图像滤波的任务时主要针对的是CV_32数据类型的图像,而且根据计算关系可知,在归一化后图像在变模糊的同时亮度也会变暗。

3、高斯滤波

代码清单5-13 GaussianBlur()函数原型
void cv::GaussianBlur(InputArray  src,
                           OutputArray  dst,
                           Size  ksize,
                           double  sigmaX,
                           double  sigmaY = 0,
                           int  borderType = BORDER_DEFAULT 
                           )
  • src:待高斯滤波图像,图像可以具有任意的通道数目,但是数据类型必须为CV_8U,CV_16U,CV_16S,CV_32F或CV_64F。
  • dst:输出图像,与输入图像src具有相同的尺寸、通道数和数据类型。
  • ksize:高斯滤波器的尺寸,滤波器可以不为正方形,但是必须是正奇数。如果尺寸为0,则由标准偏差计算尺寸。
  • sigmaX:X方向的高斯滤波器标准偏差。
  • sigmaY:Y方向的高斯滤波器标准偏差; 如果输入量为0,则将其设置为等于sigmaX,如果两个轴的标准差均为0,则根据输入的高斯滤波器尺寸计算标准偏差。
  • borderType:像素外推法选择标志,取值范围在表3-5中给出,默认参数为BORDER_DEFAULT,表示不包含边界值倒序填充。

  该函数能够根据输入参数自动生成高斯滤波器,实现对图像的高斯滤波,函数的前两个参数与前面介绍的滤波函数的参数含义相同。该函数第三个参数是高斯滤波器的尺寸,与前面函数不同的是,该函数除了必须是正奇数以外,还允许输入尺寸为0,当输入的尺寸为0时,会根据输入的标准偏差计算滤波器的尺寸。函数第四个和第五个参数为X方向和Y方向的标准偏差,当Y方向参数为0时表示Y方向的标准偏差与X方向相同,当两个参数都为0时,则根据输入的滤波器尺寸计算两个方向的标准偏差数值。但是为了能够使计算结果符合自己的预期,建议将第三个参数、第四个参数和第五个参数都明确的给出。

  高斯滤波器的尺寸和标准偏差存在着一定的互相转换关系,OpenCV 4提供了输入滤波器单一方向尺寸和标准偏差生成单一方向高斯滤波器的getGaussianKernel()函数,在函数的定义中给出了滤波器尺寸和标准偏差存在的关系,这个关系不是数学中存在的关系,而是OpenCV 4为了方便而自己设定的关系。在了解这个关系之前,我们首先了解以下getGaussianKernel()函数,该函数的函数原型在代码清单5-14中给出。

代码清单5-14 getGaussianKernel()函数原型
Mat cv::getGaussianKernel(int  ksize,
                               double  sigma,
                               int  ktype = CV_64F 
                               )
  • ksize:高斯滤波器的尺寸。
  • sigma:高斯滤波的标测差。
  • ktype:滤波器系数的数据类型,可以是CV_32F或者CV_64F,默认数据类型为CV_64F。

  该函数用于生成指定尺寸的高斯滤波器,需要注意的是该函数生成的是一个ksize×1的Mat类矩阵。函数第一个参数是高斯滤波器的尺寸,这个参数必须是一个正奇数。第二个参数表示高斯滤波的标准差,这个参数如果是一个负数,则调用程序中默认的高斯滤波器尺寸与标准差的公式,其计算公式如式所示。

生成一个二维的高斯滤波器需要调用两次getGaussianKernel()函数,将X方向的一维高斯滤波器和Y方向的一维高斯滤波器相乘,得到最终的二维高斯滤波器。例如计算的X方向的一维滤波器和Y方向的一维滤波器均如式所示。

最终二维高斯滤波器计算过程和结果如式所示。

代码清单5-15 myGaussianBlur.cpp图像高斯滤波
#include <opencv2\opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;

int main()
{
  Mat equalLena = imread("equalLena.png", IMREAD_ANYDEPTH);
  Mat equalLena_gauss = imread("equalLena_gauss.png", IMREAD_ANYDEPTH);
  Mat equalLena_salt = imread("equalLena_salt.png", IMREAD_ANYDEPTH);
  if (equalLena.empty()||equalLena_gauss.empty()||equalLena_salt.empty())
  {
    cout << "请确认图像文件名称是否正确" << endl;
    return -1;
  }
  Mat result_5, result_9;  //存放不含噪声滤波结果,后面数字代表滤波器尺寸
  Mat result_5gauss, result_9gauss;  //存放含有高斯噪声滤波结果,后面数字代表滤波器尺寸
  Mat result_5salt, result_9salt;  ////存放含有椒盐噪声滤波结果,后面数字代表滤波器尺寸
  //调用均值滤波函数blur()进行滤波
  GaussianBlur(equalLena, result_5, Size(5, 5), 10, 20);
  GaussianBlur(equalLena, result_9, Size(9, 9), 10, 20);
  GaussianBlur(equalLena_gauss, result_5gauss, Size(5, 5), 10, 20);
  GaussianBlur(equalLena_gauss, result_9gauss, Size(9, 9), 10, 20);
  GaussianBlur(equalLena_salt, result_5salt, Size(5, 5), 10, 20);
  GaussianBlur(equalLena_salt, result_9salt, Size(9, 9), 10, 20);
  //显示不含噪声图像
  imshow("equalLena ", equalLena);
  imshow("result_5", result_5);
  imshow("result_9", result_9);
  //显示含有高斯噪声图像
  imshow("equalLena_gauss", equalLena_gauss);
  imshow("result_5gauss", result_5gauss);
  imshow("result_9gauss", result_9gauss);
  //显示含有椒盐噪声图像
  imshow("equalLena_salt", equalLena_salt);
  imshow("result_5salt", result_5salt);
  imshow("result_9salt", result_9salt);
  waitKey(0);
  return 0;
}

4、可分离滤波

  前面介绍的滤波函数使用的滤波器都是固定形式的滤波器,有时我们需要根据实际需求调整滤波模板,例如在滤波计算过程中滤波器中心位置的像素值不参与计算,滤波器中参与计算的像素值不是一个矩形区域等。OpenCV 4无法根据每种需求单独编写滤波函数,因此OpenCV 4提供了根据自定义滤波器实现图像滤波的函数,就是我们本章最开始介绍的卷积函数filter2D(),不过根据函数的名称,这里称呼为滤波函数更为准确一些,输入的卷积模板也应该称为滤波器或者滤波模板。该函数的使用方式我们在一开始已经介绍,只需要根据需求定义一个卷积模板或者滤波器,便可以实现自定义滤波。

  无论是图像卷积还是滤波,在原图像上移动滤波器的过程中每一次的计算结果都不会影响到后面过程的计算结果,因此图像滤波是一个并行的算法,在可以提供并行计算的处理器中可以极大的加快图像滤波的处理速度。除此之外,图像滤波还具有可分离行,这个性质我们在高斯滤波中有简单的接触,可分离性指的是先对X(Y)方向滤波,再对Y(X)方向滤波的结果与将两个方向的滤波器联合后整体滤波的结果相同。两个方向的滤波器的联合就是将两个方向的滤波器相乘,得到一个矩形的滤波器,例如X方向的滤波器为 ,Y方向的滤波器为 ,两个方向联合滤波器可以用式(5.7)计算,无论先进行X方向滤波还是Y方向滤波,两个方向联合滤波器都是相同的。

因此在高斯滤波中,我们利用getGaussianKernel()函数分别得到X方向和Y方向的滤波器,之后通过生成联合滤波器或者分别用两个方向的滤波器进行滤波的计算结果相同。但是两个方向联合滤波需要在使用filter2D()函数滤波之前计算联合滤波器,而两个方向分别滤波需要调用两次filter2D()函数,增加了通过代码实现的复杂性,因此OpenCV 4提供了可以输入两个方向滤波器实现滤波的滤波函数sepFilter2D(),该函数的函数原型在代码清单5-16中给出。

代码清单5-16 sepFilter2D()函数原型
void cv::sepFilter2D(InputArray  src,
                       OutputArray  dst,
                       int  ddepth,
                       InputArray  kernelX,
                       InputArray  kernelY,
                       Point  anchor = Point(-1,-1),
                       double  delta = 0,
                       int  borderType = BORDER_DEFAULT 
                       )
  • src:待滤波图像
  • dst:输出图像,与输入图像src具有相同的尺寸、通道数和数据类型。
  • ddepth:输出图像的数据类型(深度),根据输入图像的数据类型不同拥有不同的取值范围,具体的取值范围在表5-1给出,当赋值为-1时,输出图像的数据类型自动选择。
  • kernelX:X方向的滤波器,
  • kernelY:Y方向的滤波器。
  • anchor:内核的基准点(锚点),其默认值为(-1,-1)代表内核基准点位于kernel的中心位置。基准点即卷积核中与进行处理的像素点重合的点,其位置必须在卷积核的内部。
  • delta:偏值,在计算结果中加上偏值。
  • borderType:像素外推法选择标志,取值范围在表3-5中给出。默认参数为BORDER_DEFAULT,表示不包含边界值倒序填充。

  该函数将可分离的线性滤波器分离成X方向和Y方向进行处理,与filter2D()函数不同之处在于,filter2D()函数需要通过滤波器的尺寸区分滤波操作是作用在X方向还是Y方向,例如滤波器尺寸为K×1时是Y方向滤波,1×K尺寸的滤波器是X方向滤波。而sepFilter2D()函数通过不同参数区分滤波器是作用在X方向还是Y方向,无论输入滤波器的尺寸是K×1还是1×K,都不会影响滤波结果。

  为了更加了解线性滤波的可分离性,在代码清单5-17中给出了利用filter2D()函数和sepFilter2D()函数实现滤波的示例程序。程序中利用filter2D()函数依次进行Y方向和X方向滤波,将结果与两个方向联合滤波器滤波结果相比较,验证两种方式计算结果的一致性。同时将两个方向的滤波器输入sepFilter2D()函数中,验证该函数计算结果是否与前面的计算结果一致。最后利用自定义的滤波器,对图像依次进行X方向滤波和Y方向滤波,查看滤波结果是否与使用联合滤波器的滤波结果一致。

代码清单 5-17 myselfFilter.cpp可分离图像滤波
#include <opencv2\opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;

int main()
{
  system("color F0");  //更改输出界面颜色
  float points[25] = { 1,2,3,4,5,
                         6,7,8,9,10,
                         11,12,13,14,15,
                         16,17,18,19,20,
                         21,22,23,24,25 };
  Mat data(5, 5, CV_32FC1, points);
  //X方向、Y方向和联合滤波器的构建
  Mat a = (Mat_<float>(3, 1) << -1, 3, -1);
  Mat b = a.reshape(1, 1);
  Mat ab = a*b;
  //验证高斯滤波的可分离性
  Mat gaussX = getGaussianKernel(3, 1);
  Mat gaussData, gaussDataXY;
  GaussianBlur(data, gaussData, Size(3, 3), 1, 1, BORDER_CONSTANT);
  sepFilter2D(data,gaussDataXY,-1, gaussX, gaussX, Point(-1,-1),0,BORDER_CONSTANT);
  //输入两种高斯滤波的计算结果
  cout << "gaussData=" << endl
    << gaussData << endl;
  cout << "gaussDataXY=" << endl
    << gaussDataXY << endl;
  //线性滤波的可分离性
  Mat dataYX, dataY, dataXY, dataXY_sep;
  filter2D(data, dataY, -1, a, Point(-1, -1), 0, BORDER_CONSTANT);
  filter2D(dataY, dataYX, -1, b, Point(-1, -1), 0, BORDER_CONSTANT);
  filter2D(data, dataXY, -1, ab, Point(-1, -1), 0, BORDER_CONSTANT);
  sepFilter2D(data, dataXY_sep, -1, b, b, Point(-1, -1), 0, BORDER_CONSTANT);
  //输出分离滤波和联合滤波的计算结果
  cout << "dataY=" << endl
    << dataY << endl;
  cout << "dataYX=" << endl
    << dataYX << endl;
  cout << "dataXY=" << endl
    << dataXY << endl;
  cout << "dataXY_sep=" << endl
    << dataXY_sep << endl;
  //对图像的分离操作
  Mat img = imread("lena.png");
  if (img.empty())
  {
    cout << "请确认图像文件名称是否正确" << endl;
    return -1;
  }
  Mat imgYX, imgY, imgXY;
  filter2D(img, imgY, -1, a, Point(-1, -1), 0, BORDER_CONSTANT);
  filter2D(imgY, imgYX, -1, b, Point(-1, -1), 0, BORDER_CONSTANT);
  filter2D(img, imgXY, -1, ab, Point(-1, -1), 0, BORDER_CONSTANT);
  imshow("img", img);
  imshow("imgY", imgY);
  imshow("imgYX", imgYX);
  imshow("imgXY", imgXY);
  waitKey(0);
  return 0;
}

5、中值滤波

代码清单5-18 medianBlur()函数原型
void cv::medianBlur(InputArray     src,
                      OutputArray  dst,
                      int  ksize 
                      )
  • src:待中值滤波的图像,可以是单通道,三通道和四通道,数据类型与滤波器的尺寸相关,当滤波器尺寸为3或5时,图像可以是CV_8U,CV_16U或CV_32F类型,对于较大尺寸的滤波器,数据类型只能是CV_8U。
  • dst:输出图像,与输入图像src具有相同的尺寸和数据类型。
  • ksize:滤波器尺寸,必须是大于1的奇数,例如:3、5、7……

6、双边滤波

  前面我们介绍的滤波方法都会图像照成模糊,使得边缘信息变弱或者消失,因此需要一种能够对图像边缘信息进行保留的滤波算法,双边滤波就是经典的常用的能够保留图像边缘信息的滤波算法之一。双边滤波是一种综合考虑滤波器内图像空域信息和滤波器内图像像素灰度值相似性的滤波算法,可以实现在保留区域信息的基础上实现对噪声的去除、对局部边缘的平滑。双边滤波对高频率的波动信号起到平滑的作用,同时保留大幅值的信号波动,进而实现对保留图像中边缘信息的作用。双边滤波的示意图如图5-24所示,双边滤波器是两个滤波器的结合,分别考虑空域信息和值域信息,使得滤波器对边缘附近的像素进行滤波时,距离边缘较远的像素值不会对边缘上的像素值影响太多,进而保留了边缘的清晰性。

双边滤波原理的数学表示如式:

其中ω(i,j,k,l)为加权系数,其取值决定于空域滤波器和值域滤波器的乘积,空域滤波器的表示形式如式(5.9)所示,值域表示形式如式:

两者相乘后,会产生形如式(5.11)所示的依赖于数据的双边滤波器。

代码清单5-20 bilateralFilter()函数原型
void cv::bilateralFilter(InputArray  src,
                            OutputArray  dst,
                            int  d,
                            double  sigmaColor,
                            double  sigmaSpace,
                            int  borderType = BORDER_DEFAULT 
                            )
  • src:待双边滤波图像,图像数据类型为必须是CV_8U、CV_32F和CV_64F三者之一,并且通道数必须为单通道或者三通道。
  • dst:双边滤波后的图像,尺寸和数据类型与输入图像src相同。
  • d:滤波过程中每个像素邻域的直径,如果这个值是非正数,则由第五个参数sigmaSpace计算得到。
  • sigmaColor:颜色空间滤波器的标准差值,这个参数越大表明该像素领域内有越多的颜色被混合到一起,产生较大的半相等颜色区域。
  • sigmaSpace:空间坐标中滤波器的标准差值,这个参数越大表明越远的像素会相互影响,从而使更大领域中有足够相似的颜色获取相同的颜色。当第三个参数d大于0时,邻域范围由d确定,当第三个参数小于等于0时,邻域范围正比于这个参数的数值。
  • borderType:像素外推法选择标志,取值范围在表3-5中给出,默认参数为BORDER_DEFAULT,表示不包含边界值倒序填充。

  该函数可以对图像进行双边滤波处理,在减少噪声的同时保持边缘的清晰。该函数第一个参数是待进行双边滤波的图像,该函数要求只能输入单通道的灰度图和三通道的彩色图像,并且对于图像的数据类型也有严格的要求,必须是CV_8U、CV_32F和CV_64F三者之一。函数第三个参数是滤波器的直径,当滤波器的直径大于5时,函数的运行速度会变慢,因此如果需要在实时系统中使用该函数,建议将滤波器的半径设置为5,对于离线处理含有大量噪声的滤波图像时,可以将滤波器的半径设为9,当滤波器半径为非正数的时候,会根据空间滤波器的标准差计算滤波器的直径。函数第四个和第五个参数是两个滤波器的标准差值,为了简单起见可以将两个参数设置成相同的数值,当他们小于10时,滤波器对图像的滤波作用较弱,当他们大于150时滤波效果会非常的强烈,使图像看起来具有卡通的效果。该函数运行时间比其他滤波方法时间要长,因此在实际工程中使用的时候,选择合适的参数十分重要。另外比较有趣的现象是,使用双边滤波会具有美颜效果。

代码清单5-21 myBilateralFilter.cpp人脸图像双边滤波
#include <opencv2\opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;

int main()
{
  //读取两张含有人脸的图像
  Mat img1 = imread("img1.png", IMREAD_ANYCOLOR);
  Mat img2 = imread("img2.png", IMREAD_ANYCOLOR);
  if (img1.empty()||img2.empty())
  {
    cout << "请确认图像文件名称是否正确" << endl;
    return -1;
  }
  Mat result1, result2, result3, result4;

  //验证不同滤波器直径的滤波效果
  bilateralFilter(img1, result1, 9, 50, 25 / 2);
  bilateralFilter(img1, result2, 25, 50, 25 / 2);

  //验证不同标准差值的滤波效果
  bilateralFilter(img2, result3, 9, 9, 9);
  bilateralFilter(img2, result4, 9, 200, 200);

  //显示原图
  imshow("img1", img1);
  imshow("img2", img2);
  //不同直径滤波结果
  imshow("result1", result1);
  imshow("result2", result2);
  //不同标准差值滤波结果
  imshow("result3 ", result3);
  imshow("result4", result4);

  waitKey(0);
  return 0;
}

 

posted @ 2023-04-11 12:00  夏蝉沐雪  阅读(124)  评论(0编辑  收藏  举报