OpenCV14-图像平滑:线性滤波和非线性滤波
OpenCV14-图像平滑:线性滤波和非线性滤波
- 1.图像滤波
- 2.线性滤波
- 2.1均值滤波
- 2.2方框滤波
- 2.3高斯滤波
- 2.4可分离滤波
 
- 3.非线性滤波
- 3.1中值滤波
- 3.2双边滤波
 
 
1.图像滤波
图像滤波是指去除图像中不重要的内容,而使关心的内容表现得更加清晰的方法,例如去除图像中的噪声、提取某些信息等。
根据图像滤波的目的不同,可以将图像滤波分为消除图像噪声的滤波和提取图像中部分特征信息的滤波。
去除图像中的噪声称作图像的平滑或者图像去噪。由于噪声信号在图像中主要集中在高频段,因此图像去噪可以看作去除图像中高频段信号的同时保留图像的低频段和中频段信号,此时使用的滤波器就是低通或者高阻滤波器。图像中纹理变化越明显的区域信号频率也就越高,因此使用高通滤波器对图像信号处理可以起到对图像边缘信息提取、增强和图像锐化的作用。
在部分图像处理书籍中,常用图像模糊来替代图像的低通滤波,因为图像的低通滤波在去除图像噪声的同时会将图像的边缘信息弱化,使得整幅图像看起来变得模糊。在低通滤波中,模糊可以与滤波等价,例如图像高斯模糊和图像高斯低通滤波是一个概念。
总之有:
噪声在高频 纹理信息在高频
图像平滑:去噪 -> 低通高阻滤波器 -> 卷积
图像锐化:提取纹理 -> 低阻高通滤波器 -> 微分
本文讲解使用低通滤波对图像进行去噪。
图像的滤波分为线性滤波和非线性滤波,常见的线性滤波包括均值滤波、方框滤波、高斯滤波,常见的非线性滤波主要包括中值滤波、双边滤波。
2.线性滤波
图像的线性滤波与图像的卷积类似,卷积操作中的卷积模版(卷积核)在图像滤波中称为滤波模版、滤波器或者领域算子。滤波器表示中心像素与滤波范围内其他像素之间的线性关系,通过滤波范围内所有像素值之间的线性组合,得到中心位置像素滤波后的像素值,因此这种方式称为线性滤波。
2.1均值滤波
均值滤波将滤波器内所有像素值都看作中心像素的测量,将滤波器内所有的像素值的平均值作为滤波器中心处图像像素值。
均值滤波的优点是,在像素值变换趋势一致的情况下,可以将受噪声影响而突然变化的像素值修正为周围邻近像素值的平均值,去除噪声影响。但是这种方式会缩小像素值之间的差距,使得细节信息变得更加模糊,滤波器范围越大,变模糊越明显。
均值滤波对于去除高斯噪声等均匀分布的噪声效果较好,但在处理图像中的细节和边缘时可能会导致模糊。此外,均值滤波不能有效消除椒盐噪声等离散噪声,因为离群值会对平均值产生较大影响。均值滤波是一种简单但计算量较大的滤波方法,特别是对于较大的滑动窗口。在实际应用中,可能会使用更高级的滤波方法来处理图像噪声。
OpenCV中提供了blur()函数用于实现图像的均值滤波:
void blur(InputArray src,  // 待均值滤波的图像OutputArray dst, // 均值滤波后的图像Size ksize,      // 滤波器尺寸Point anchor = Point(-1,-1),    // 内核基准点,默认为滤波器中心点int borderType = BORDER_DEFAULT // 像素边界外推标志 在
);
输入滤波器的尺寸(3x3或5x5)后,函数会自动确定滤波器:
  K = 1 k s i z e . w i d t h × k s i z e . h e i g h t [ 1 1 ⋯ 1 1 1 1 ⋯ 1 1 ⋮ ⋮ ⋱ ⋮ ⋮ 1 1 ⋯ 1 1 1 1 ⋯ 1 1 ] K = \frac{1}{ksize.width \times ksize.height} \begin{bmatrix} 1 & 1 & \cdots & 1 & 1 \\ 1 & 1 & \cdots & 1 & 1 \\ \vdots & \vdots & \ddots & \vdots & \vdots\\ 1 &1 & \cdots & 1 & 1\\ 1 &1 & \cdots & 1 & 1\\ \end{bmatrix} K=ksize.width×ksize.height1 11⋮1111⋮11⋯⋯⋱⋯⋯11⋮1111⋮11 
 下面例子给出了利用不同尺寸的滤波器分别处理不含有噪声的图像、含有椒盐噪声的图像和含有高斯噪声的图像。通过结果可以发现,滤波器的尺寸越大,滤波后图像变得越模糊:
#include <opencv2/core/utils/logger.hpp>
#include <opencv2/opencv.hpp>using namespace cv;
using namespace std;int main()
{cout << "OpenCV Version: " << CV_VERSION << endl;utils::logging::setLogLevel(utils::logging::LOG_LEVEL_SILENT);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);int k = waitKey(0); // Wait for a keystroke in the windowreturn 0;
}
2.2方框滤波
方框滤波是均值滤波的一般形式。在均值滤波中,将滤波器中所有的像素值求和后的平均值作为滤波后结果。方框滤波也是求滤波器内所有像素值的和,但是方框滤波可以选择不进行归一化,是将所有像素值的和作为滤波结果,而不是平均值。
OpenCV中提供了 boxFilter() 函数实现方框滤波:
void boxFilter(InputArray src,  // 待滤波图像OutputArray dst, // 输出图像int ddepth, // 输出图像数据类型,-1表示自动选择Size ksize, // 卷积核尺寸Point anchor = Point(-1,-1), // 内核的基准点bool normalize = true, // 是否归一化标志,默认进行归一化int borderType = BORDER_DEFAULT // 像素边界外推标志
);
该函数使用和 blur() 类似,不过可以选择输出图像的数据类型。在不考虑数据类型的情况下,方框滤波函数和均值滤波函数具有相同的滤波效果。
除对滤波器内每个像素值直接求和之外,OpenCV还提供了 sqrtBoxFilter() 函数实现对滤波器内每个像素值的平方求和,之后根据输入参数选择是否进行归一化:
void sqrBoxFilter(InputArray src,  // 参数同上OutputArray dst, int ddepth,Size ksize, Point anchor = Point(-1, -1),bool normalize = true,int borderType = BORDER_DEFAULT );
CV_8U数据类型的图像像素值为 0-255,计算平方后数据会变得更大,即使归一化操作也不能保证像素值不会超过最大值。CV_32F数据类型的图像像素值是 0-1的小数,平方之后会变得更小,但始终保持在0-1.因此该函数处理图像滤波的任务主要针对CV_32F数据类型的图像,在归一化后,图像在变模糊的同时亮度也会变暗。
下面例子给出了利用方框滤波分别处理矩阵数据和图相关的实例:
#include <opencv2/core/utils/logger.hpp>
#include <opencv2/opencv.hpp>using namespace cv;
using namespace std;int main()
{cout << "OpenCV Version: " << CV_VERSION << endl;utils::logging::setLogLevel(utils::logging::LOG_LEVEL_SILENT);Mat equalLena = imread("equalLena.png", IMREAD_ANYDEPTH);  //用于方框滤波的图像if (equalLena.empty()){cout << "请确认图像文件名称是否正确" << endl;return -1;}//验证方框滤波算法的数据矩阵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);//将CV_8U类型转换成CV_32F类型Mat equalLena_32F;equalLena.convertTo(equalLena_32F, CV_32F, 1.0 / 255);Mat resultNorm, result, dataSqrNorm, dataSqr, equalLena_32FSqr;//方框滤波boxFilter()和sqrBoxFilter()boxFilter(equalLena, resultNorm, -1, Size(3, 3), Point(-1, -1), true);  //进行归一化boxFilter(equalLena, result, -1, Size(3, 3), Point(-1, -1), false);  //不进行归一化sqrBoxFilter(data, dataSqrNorm, -1, Size(3, 3), Point(-1, -1),true, BORDER_CONSTANT);  //进行归一化sqrBoxFilter(data, dataSqr, -1, Size(3, 3), Point(-1, -1),false, BORDER_CONSTANT);  //不进行归一化sqrBoxFilter(equalLena_32F, equalLena_32FSqr, -1, Size(3, 3), Point(-1, -1),true, BORDER_CONSTANT);cout << "data: " << endl << data << endl;cout << "dataSqrNorm: " << endl << dataSqrNorm << endl;cout << "dataSqr: " << endl << dataSqr << endl;//显示处理结果imshow("resultNorm", resultNorm);imshow("result", result);imshow("equalLena_32FSqr", equalLena_32FSqr);int k = waitKey(0); // Wait for a keystroke in the windowreturn 0;
}
/*
data:
[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]
dataSqrNorm:
[10, 18.11111111111111, 24.77777777777778, 32.77777777777778, 24.66666666666666;39.44444444444444, 66.33333333333333, 81.33333333333333, 98.33333333333333, 71.44444444444444;99.44444444444444, 161.3333333333333, 186.3333333333333, 213.3333333333333, 151.4444444444444;192.7777777777778, 306.3333333333333, 341.3333333333333, 378.3333333333333, 264.7777777777778;163.3333333333333, 258.1111111111111, 284.7777777777778, 312.7777777777778, 218]
dataSqr:
[90, 163, 223, 295, 222;355, 597, 732, 885, 643;895, 1452, 1677, 1920, 1363;1735, 2757, 3072, 3405, 2383;1470, 2323, 2563, 2815, 1962]
*/
上面的dataSqr中:计算的597
[1, 2, 36, 7, 811, 12, 13]平方和为597
上面的dataSqrNorm中:计算的66.33333333333333
597/9=66.33333333333333
2.3高斯滤波
高斯滤波器考虑了像素离滤波器中心距离的影响,以滤波器中心位置为高斯分布的均值,根据高斯分布公式和每个像素离中心位置的距离计算出滤波器内每个位置的数值。
OpenCV中提供了 GaussianBlur() 函数对图像进行高斯滤波:
void GaussianBlur(InputArray src,  // 待高斯滤波图像OutputArray dst, // 输出图像Size ksize,        // 高斯滤波器的尺寸,可以不为正方形,但必须为正奇数,如果为0那么由标准偏差计算尺寸double sigmaX,     // X方向的高斯滤波器标准偏差double sigmaY = 0, // Y方向的高斯滤波器标准偏差。如果为0,将其设置为sigmaX,如果两个标准偏差都为0,那么根据输入的ksize计算标准偏差int borderType = BORDER_DEFAULT
);
该函数能够根据输入的参数自动生成高斯滤波器,实现对图像的高斯滤波。
高斯滤波器的尺寸和标准偏差存在着一定的互相转换关系,OpenCV中提供了输入滤波器单一方向尺寸和标准偏差生成单一方向高斯滤波器的 getGaussianKernel() 函数。
Mat getGaussianKernel(int ksize,     // 高斯滤波器尺寸double sigma,  // 高斯滤波器的标准差int ktype = CV_64F
);
该函数给出了滤波器尺寸和标准偏差存在的关系,这个关系不是数学中存在的关系,而是OpenCV为了方便自己设定的关系。该函数用于生成指定尺寸的高斯滤波器,生成的是 ksize x 1 的Mat类矩阵。
标准差如果为负数,则使用如下公式计算标准差:
  s i g m a = 0.3 ( ( k s i z e − 1 ) 0.5 − 1 + 0.8 ) sigma = 0.3((ksize -1)0.5 -1 + 0.8) sigma=0.3((ksize−1)0.5−1+0.8)
 生成一个二维的高斯滤波需要调用两次 getGaussianKernel() 函数,将 X 方向的一维高斯滤波器和 Y 方向的以为高斯滤波器相乘得到最终的二维高斯滤波器。
#include <opencv2/core/utils/logger.hpp>
#include <opencv2/opencv.hpp>using namespace cv;
using namespace std;int main()
{cout << "OpenCV Version: " << CV_VERSION << endl;utils::logging::setLogLevel(utils::logging::LOG_LEVEL_SILENT);cv::Mat x = cv::getGaussianKernel(3, -1, CV_32FC1);cv::Mat y = cv::getGaussianKernel(3, -1, CV_32FC1);x = x.reshape(1).t();Mat matrix = x.t() * y;std::cout << "x * y: " << std::endl << matrix << std::endl;int k = waitKey(0); // Wait for a keystroke in the windowreturn 0;
}
不知道为什么输出的不是一个矩阵?
下面的例子中中利用高斯滤波分别处理不含有噪声的图像、含有椒盐噪声的图像和含有高斯噪声的图像。通过处理结果发现,高斯滤波对高斯噪声去除效果较好,但是同样会对图像造成模糊,并且滤波器的尺寸越大,滤波后图像变得越模糊。
#include <opencv2/core/utils/logger.hpp>
#include <opencv2/opencv.hpp>using namespace cv;
using namespace std;int main()
{cout << "OpenCV Version: " << CV_VERSION << endl;utils::logging::setLogLevel(utils::logging::LOG_LEVEL_SILENT);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);int k = waitKey(0); // Wait for a keystroke in the windowreturn 0;
}
2.4可分离滤波
前面介绍的滤波函数使用的滤波器都是固定形式的滤波器,有时需要根据实际需求调整滤波模版。例如,在滤波计算过程中,滤波器中心位置的像素值不参与计算、滤波器中参与计算的像素值不是一个矩形区域等。OpenCV无法根据每种需要求单独编写滤波函数,因此提供了根据自定义滤波器实现图像滤波的函数,即 图像卷积 一文中介绍的卷积函数 filter2D() ,其实应该叫滤波函数更为准确。
无论是图像卷积还是滤波,在原始图像上移动滤波器的过程中每一次的计算结果都不会影响到后面过程的计算结果,因此图像滤波是一个并行算法,在可以提供并行计算的处理器中可以极大地加快图像滤波的处理速度。
此外,图像滤波还具有可分离性,可分离性指的是先对 X(Y) 方向滤波,再对Y(X)方向滤波的结果与将两个方向的滤波器联合后整体滤波的结果相同。两个方向的的滤波器的联合就是将两个方向的滤波器相乘得到一个矩形的滤波器,如 X 方向滤波器为 x = [ x 1 , x 2 , x 3 ] x=[x_1,x_2,x_3] x=[x1,x2,x3] ,Y方向的滤波器为 y = [ y 1 , y 2 , y 3 ] T y = [y_1,y_2,y_3]^T y=[y1,y2,y3]T ,则两个方向的联合滤波器为:
x y = [ y 1 y 2 y 3 ] [ x 1 x 2 x 3 ] = [ x 1 y 1 x 2 y 1 x 3 y 1 x 1 y 2 x 2 y 2 x 3 y 2 x 1 y 3 x 2 y 3 x 3 y 3 ] xy = \begin{bmatrix} y_1 \\y_2 \\y_3 \\ \end{bmatrix} \begin{bmatrix}x_1 & x_2 & x_3 \\\end{bmatrix} =\begin{bmatrix} x_1y_1 & x_2y_1 & x_3y_1 \\ x_1y_2 & x_2y_2 & x_3y_2 \\ x_1y_3 & x_2y_3 & x_3y_3 \\ \end{bmatrix} xy= y1y2y3 [x1x2x3]= x1y1x1y2x1y3x2y1x2y2x2y3x3y1x3y2x3y3 
因此,在高斯滤波中,可以利用 getGaussianKernel 函数分别得到 X 方向和 Y 方向滤波器,之后通过生成联合滤波器或者分别用两个方向的滤波器进行滤波,计算结果相同。
两个方向的滤波器可以使用 filter2D 计算滤波结果。OpenCV提供了可以输入两个方向的滤波器实现滤波:
void sepFilter2D(InputArray src,  // 带滤波图像OutputArray dst, // 输出图像int ddepth, // 输出图像数据类型InputArray kernelX,  // X方向滤波器InputArray kernelY,  // Y方向滤波器Point anchor = Point(-1,-1), // 内核基准点double delta = 0,    // 偏值int borderType = BORDER_DEFAULT // 像素边界外推标志
);该函数将可分离的线性滤波器分离成 X 方向和 Y 方向进行处理,与 filter2D 函数不同之处在于,filter2D 函数需要通过滤波器的尺寸区分滤波操作是作用在 X 方向还是 Y 方向,例如滤波器尺寸为  K × 1 K \times 1 K×1 时是Y方向滤波,  1 × K 1 \times K 1×K 尺寸的滤波器是X方向滤波。而 sepFilter2D 函数通过不同参数区分是作用在 X 方向还是作用在 Y 方向。
下面例子中给出了利用 filter2D 函数依次进行 Y 方向和 X 方向滤波,将结果与两个方向联合滤波器滤波结果相比较,验证两种方式计算结果的一致性。同时,将两个方向的滤波器输入 sepFilter2D 函数汇总,验证该函数计算结果是否与前面的计算结果一致。
#include <opencv2/core/utils/logger.hpp>
#include <opencv2/opencv.hpp>using namespace cv;
using namespace std;int main()
{cout << "OpenCV Version: " << CV_VERSION << endl;utils::logging::setLogLevel(utils::logging::LOG_LEVEL_SILENT);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);int k = waitKey(0); // Wait for a keystroke in the windowreturn 0;
}
输出结果:
gaussData=
[1.7207065, 2.822206, 3.5481372, 4.2740688, 3.430702;4.6296568, 7, 8, 9, 6.9852457;8.2593136, 12, 13, 14, 10.614902;11.888971, 17, 18, 19, 14.244559;10.270683, 14.600147, 15.326078, 16.05201, 11.98068]
gaussDataXY=
[1.7207065, 2.822206, 3.5481372, 4.2740688, 3.430702;4.6296568, 7, 8, 9, 6.9852457;8.2593136, 12, 13, 14, 10.614902;11.888971, 17, 18, 19, 14.244559;10.270683, 14.600147, 15.326078, 16.05201, 11.98068]
dataY=
[-3, -1, 1, 3, 5;6, 7, 8, 9, 10;11, 12, 13, 14, 15;16, 17, 18, 19, 20;47, 49, 51, 53, 55]
dataYX=
[-8, -1, 1, 3, 12;11, 7, 8, 9, 21;21, 12, 13, 14, 31;31, 17, 18, 19, 41;92, 49, 51, 53, 112]
dataXY=
[-8, -1, 1, 3, 12;11, 7, 8, 9, 21;21, 12, 13, 14, 31;31, 17, 18, 19, 41;92, 49, 51, 53, 112]
dataXY_sep=
[-8, -1, 1, 3, 12;11, 7, 8, 9, 21;21, 12, 13, 14, 31;31, 17, 18, 19, 41;92, 49, 51, 53, 112]
3.非线性滤波
非线性滤波的滤波结果不是由滤波器内的像素值通过线性组合计算得到,其计算过程可能包含排序、逻辑计算等。
由于线性滤波是通过对所有像素值的线性组合得到滤波后的结果,因此含有噪声的像素点也会被考虑进去,噪声不会被消除,而是以更柔和的形式存在。
常见的非线性滤波有中值滤波和双边滤波。
3.1中值滤波
中值滤波就是用滤波器范围内所有像素值的中值来替代滤波器中心位置像素值的滤波方法,是一种基于排序统计理论的能够有效抑制噪声的非线性信号处理方法。
相比于均值滤波,中值滤波对于脉冲干扰信号和图像扫描噪声的处理效果更佳,同时,在一定条件下,中值滤波对图像的边缘信息保护效果更佳,可以避免图像细节的模糊,但是,当中值滤波尺寸变大之后,同样会产生图像模糊的效果。在处理时间上,中值滤波消耗的时间要远大于均值滤波消耗的时间。
OpenCV提供了 medianBlur 函数对图像进行中值滤波:
void medianBlur(InputArray src,  // 原图OutputArray dst, // 中值滤波后图像int ksize // 滤波器尺寸,奇数
);
下面代码对含有椒盐噪声的灰度图像和含有椒盐噪声的彩色图像进行中值滤波:
#include <opencv2/core/utils/logger.hpp>
#include <opencv2/opencv.hpp>using namespace cv;
using namespace std;int main()
{cout << "OpenCV Version: " << CV_VERSION << endl;utils::logging::setLogLevel(utils::logging::LOG_LEVEL_SILENT);Mat gray = imread("equalLena_salt.png", IMREAD_ANYCOLOR);Mat img = imread("lena_salt.png", IMREAD_ANYCOLOR);if (gray.empty() || img.empty()){cout << "请确认图像文件名称是否正确" << endl;return -1;}Mat imgResult3, grayResult3, imgResult9, grayResult9;//分别对含有椒盐噪声的彩色和灰度图像进行滤波,滤波模板为3×3medianBlur(img, imgResult3, 3);medianBlur(gray, grayResult3, 3);//加大滤波模板,图像滤波结果会变模糊medianBlur(img, imgResult9, 9);medianBlur(gray, grayResult9, 9);//显示滤波处理结果imshow("img", img);imshow("gray", gray);imshow("imgResult3", imgResult3);imshow("grayResult3", grayResult3);imshow("imgResult9", imgResult9);imshow("grayResult9", grayResult9);int k = waitKey(0); // Wait for a keystroke in the windowreturn 0;
}
3.2双边滤波
前面介绍的滤波方法都会对图像造成模糊,使得边缘信息变弱或者消失,因此需要一种能够对图像边缘信息进行保留的滤波算法。
双边滤波是一种综合考虑滤波器内图像空域信息和滤波器内图像像素灰度值相似性的滤波算法,可以实现在保留区域信息的基础上实现对噪声的去除、对局部边缘的平滑。双边滤波对高频率的波动信号起到平滑作用,同时保留大幅值变化的信号波动,进而实现对保留图像中边缘信息的作用。
双边滤波器是两个滤波器的结合,分别考虑空域信息和值域信息,使得滤波器对边缘附近的像素进行滤波时,距离边缘较远的像素值不会对边缘上的像素值影响太多,进而保留边缘的清晰性。
双边滤波原理的数学表达式:
  g ( i , j ) = ∑ k , l f ( k , l ) ω ( i , j , k , l ) ∑ k , l ω ( i , j , k , l ) g(i,j) = \frac{\sum_{k,l}f(k,l) \omega(i,j,k,l)}{\sum_{k,l}\omega(i,j,k,l)} g(i,j)=∑k,lω(i,j,k,l)∑k,lf(k,l)ω(i,j,k,l)
 其中  ω ( i , j , k , l ) \omega(i,j,k,l) ω(i,j,k,l) 为加权系数,其值取决于空域滤波器和值域滤波器的乘积。
空域滤波器表示如下:
  d ( i , j , k , l ) = e x p ( − ( i − k ) 2 + ( j − l ) 2 2 σ d 2 ) d(i,j,k,l) = exp({-\frac{(i-k)^2 + (j-l)^2}{2 \sigma_d^2}}) d(i,j,k,l)=exp(−2σd2(i−k)2+(j−l)2)
 值域滤波器表示如下:
  r ( i , j , k , l ) = e x p ( − ∣ ∣ f ( i , j ) − f ( k , l ) ∣ ∣ 2 2 σ d 2 ) r(i,j,k,l) = exp(- \frac{||f(i,j) - f(k,l)||^2}{2 \sigma_d^2}) r(i,j,k,l)=exp(−2σd2∣∣f(i,j)−f(k,l)∣∣2)
 两者相乘后产生如下依赖于数据的双边滤波器:
  ω ( i , j , k , l ) = e x p ( − ( i − k ) 2 + ( j − l ) 2 2 σ d 2 − ∣ ∣ f ( i , j ) − f ( k , l ) ∣ ∣ 2 2 σ d 2 ) \omega(i,j,k,l) = exp({-\frac{(i-k)^2 + (j-l)^2}{2 \sigma_d^2} - \frac{||f(i,j) - f(k,l)||^2}{2 \sigma_d^2}}) ω(i,j,k,l)=exp(−2σd2(i−k)2+(j−l)2−2σd2∣∣f(i,j)−f(k,l)∣∣2)
OpenCV提供了 bilateralFilter() 函数对图像进行双边滤波:
void bilateralFilter(InputArray src,  // 单通道或者三通道图像OutputArray dst, // 双边滤波后的输出图像int d, // 滤波过程中每个像素邻域的直径。如果为非正数,则有第五个参数计算得到double sigmaColor, // 颜色空间滤波器的标准差值。这个参数越大,表明该像素邻域内有越多的颜色被混合在一起,产生较大的半相等颜色区域。double sigmaSpace, // 空间坐标中滤波器的标准差值。这个参数越大,表明越远的像素会互相影响,从而使更大领域内有足够相似的颜色获取相同的颜色。当d大于0时,领域范围由d确定,当d小于等于0时,领域范围由正比于这个参数的数值int borderType = BORDER_DEFAULT // 像素边界外推标志
);
当滤波器直径大于5时,函数的运行速度会变慢,实时系统可以设为5,离线处理可以设为9。第4、5两个参数,可以将他们设置成相同的值,当他们小于10时,滤波器对图像的滤波作用较弱,当他们大于150时,滤波效果会非常强烈,使图像看起来具有卡通效果。该函数的缺点是运行时间比其他滤波方法要长。
下面给出了使用双边滤波对人脸图像进行滤波:
#include <opencv2/core/utils/logger.hpp>
#include <opencv2/opencv.hpp>using namespace cv;
using namespace std;int main()
{cout << "OpenCV Version: " << CV_VERSION << endl;utils::logging::setLogLevel(utils::logging::LOG_LEVEL_SILENT);//读取两张含有人脸的图像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);int k = waitKey(0); // Wait for a keystroke in the windowreturn 0;
}
从结果可以看出,滤波直径越大,滤波效果越明显,当滤波直径相同时,标准差值越大,滤波效果越明显。能对人脸起到美颜效果。
相关文章:
OpenCV14-图像平滑:线性滤波和非线性滤波
OpenCV14-图像平滑:线性滤波和非线性滤波 1.图像滤波2.线性滤波2.1均值滤波2.2方框滤波2.3高斯滤波2.4可分离滤波 3.非线性滤波3.1中值滤波3.2双边滤波 1.图像滤波 图像滤波是指去除图像中不重要的内容,而使关心的内容表现得更加清晰的方法,…...
kafka_2.10启动Kafka broker
要启动 Kafka broker,你需要执行以下步骤: 首先,确保你已经安装了 Kafka。你可以从 Apache Kafka 的官方网站下载 Kafka 的二进制发行版,并按照官方文档中的说明进行安装。 在安装完成后,进入 Kafka 的安装目录。 打…...
 
【配置环境】SQLite数据库安装和编译以及VS下C++访问SQLite数据库
一,环境 Windows 11 家庭中文版,64 位操作系统, 基于 x64 的处理器SQLite - 3.43.2Microsoft Visual Studio Community 2022 (64 位) - Current 版本 17.5.3 二,SQLite简介 简要介绍 SQLite(Structured Query Language for Lite&a…...
 
Confluence 自定义展示页面
1. 概述 Confluence 作为知识库可通过JS脚本方式,根据登录用户或用户组进行前端页面的自定义 2. 实现方式 Confluence →管理→自定义HTML 嵌入对应JS脚本,示例如下 <script type"text/javascript">jQuery(#footer).html(<div>…...
使用C#的Socket从头实现的带有文件上传和下载功能的HTTP服务器
使用C#和Socket从头实现的带有文件上传和下载功能的HTTP服务器。它支持GET、POST请求方法,并能处理URL参数、请求体以及文件上传和下载。 using System; using System.IO; using System.Net; using System.Net.Sockets; using System.Text;class HttpServer {publi…...
 
【OSPF Loading、FULL状态与display ospf peer brief命令、OSPF的数据库讲解】
个人名片: 🐼作者简介:一名大二在校生,喜欢编程🎋 🐻❄️个人主页🥇:落. 🐼个人WeChat:hmmwx53 🕊️系列专栏:🖼️ 零基…...
 
除氟树脂在工业、市政含氟废水处理中的应用
含氟废水的不达标排放对自然环境有很大的危害,氟化物离子可以累积在土壤和水体中,从而对生态系统造成破坏。大量的氟化物离子会对植物生长产生不良影响,并对水生生物造成毒性作用,严重时还可能导致生态灾难。氟化物离子如果没有得…...
模拟地和数字地的区别
模拟地和数字地的主要区别体现在设计目的、处理技术、数据类型和数据精度四个方面。 设计目的:模拟地的主要设计目的是分析时空数据、进行模型和预测,它主要关注动态变化和过程。而数字地的主要设计目的是数据的存储、管理、查询和分析,在地…...
 
Druid连接池最小连接数设置失效问题
问题发现: 配置 当项目启动后 线程池确实是初始化了5条连接,但是当项目运行一段时间后,5条连接确消失了,只会程序用到得时候,再去初始化连接,这样有点违背了参数设置得意义,后来通过查阅资料发…...
Javascript数据类型和类型转换
Javascript数据类型和类型转换 在JavaScript中,理解数据类型,如何区分它们,以及它们如何被转换是至关重要的。在这篇文章中,我们将探讨这些主题,以帮助巩固你的JavaScript基础。 基础数据类型和引用数据类型 当涉及…...
 
冲刺十五届蓝桥杯P0005单词分析
文章目录 题目分析代码 题目 单词分析 分析 统计字符串中字母出现的次数,可以采用哈希表,代码采用的是数组来存储字符,将字符-97,得到对应的数组下标,将对应下标的数组;找到数组元素最大的下标ÿ…...
 
php获取10年内的年份并加入下拉列表
要实现的效果 在html中内嵌php循环将数组中的年份加入下拉列表 <div class="form-group"><label>年份:</label><div class="input-group"><div class="input-group-prepend"><span class="input-group-te…...
 
2020年亚太杯APMCM数学建模大赛B题美国总统的经济影响分析求解全过程文档及程序
2020年亚太杯APMCM数学建模大赛 B题 美国总统的经济影响分析 原题再现: 美国总统选举每四年举行一次。 2020年是美国总统大选年,共和党候选人唐纳德特朗普和民主党对手乔拜登竞选总统。 甲乙双方候选人在金融贸易,经济金融治理,…...
 
保护隐私就是在保护自己!如何在Android上更改应用程序权限
如果你关心隐私,知道如何在Android上更改应用程序权限将成为一项非常重要的技能。即使是最好的安卓应用程序也可以对手机的功能和数据进行广泛的访问,因此准确控制它们的使用范围会有所帮助。 一旦你在手机上加载了应用程序,你可能会注意到它…...
 
Linux/Ubuntu 安装 Java运行环境
linux下安装Java运行环境 1、下载安装包 .tar.gz 先在官网下载 JDK 点击这里 在这里要选择对应的 JDK 版本,一般我们目前选择JDK8 点击这里 2、在 /usr/local/ 目录下创建Java文件夹 cd /usr/local/ mkdir java3、将下载的文件通过FTP程序上传到刚刚创建的Java文…...
vue2 中activated和deactivated是详细解说
activated 和 deactivated 是 Vue.js 中的生命周期钩子函数,主要用于处理组件在 keep-alive 缓存中进入和离开时的行为。 这些钩子函数通常用于在组件进入缓存时执行特定的操作,以及在组件离开缓存时执行清理操作。 下面是关于这两个钩子函数的用例和示例…...
 
C# GFPGAN 图像(人脸面部)修复
效果 项目 代码 using Microsoft.ML.OnnxRuntime; using Microsoft.ML.OnnxRuntime.Tensors; using OpenCvSharp; using System; using System.Collections.Generic; using System.Drawing; using System.Drawing.Imaging; using System.Windows.Forms;namespace 图像修复 {pu…...
ruoyi项目登录验证变更
背景:我用的是ruoyi-vue3.8.6版本,因公司需要使用window的域用户进行登录验证,因此原有的ruoyi登录验证方法就得替换掉 1. 首先登录系统添加一些域账号,以确保登录方式更改后,能在sys_user中找到该账号,因…...
maven依赖冲突以及解决方法
什么是依赖冲突 依赖冲突是指项目依赖的某一个jar包,有多个不同的版本,因而造成类包版本冲突 依赖冲突的原因 依赖冲突很经常是类包之间的间接依赖引起的。每个显式声明的类包都会依赖于一些其它的隐式类包,这些隐式的类包会被maven间接引…...
K8S常用的一些命令及工具
已json格式输出 [rootk8master1 ~]# yum install epel-release [rootk8master1 ~]# yum -y install jq [rootk8master1 ~]# kubectl get --raw /api/v1/namespaces/dev | jq {"kind": "Namespace","apiVersion": "v1","metadata…...
 
AI Agent与Agentic AI:原理、应用、挑战与未来展望
文章目录 一、引言二、AI Agent与Agentic AI的兴起2.1 技术契机与生态成熟2.2 Agent的定义与特征2.3 Agent的发展历程 三、AI Agent的核心技术栈解密3.1 感知模块代码示例:使用Python和OpenCV进行图像识别 3.2 认知与决策模块代码示例:使用OpenAI GPT-3进…...
AtCoder 第409场初级竞赛 A~E题解
A Conflict 【题目链接】 原题链接:A - Conflict 【考点】 枚举 【题目大意】 找到是否有两人都想要的物品。 【解析】 遍历两端字符串,只有在同时为 o 时输出 Yes 并结束程序,否则输出 No。 【难度】 GESP三级 【代码参考】 #i…...
相机Camera日志分析之三十一:高通Camx HAL十种流程基础分析关键字汇总(后续持续更新中)
【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了:有对最普通的场景进行各个日志注释讲解,但相机场景太多,日志差异也巨大。后面将展示各种场景下的日志。 通过notepad++打开场景下的日志,通过下列分类关键字搜索,即可清晰的分析不同场景的相机运行流程差异…...
 
图表类系列各种样式PPT模版分享
图标图表系列PPT模版,柱状图PPT模版,线状图PPT模版,折线图PPT模版,饼状图PPT模版,雷达图PPT模版,树状图PPT模版 图表类系列各种样式PPT模版分享:图表系列PPT模板https://pan.quark.cn/s/20d40aa…...
 
均衡后的SNRSINR
本文主要摘自参考文献中的前两篇,相关文献中经常会出现MIMO检测后的SINR不过一直没有找到相关数学推到过程,其中文献[1]中给出了相关原理在此仅做记录。 1. 系统模型 复信道模型 n t n_t nt 根发送天线, n r n_r nr 根接收天线的 MIMO 系…...
 
vulnyx Blogger writeup
信息收集 arp-scan nmap 获取userFlag 上web看看 一个默认的页面,gobuster扫一下目录 可以看到扫出的目录中得到了一个有价值的目录/wordpress,说明目标所使用的cms是wordpress,访问http://192.168.43.213/wordpress/然后查看源码能看到 这…...
Oracle11g安装包
Oracle 11g安装包 适用于windows系统,64位 下载路径 oracle 11g 安装包...
uniapp 集成腾讯云 IM 富媒体消息(地理位置/文件)
UniApp 集成腾讯云 IM 富媒体消息全攻略(地理位置/文件) 一、功能实现原理 腾讯云 IM 通过 消息扩展机制 支持富媒体类型,核心实现方式: 标准消息类型:直接使用 SDK 内置类型(文件、图片等)自…...
 
【Linux】Linux安装并配置RabbitMQ
目录 1. 安装 Erlang 2. 安装 RabbitMQ 2.1.添加 RabbitMQ 仓库 2.2.安装 RabbitMQ 3.配置 3.1.启动和管理服务 4. 访问管理界面 5.安装问题 6.修改密码 7.修改端口 7.1.找到文件 7.2.修改文件 1. 安装 Erlang 由于 RabbitMQ 是用 Erlang 编写的,需要先安…...
【Kafka】Kafka从入门到实战:构建高吞吐量分布式消息系统
Kafka从入门到实战:构建高吞吐量分布式消息系统 一、Kafka概述 Apache Kafka是一个分布式流处理平台,最初由LinkedIn开发,后成为Apache顶级项目。它被设计用于高吞吐量、低延迟的消息处理,能够处理来自多个生产者的海量数据,并将这些数据实时传递给消费者。 Kafka核心特…...
