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 1111111111111111
下面例子给出了利用不同尺寸的滤波器分别处理不含有噪声的图像、含有椒盐噪声的图像和含有高斯噪声的图像。通过结果可以发现,滤波器的尺寸越大,滤波后图像变得越模糊:

#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((ksize1)0.51+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(ik)2+(jl)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(ik)2+(jl)22σ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;
}

从结果可以看出,滤波直径越大,滤波效果越明显,当滤波直径相同时,标准差值越大,滤波效果越明显。能对人脸起到美颜效果。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/110794.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【MultiOTP】在Linux上使用MultiOTP进行SSH登录

在前面的文章中【FreeRADIUS】使用FreeRADIUS进行SSH身份验证已经了解过如何通过Radius去来实现SSH和SUDO的登录&#xff0c;在接下来的文章中只是将密码从【LDAP PASSWORD Googlt OTP】改成了【MultiOTP】生成的passcode&#xff0c;不在需要密码&#xff0c;只需要OTP去登录…

大鼠药代动力学(PK参数/ADME)+毒性 实验结果分析

在真实做实验的时候&#xff0c;出现了下面真实测试的一些参数&#xff0c;一起学习一下&#xff1a; 大鼠药代动力学&#xff1a; 为了进一步了解化合物 96 的药代动力学性质&#xff0c;我们选择化合物 500 进行 SD大鼠药代动力学评估。 经静脉注射和口服给药后观察大鼠血药…

广东广西大量工地建筑支模

近年来&#xff0c;广东广西地区的建筑工地发展迅猛&#xff0c;为满足日益增长的建筑需求&#xff0c;大量工地都需要使用支模模板。支模模板是建筑施工中不可或缺的重要工具&#xff0c;用于搭建楼层、梁柱等结构的模板系统。在广东广西&#xff0c;有许多专业的支模模板厂家…

TCP/IP(二十一)TCP 实战抓包分析(五)TCP 第三次握手 ACK 丢包

一 实验三&#xff1a;TCP 第三次握手 ACK 丢包 第三次握手丢失了,会发生什么? 注意: ACK 报文是不会有重传的,当 ACK 丢失了,就由对方重传对应的报文 ① 实验环境 ② 模拟方式 1、 服务端配置防火墙iptables -t filter -I INPUT -s 172.25.2.157 -p tcp --tcp-flag ACK…

基于Springboot服装商品管理系统免费分享

基于Springboot服装商品管理系统 作者: 公众号(擎云毕业设计指南) 更多毕设项目请关注公众号&#xff0c;获取更多项目资源。如需部署请联系作者 注&#xff1a;禁止使用作者开源项目进行二次售卖&#xff0c;发现必究&#xff01;&#xff01;&#xff01; 运行环境&…

Flink之Watermark水印、水位线

Watermark水印、水位线 水位线概述水印本质生成WatermarkWatermark策略WatermarkStrategy工具类使用Watermark策略 内置Watermark生成器单调递增时间戳分配器固定延迟的时间戳分配器 自定义WatermarkGenerator周期性Watermark生成器标记Watermark生成器Watermark策略与Kafka连接…

新版pycharm(2023.2.2)修改字体大小

下载了2023新版pycharm&#xff0c;想修改字体&#xff0c;发现找不到之前的setting入口&#xff0c;网上搜索也都是file-setting-editor这些&#xff0c;自己找了找&#xff0c;记录下 2023版pycharm的修改字体大小在file-Manage IDE Settings-Setting Sync… 里面&#xff0…

一篇文章带你弄懂编译和链接

一篇文章带你弄懂编译和链接 文章目录 一篇文章带你弄懂编译和链接一、环境二、翻译环境1.编译①预处理②编译③汇编 2.链接 三、运行环境 一、环境 翻译环境和运行环境 翻译环境&#xff1a;源代码被转换成可执行的机器指令。 运行环境&#xff1a;用于实际执行代码。 二、…

设计模式之抽象工厂模式

前言 工厂模式一般指的是简单工厂模式、工厂方法模式、抽象工厂模式&#xff0c;这是三种工厂模式的最后一篇&#xff0c;其他两种的文章链接如下&#xff1a; 设计模式之简单工厂模式-CSDN博客 设计模式之工厂方法模式-CSDN博客 建议三种模式放在一起对比学习&#xff0c;…

亚马逊测评关于IP和DNS的问题

最近不少人询问了关于IP和DNS的问题&#xff0c;在此进行一些科普。 当客户端试图访问一个网站时&#xff0c;首先会向其所在的ISP的DNS服务器进行查询。如果ISP的DNS服务器没有相关缓存&#xff0c;则会向上级DNS服务器进行查询。 一些诸如CDN之类的服务&#xff0c;可能会为…

云安全—责任共担

0x00 前言 云安全的职责范围实际上一直遵循的是&#xff0c;谁提供谁负责&#xff0c;如果交付给云消费者的时候&#xff0c;交付者使用过程中就要自行负责&#xff0c;也就是我们经常遇到的配置不当等问题&#xff0c;在三层服务模式中&#xff0c;责任互相嵌套&#xff0c;最…

IT运维管理系统在国有大型企业网络中的应用和可以解决的问题

随着国有大型企业业务的快速发展&#xff0c;网络运维管理面临着诸多挑战。本文将从问题概述、解决方案、监控易优势、实际案例和总结等方面阐述IT运维管理系统在国有大型企业网络中的应用和可以解决的问题。​IT运维管理系统&#xff1a;国有大型企业网络的变革者与解决之道 一…

MMKV源码解读与理解

概述 通过 mmap 技术实现的高性能通用 key-value 组件。同时选用 protobuf 协议&#xff0c;进一步压缩数据存储。 标准 protobuf 不提供增量更新的能力&#xff0c;每次写入都必须全量写入。考虑到主要使用场景是频繁地进行写入更新&#xff0c;我们需要有增量更新的能力&am…

批量修改视频尺寸:简单易用的视频剪辑软件教程

如果你需要批量修改视频尺寸&#xff0c;同时保持高质量的画质&#xff0c;那么“固乔剪辑助手”这款软件是你的不二之选。下面就是如何使用这款软件进行批量修改视频尺寸的详细步骤。 1. 首先&#xff0c;你需要在浏览器中进入“固乔科技”的官网&#xff0c;然后下载并安装“…

大数据 DataX 数据同步数据分析入门

目录 一、DataX 概览 1.1 DataX 是什么 1.2 DataX 3.0 概览 设计理念 当前使用现状 二、DataX 详解 2.1 DataX 3.0 框架设计 2.2 DataX 3.0 插件体系 2.3 DataX 3.0 核心架构 2.3.1 核心模块介绍 2.3.2 DataX 调度流程 2.4 DataX 3.0 的六大核心优势 2.4.1 可靠的…

Linux考试复习整理

文章目录 Linux考试整理一.选择题1.用户的密码现象放置在哪个文件夹&#xff1f;2.删除文件或目录的命令是&#xff1f;3.显示一个文件最后几行的命令是&#xff1f;4.删除一个用户并同时删除用户的主目录5.Linux配置文件一般放在什么目录&#xff1f;6.某文件的组外成员的权限…

MongoDB常用脚本汇总

概述 本文汇总记录日常工作中常用的MongoDB查询脚本。 实战 新增 新增集合&#xff1a; db.getSiblingDB("corpus").createCollection(message);删除 删除一条数据&#xff1a; db.getSiblingDB("cx_user").userAccount.deleteOne({_id: ObjectId(6…

科技与时尚共进化,优衣库以硬实力创造品牌长期价值

时尚总是轮回&#xff0c;服装产品如何保持长青&#xff1f;对优衣库来说&#xff0c;产品力不褪色的密码之一&#xff0c;就是始终坚持推动服装科技与时尚融合&#xff0c;赋予生活潮流更多内涵&#xff0c;和更高品质的穿搭体验。 这一点&#xff0c;往往在每年换季新品上市…

2023年【氧化工艺】考试报名及氧化工艺考试总结

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 氧化工艺考试报名是安全生产模拟考试一点通总题库中生成的一套氧化工艺考试总结&#xff0c;安全生产模拟考试一点通上氧化工艺作业手机同步练习。2023年【氧化工艺】考试报名及氧化工艺考试总结 1、【单选题】 由和O…

多维时序 | MATLAB实现SSA-CNN-BiGRU-Attention多变量时间序列预测(SE注意力机制)

多维时序 | MATLAB实现SSA-CNN-BiGRU-Attention多变量时间序列预测&#xff08;SE注意力机制&#xff09; 目录 多维时序 | MATLAB实现SSA-CNN-BiGRU-Attention多变量时间序列预测&#xff08;SE注意力机制&#xff09;预测效果基本描述模型描述程序设计参考资料 预测效果 基本…