线性滤波

图像滤波是指去除图像中不重要的内容而使关心的内容表现得更加清晰的方法,例如去除图像中的噪声、提取某些信息等

图像滤波是图像处理中不可缺少的部分,图像滤波结果的好坏对后续从图中获取更多数据和信息具有重要的影响

根据图像滤波的不同,可以将图像滤波分为消除图像噪声的滤波和提取图像中部分特征信息的滤波

图像滤波需要保证图像关注的信息不在滤波过程中被破坏,因此图像去除噪声应最大程度不损坏图像的轮廓和边缘信息

同时图像去除噪声后使图像视觉效果更加清晰

去除图像中的噪声称作图像的平滑或者图像去噪,由于噪声信号在图像中主要集中在高频段

因此图像去噪可以看作去除图像中高频段信号的同时图像的低频段和中频段信号的滤波操作

图像滤波使用的滤波器决定了滤波操作是去除噪音还是提取特征信号

因此,使用允许低频和中频信号通过的滤波器,效果就是去除噪声,图像中纹理变化越明显的区域信号频率就越高

因此使用高通滤波器对图像信号处理可以起到对图像边缘信息的提取、增强和图像锐化的作用

图像的线性滤波操作与图像的卷积操作过程相似,但是图像滤波不需要将滤波模板旋转180°

卷积操作中的卷积模板在图像滤波中称为滤波模板、滤波器或者领域算子

图像滤波分为线性滤波和非线性滤波,常见的线性滤波包括均值滤波、方框滤波和高斯滤波

常见的非线性滤波包括中值滤波和双边滤波

均值滤波blur()

在测量数据时,往往会多次测量、最后求所有数据的平均值作为最终结果,均值滤波和求取平均值的思想一致

均值滤波将滤波器内所有的像素值都看作中心像素值的测量,将滤波器内所有像素值的平均值作为滤波器中心处图像像素值

滤波器内所有像素值在决定中心像素值的过程中具有相同的权重

优点:在像素值变换趋势一致的情况下,可以将受噪声影响而突然变化的像素值修正为周围邻近像素值的平均值,去除噪声影响

但是这种滤波方式会缩小像素值之间的差距,使得细节信息变得更加模糊,滤波器范围越大,变模糊的效果越明显

OpenCV4提供了blur()函数用于实现图像的均值滤波

blur()函数原型:

void blur(InputArray src,
          OutputArray dst,
          Size ksize,
          Point anchor = Point(-1,-1),
          int borderType = BORDER_DEFAULT
          )
  • src:待均值滤波的图像,图像的数据类型必须是CV_8U、CV_16U、CV_16S、CV_32F和CV_64F 中之一
  • dst:均值滤波后的图像,与输入图像具有相同的尺寸、数据类型及通道数
  • ksize:卷积核尺寸(滤波器尺寸)
  • anchor:内核的基准点,默认为(-1,-1),代表内核基准点位于kernel的中心位置
  • borderType:像素外推选择标志,默认为BORDER_DEFAULT,表示不包含边界值的倒序填充

该函数第一个参数为待滤波图像(可以是彩色图像、灰度图像,甚至是Mat类型的多维矩阵数据)

第二个参数滤波后图像,保持于输入图像相同d1数据类型、尺寸及通道数

第三个参数是滤波器尺寸,输入滤波器尺寸后函数会自动确定滤波器

第四个参数为滤波器的基准点,第五个参数为图像外推方法选择标志

示例:

#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
//椒盐噪声函数
void saltAndPepper(Mat image,int n)
{
    for (int k = 0; k < n/2; ++k)
    {
        //随机确定图像中位置
        int i ,j;
        i = rand() % image.cols; //取余数运算,保证在图像的列数内
        j = rand() % image.rows; //取余数运算,保证在图像的行数内
        int write_black = rand() % 2; //判定是白色噪声还是黑色噪声
        if ( write_black == 0 ) //添加白色噪声
        {
            if ( image.type() == CV_8UC1 ) //出来灰度图像
            {
                image.at<uchar>(j,i) = 255; //白色噪声
            }
            else if ( image.type() == CV_8UC3 )  //处理彩色图像
            {
                image.at<Vec3b>(j,i)[0] = 255;   //Vec3b为OpenCV定义的3歌值的向量类型
                image.at<Vec3b>(j,i)[1] = 255;  //[]指定通道,B:0,G:1,R:2
                image.at<Vec3b>(j,i)[2] = 255;
            }
        }
        else  //添加黑色噪声
        {
            if ( image.type() == CV_8UC1 )
            {
                image.at<uchar>(j,i) = 255;
            }
            else if ( image.type() == CV_8UC3 )  //处理彩色图像
            {
                image.at<Vec3b>(j,i)[0] = 0;   //Vec3b为OpenCV定义的3歌值的向量类型
                image.at<Vec3b>(j,i)[1] = 0;  //[]指定通道,B:0,G:1,R:2
                image.at<Vec3b>(j,i)[2] = 0;
            }
        }

    }
}
void GaussNorise(Mat equalLena_gauss)  //只处理灰度图像
{
    Mat equalLena_noise = Mat::zeros(equalLena_gauss.rows,equalLena_gauss.cols,equalLena_gauss.type());
    RNG rng; //创建一个RNG类
    rng.fill(equalLena_noise,RNG::NORMAL,15,30); //生成单通道的高斯分布随机数
    equalLena_gauss = equalLena_gauss + equalLena_noise; //在灰度图像中添加高斯噪声
}
int main()
{
    Mat equalLena = imread("E:\\CLion\\opencv_xin\\SG\\SG_0.jpg",IMREAD_ANYDEPTH);
    Mat equalLena_gauss = imread("E:\\CLion\\opencv_xin\\SG\\SG_0.jpg",IMREAD_ANYDEPTH);
    Mat equalLena_salt = imread("E:\\CLion\\opencv_xin\\SG\\SG_0.jpg",IMREAD_ANYDEPTH);
    if (equalLena.empty() || equalLena_gauss.empty() || equalLena_salt.empty())
    {
        cout << "Error" << endl;
        return -1;
    }
    Mat result_3,result_9; //存放不含噪声滤波结果,数字代表滤波器尺寸
    Mat result_3gauss,result_9gauss; //存放含有高斯噪声滤波结果,数字表示滤波器尺寸
    Mat result_3salt,result_9salt; //存放含有椒盐噪声滤波结果,数字表示滤波器尺寸
    saltAndPepper(equalLena_salt,10000); //灰度图像添加椒盐噪声
    GaussNorise(equalLena_gauss); //灰度图像添加高斯噪声
    //调用均值滤波函数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;
}

滤波器的尺寸越大,滤波后图像变的越模糊

方框滤波boxFilter()

方框滤波是均值滤波的一般形式,方框滤波也是求滤波器内所有像素值的和,但是方框滤波可以选择不进行归一化

将所有像素值的和作为滤波结果,而不是所有像素值的平均值

OpenCV4提供了boxFilter()函数实现方框滤波

boxFilter()函数原型:

void boxFilter(InputArray src,
               OutputArray dst,
               int ddepth,
               Size ksize,
               Point anchor = Point(-1,-1),
               bool normalize = ture,
               int borderType = BORDER_DEFAULT
               )
  • src:输入图像
  • dst:输出图像,与输入图像具有相同的尺寸和通道数
  • ddepth:输出图像的数据类型(深度),根据输入图像的数据类型不同,拥有不同的取值范围(表:图像卷积)

当赋值为-1时,输出图像的数据类型自动选择

  • ksize:卷积核尺寸(滤波器尺寸)
  • anchor:内核的基准点,默认为(-1,-1),代表内核基准点位于kernel的中心位置
  • normalize:是否将卷积核进行归一化的标志,默认参数为ture,表示进行归一化
  • borderType:像素外推选择标志,默认为BORDER_DEFAULT,表示不包含边界值的倒序填充

该函数的使用方法与均值滤波函数blur()相似,区别之一为该函数可以选择输出图像的数据类型

第六个参数表示是否对滤波器内所有数值进行归一化操作,默认状态下参数需要对滤波器内所有的数值进行归一化

此时,在不考虑数据类型的情况下,方框滤波和均值滤波具有相同的滤波结果

除了对滤波器内每个像素值直接求和之外,OpenCV4还提供了sqrBoxFilter()函数实现对滤波器内每个像素值的平方求和

根据输入参数选择是否进行归一化操作

sqrBoxFilter()函数原型:

void sqrBoxFilter(InputArray src,
                  OutputArray dst,
                  int ddepth,
                  Size ksize,
                  Point anchor = Point(-1,-1),
                  bool normalize = ture,
                  int borderType = BORDER_DEFAULT
                  )

该函数是在boxFilter()函数功能基础上进行的功能扩展,参数与boxFilter()相似

该函数在处理图像滤波的任务主要是针对CV_32F数据类型的图像,在归一化后,图像在变模糊的同时亮度也会变暗

示例:

#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int main()
{
    Mat equalLena = imread("E:\\CLion\\opencv_xin\\SG\\SG_1.jpg",IMREAD_ANYDEPTH); //用于方框滤波的图像
    if (equalLena.empty())
    {
        cout << "Error" << 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_32Sqr;
    //方框滤波函数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_32Sqr,-1,Size(3,3),Point(-1,-1),true,BORDER_CONSTANT);
    //显示处理结果
    imshow("resultNorm",resultNorm);
    imshow("result",result);
    imshow("equalLena_32FSqr",equalLena_32Sqr);
    waitKey(0);
    return 0;
}

高斯滤波GaussianBlur()

高斯噪声是一种常见的噪声,OpenCV4提供了对图像进行高斯滤波操作的GaussianBlur()函数

GaussianBlur()函数原型:

void 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方向的高斯滤波器标准偏差
  • borderType:像素外推法选择标志,默认参数为BORDER_DEFAULT,表示不包含边界值倒序填充

该函数能够根据输入参数自动生成高斯滤波器,实现对图像的高斯滤波

允许输入尺寸为0和正奇数,如果尺寸为0,那么由标准偏差计算尺寸

高斯滤波器的尺寸和标准偏差存在着一定的互相转换关系

OpenCV4提供了输入滤波器单一方向尺寸和标准偏差生成单一方向高斯滤波器的getGaussianKernel()函数

getGaussianKernel()函数原型:

Mat getGaussianKernel(int ksize,
                      double sigma,
                      int ktype = CV_64F
                      )
  • ksize:高斯滤波器的尺寸
  • sigma:高斯滤波的标准差
  • ktype:滤波器系数的数据类型,可以是CV_32F或者CV_64F,默认数据类型为CV_64F

该函数用于生成指定尺寸的高斯滤波器,需要注意的是

第一个参数为高斯滤波器尺寸,必须是一个正奇数,第二个参数表示高斯滤波的标准差(标准偏差)

示例:

#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
void saltAndPepper(Mat image,int n)
{
    for (int k = 0; k < n/2; ++k)
    {
        //随机确定图像中位置
        int i ,j;
        i = rand() % image.cols; //取余数运算,保证在图像的列数内
        j = rand() % image.rows; //取余数运算,保证在图像的行数内
        int write_black = rand() % 2; //判定是白色噪声还是黑色噪声
        if ( write_black == 0 ) //添加白色噪声
        {
            if ( image.type() == CV_8UC1 ) //出来灰度图像
            {
                image.at<uchar>(j,i) = 255; //白色噪声
            }
            else if ( image.type() == CV_8UC3 )  //处理彩色图像
            {
                image.at<Vec3b>(j,i)[0] = 255;   //Vec3b为OpenCV定义的3歌值的向量类型
                image.at<Vec3b>(j,i)[1] = 255;  //[]指定通道,B:0,G:1,R:2
                image.at<Vec3b>(j,i)[2] = 255;
            }
        }
        else  //添加黑色噪声
        {
            if ( image.type() == CV_8UC1 )
            {
                image.at<uchar>(j,i) = 255;
            }
            else if ( image.type() == CV_8UC3 )  //处理彩色图像
            {
                image.at<Vec3b>(j,i)[0] = 0;   //Vec3b为OpenCV定义的3歌值的向量类型
                image.at<Vec3b>(j,i)[1] = 0;  //[]指定通道,B:0,G:1,R:2
                image.at<Vec3b>(j,i)[2] = 0;
            }
        }

    }
}
void GaussNorise(Mat equalLena_gauss)  //只处理灰度图像
{
    Mat equalLena_noise = Mat::zeros(equalLena_gauss.rows,equalLena_gauss.cols,equalLena_gauss.type());
    RNG rng; //创建一个RNG类
    rng.fill(equalLena_noise,RNG::NORMAL,15,30); //生成单通道的高斯分布随机数
    equalLena_gauss = equalLena_gauss + equalLena_noise; //在灰度图像中添加高斯噪声
}
int main()
{
    Mat equalLena = imread("E:\\CLion\\opencv_xin\\SG\\SG_0.jpg",IMREAD_ANYDEPTH);
    Mat equalLena_gauss = imread("E:\\CLion\\opencv_xin\\SG\\SG_0.jpg",IMREAD_ANYDEPTH);
    Mat equalLena_salt = imread("E:\\CLion\\opencv_xin\\SG\\SG_0.jpg",IMREAD_ANYDEPTH);
    if (equalLena.empty() || equalLena_gauss.empty() || equalLena_salt.empty())
    {
        cout << "Error" << endl;
        return -1;
    }
    Mat result_5,result_9; //存放不含噪声滤波结果,数字代表滤波器尺寸
    Mat result_5gauss,result_9gauss; //存放含有高斯噪声滤波结果,数字表示滤波器尺寸
    Mat result_5salt,result_9salt; //存放含有椒盐噪声滤波结果,数字表示滤波器尺寸
    saltAndPepper(equalLena_salt,10000); //灰度图像添加椒盐噪声
    GaussNorise(equalLena_gauss); //灰度图像添加高斯噪声
    //调用高斯滤波函数Gaussian 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;
}

高斯滤波对高斯噪声去除效果较好,但是同样会对图像造成模糊,且滤波器的尺寸越大,滤波后图像变得越模糊

可分离滤波sepFilter2D()

前面滤波函数使用的滤波器都是固定形式的滤波器,有时需要根据实际需求调整滤波模板

例如:滤波器中心位置的像素值不参与计算、滤波器中参与计算的像素值不是一个矩形区域

OpenCV4提供了根据自定义滤波器实现图像滤波的函数,即卷积函数filter2D()

称为滤波函数更加准确一些,输入的卷积模板也应该称为滤波器或者滤波模板

只需要根据需要定义一个卷积模板或者滤波器,便可以实现自定义滤波

在原始图像上移动滤波器的过程中每一次的计算结果都不会影响到后面过程的计算结果,因此图像滤波是一个并行算法

在可以提供并行并行计算的处理器中可以极大地加快图像滤波器的处理速度,图像滤波还具有可分离性

可分离性指的是先对X(Y)方向滤波,再对Y(X)反向滤波的结果与将两个方向的滤波器联合后整体滤波的结果相同

两个方向的滤波器的联合就是将两个方向的滤波器相乘,得到一个矩形的滤波器

无论是先进行X方向滤波还是Y方向滤波,两个方向联合滤波器都是相同的

因此,在高斯滤波中,我们利用getGaussianKernel()函数分别得到X方向和Y方向的滤波器

之后通过生成联合滤波器或者分别用两个方向的滤波器进行滤波,计算结果相同

两个方向联合滤波需要在使用filter2D()函数滤波之前计算联合滤波器,而两个方向分别滤波需要调用2次filter2D()函数

增加了代码的复杂度,因此OpenCV4提供了可以输入两个滤波器实现滤波的滤波函数sepFilter2D()函数

sepFilter2D()函数原型:

void 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:输出图像的数据类型(深度),根据输入图像的数据类型不同,拥有不同的取值范围(表:图像卷积)

当赋值为-1时,输出图像的数据类型自动选择

  • kernelX:X方向的滤波器
  • kernelY:Y方向的滤波器
  • anchor:内核的基准点,默认为(-1,-1),代表内核基准点位于kernel的中心位置
  • delta:偏值,在计算结果中加上偏值
  • borderType:像素外推选择标志,默认为BORDER_DEFAULT,表示不包含边界值的倒序填充(2.3.4仿射变换)

该函数将可分离的线性滤波器分离为X方向和Y方向进行处理,与filter2D()函数不同之处在于,filter2D()函数需要通过滤波器

尺寸区分滤波操作是作用在X方向还是Y方向,例如 Kx1 的尺寸是Y方向滤波器,1xK 的尺寸是X方向滤波器

而sepFilter2D()函数通过不同参数区分滤波器是作用X方向还是Y方向,无论输入滤波器的尺寸是 Kx1还是1xK,都不会影响滤波结果

示例:

#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int main()
{
    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);     //reshape()函数,将矩阵变为设置矩阵,这里是通道数为1,行数为1的矩阵
    Mat ab = a*b;
    //验证高斯滤波的可分离性
    Mat gaussX = getGaussianKernel(3,1); //生成X方向的单一滤波器
    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,a,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);
    //a滤波器和b滤波器的数值上相同,只是方向不同,因此在sepFilter2D中X、Y方向的滤波器是a或b结果都是相同的
    //输出可分离滤波和联合滤波的计算结果
    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("E:\\CLion\\opencv_xin\\SG\\SG_0.jpg");
    if (img.empty())
    {
        cout << "Error" << 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;
}

reshape()函数原型:

Mat Mat::reshape(int cn,
                 int rows = 0
                 )
  • cn:通道数,如果设为0就表示通道数不变
  • rows:表示矩阵行数,如果设为0就表示保持原有的行数不变

results matching ""

    No results matching ""