OpenCV从入门到入魔(3):图像去噪与线性滤波(均值、方框、高斯)

530 阅读18分钟

图像滤波(filtering),又称图像去噪或模糊(blur)或平滑(smooth),从某些程度上来说它们属于一个概念,其目的是在尽量保留图像细节特征的条件下对目标图像的噪声进行抑制,是图像预处理中不可缺少的操作,图像滤波处理效果的好坏直接影响到后续图像处理和分析的有效性和可靠性。常见的图像滤波有线性滤波、非线性滤波、形态学滤波等,本节将重点阐述线性滤波相关算法的核心原理以及OpenCV中API的使用。

1. 点算子与邻域算子

 在讲解点算子和邻域算子之前,我们先来了解下图像处理算子的概念,在上一篇博文中谈到,一副数字图像在计算机中实质就是一个数值矩阵,矩阵中的每个元素就是图像中的像素,所谓对图像的处理其实就是对图像中像素的处理,而处理的方法或称算法即为算子。一般的图像处理算子都是一个函数,它接受一个或多个输入图像,并产生输出图像,它的一般形式为:

 g(x) = h(f(x)) 或者 g(x) = h(f0(x)...fn(x))

1.1 点算子

 点算子,也称点操作,是图像处理变换中最为简单的一种,它的特点是输出的像素值只与输入的像素值有关,无输入像素相邻的像素无关。常见的点算子有图像的亮度调整、对比度调整、变换以及颜色校正等,这里以图像的亮度和对比度处理算子为例,函数表达式为:

 g(i,j) = a*f(i,j) + b;

 其中,i,j表示像素位于矩阵的第i行、第j列;f(i,j)表示源图像(i,j)位置像素;g(i,j)表示输出图像像素;参数a(a>0)称为增益,用于控制图像的对比度;参数b称为偏置,用于控制图像亮度。示例代码:

/**
* created by jiangdongguo on 20180503
* 调整图像亮度和对比度
*/
void resizeBrightnessAndContrast(int a,int b) {
	Mat src, dst;
	src = imread("yuwenwen.jpg");
	if (!src.data) {
		printf("加载源图像失败");
	}
	namedWindow("源图像");
	imshow("源图像", src);
	// dst矩阵尺寸、像素类型与源图像一致,并初始化置零
	dst = Mat::zeros(src.size(), src.type());
	// 遍历所有像素,使用点算子处理
	// g(i,j) = a*f(i,j) + b;
	for (int i = 0; i < src.rows; i++) {
		for (int j = 0; j < src.cols; j++) {
			if (src.channels() == 1) {
				// 灰度图像
				dst.at<uchar>(i, j) = saturate_cast<uchar>(a*src.at<uchar>(i,j) + b);
			} else {
				// 彩色图像
				for (int channel = 0; channel < src.channels(); channel++) {
					dst.at<Vec3b>(i, j)[channel] = saturate_cast<uchar>(a*src.at<Vec3b>(i,j)[channel]+b);
				}
			}
		}
	}
	imshow("a=2,b=2效果图", dst);
}

 说明:OpenCV中使用Mat::at<像素类型>(i,j)方法返回矩阵中(i,j)位置元素的引用,即访问图像矩阵中(i,j)位置像素,其中<..>尖括号提供的是一种模板方法,用于指定访问像素的类型。比如矩阵的类型为CV_8U,对应为Mat::at<uchar>(i,j); 矩阵类型为CV_32F对应为Mat::at<float>(i,j),当然,对于多通道而言,我们还可以使用Vec3b、Vec4b等向量类型,如CV_8UC3,对应为Mat::at<Vec3b>(i,j)。另外,在处理像素值时,需要注意计算后的像素值超过类型范围情况,比如对于灰度图像像素值或BRG/RGB彩色图像单通道分量取值范围为0-255之间,我们使用saturate_cast<uchar>函数来处理,其中尖括号指定数据类型,或称取值范围。

效果演示: 这里写图片描述

1.2 邻域算子

 邻域算子是图像处理中非常重要的一种算子,常用于图像滤波(平滑、锐化)、图像边缘增强以及局部色调调整等方面,它的特点是给定像素的最终输出值,不仅与自身有关,还与该像素周围像素的值有关。本文将要介绍的线性邻域滤波就是一种常用的领域算子,像素的输出值取决于输入像素的加权和。具体过程如下图所示: 这里写图片描述  上图演示了从原图像中取一个3x3的矩阵,使之与另一个3x3矩阵相乘,然后得到一个新的3x3的矩阵,再将新的3x3的矩阵的所有元素进行累加,最终得到235,即为原图像第(2,2)个像素的经过处理的值,而这一过程又称为邻域卷积

1.3 图像归一化

图像归一化是指对图像进行了一系列标准的处理变换,使之变换为一固定标准形式的过程,该标准图像称作归一化图像。它的基本原理:首先,利用图像中对仿射变换具有不变性的矩确定变换函数的参数,然后,利用此参数确定的变换函数把原始图像变换为一个标准形式的图像,且该标准形式图像对平移、旋转、缩放等仿射变换具有不变性。
举个例子:在制作表情图像时,一般表情图像经过特征块切割后,切割生成的特征块大小不一,这就需要采取归一化操作来统一特征块图像的尺寸。OpenCV中实现图像归一化操作的函数为normalize,其函数原型为:

/** 归一化数组元素在一定的范围内
 *  src:输入数组
 *  dst:输出数组
 *  alpha: range normalization模式的最小值
 *  beta:range normalization模式的最大值,不用于norm normalization(范数归一化)模式。
 *  norm_type:归一化的类型,可以有以下的取值:
    - NORM_MINMAX:数组的数值被平移或缩放到一个指定的范围,线性归一化,一般较常用。
    - NORM_INF:此类型的定义没有查到,根据OpenCV 1的对应项,可能是归一化数组的C-范数(绝对值的最大值)
    - NORM_L1:归一化数组的L1-范数(绝对值的和)
    - NORM_L2:归一化数组的(欧几里德)L2-范数
 *  dtype:dtype为负数时,输出数组的type与输入数组的type相同;否则
        输出数组与输入数组只是通道数相同,而tpye=CV_MAT_DEPTH(dtype).
 *  mask:操作掩膜,用于指示函数是否仅仅对指定的元素进行操作。
 *
 */
void normalize(InputArray src,OutputArray dst, double alpha=1, 
        double beta=0, int norm_type=NORM_L2, int dtype=-1, InputArray mask=noArray() )

2. 线性滤波

 滤波是将信号中特定波段频率滤除的操作,是抑制和防止干扰的一项重要措施,以便将原始信号的有用信息通过各种组合来凸显出来。在图像处理中,对邻域中的像素的计算为线性运算时,如利用窗口函数进行平滑加权求和的运算,或者某种卷积运算,都可以称为线性滤波。常见的线性滤波有:均值滤波、高斯滤波、方框滤波、拉普拉斯滤波等,通常线性滤波器之间只是模版系数或称滤波器加权系数不同。 这里写图片描述
上图是一个典型的图像线性滤波操作过程,它描述的是左边图像与中间图像的卷积产生右边图像,即中间的图像通过扫描方式不断地在输入图像上找到相同大小的矩阵进行加权后操作,直到计算出所有可计算的像素,最终得到卷积处理后的输出图像。如果用数学方式来表述,可为:
这里写图片描述这里写图片描述
即线性滤波处理的输出像素值g(i,j) 是输入像素值f(i+k,j+I)h(k,I)的加权和;或称输出像素值g是输入像素值f与h的卷积。其中,h(k,I)被称为"核"或窗口函数,是滤波器的加权系数,是一个kxI大小的矩阵,它还有一个锚点的概念,锚点的作用就是基于“核”指定要处理的目标像素,这个接下来会讲到。

2.1 方框滤波

1. 基本理论
方框滤波是最简单的线性滤波,每个输出像素都是其内核邻域像素的平均值(它们都贡献了相同的权重),它所用的核h(k,l)为:
这里写图片描述
其中,这里写图片描述,当normalize=true时,使用归一化把要处理的量都缩放到一个范围内,比如(0,1),以便统一处理和直观量化,此时方框滤波就变成了均值滤波;当normalize=false时,方框滤波用于计算每个像素邻域内的积分特性,比如密集光流算法(dense optical flow algorithms)中用到的图像倒数的协方差矩阵(covariance matrices of image derivatives)。 2. boxFilter函数源码解析  (1) boxFilter函数原型

/**boxFilter函数 
    src:输入图像,即源图像,填Mat类对象;
    dst:输出图像,需要和源图像有一样的尺寸和深度;
    ddepth:输出图像的颜色深度,ddepth=-1表示使用输入图像的深度
    ksize:平滑处理内核的大小,通常为正奇数1、3、5...;
    anchor:Point类型的anchor表示锚点,即被平滑处理的那个像素点,
        其默认值为(-1,-1),表示锚点位于内核的正中心;
    normalize:归一化,默认为true;
    borderType:处理图像边缘像素的方法,默认为BORDER_DEFAULT,其他还
    包括BORDER_CONSTANT、BORDER_REPLICATE、BORDER_REFLECT等,这部分我们下一节详细分析。
*/
void boxFilter( InputArray src, OutputArray dst, int ddepth,
                             Size ksize, Point anchor = Point(-1,-1),
                             bool normalize = true,
                             int borderType = BORDER_DEFAULT );

 (2) 源码分析
boxFilter函数源码位于...\opencv-3.3.0-win-sdk\sources\modules\imgproc\src目录下smooth.cpp源文件中,具体源代码如下:

void cv::boxFilter( InputArray _src, OutputArray _dst, int ddepth,
                Size ksize, Point anchor,
                bool normalize, int borderType )
{
    ...
    // 创建源图像的Mat对象副本,即临时变量
    // _src和src对象指向均指向源图像的矩阵数据
    Mat src = _src.getMat();
    // 获取图像像素类型、深度sdepth、源图像通道cn,如果ddepth<0
    // 则将ddepth设置同源图像一致
    int stype = src.type(), sdepth = CV_MAT_DEPTH(stype), cn = CV_MAT_CN(stype);
    if( ddepth < 0 )
        ddepth = sdepth;
    // 调用Mat的create方法初始化目标图像,指定尺寸、深度和通道数
    _dst.create( src.size(), CV_MAKETYPE(ddepth, cn) );
    // 创建目标图像的Mat副本,即临时变量
    // _dst和dst对象指向均指向源图像的矩阵数据
    Mat dst = _dst.getMat();
    // borderType相关判断与处理(无需理解)
    if( borderType != BORDER_CONSTANT && normalize && (borderType & BORDER_ISOLATED) != 0 )
    {
        if( src.rows == 1 )
            ksize.height = 1;
        if( src.cols == 1 )
            ksize.width = 1;
    }
    // 如果使用优化选项,使用tegra::box进行方框滤波操作
#ifdef HAVE_TEGRA_OPTIMIZATION
    if ( tegra::useTegra() && tegra::box(src, dst, ksize, anchor, normalize, borderType) )
        return;
#endif
    // borderType相关判断处理(无需理解)
    CV_IPP_RUN_FAST(ipp_boxfilter(src, dst, ksize, anchor, normalize, borderType));

    Point ofs;
    Size wsz(src.cols, src.rows);
    if(!(borderType&BORDER_ISOLATED))
        src.locateROI( wsz, ofs );
    borderType = (borderType&~BORDER_ISOLATED);
    // 创建方框滤波器
    Ptr<FilterEngine> f = createBoxFilter( src.type(), dst.type(),
                        ksize, anchor, normalize, borderType );
    // 将滤波器操作应用到指定的图像
    f->apply( src, dst, wsz, ofs );
}

 从上述源码可知,boxFilter函数首先为源图像创建一个Mat对象临时变量,同时获取源图像的深度、通道数等参数并做ddepth小于0时等情况的处理;然后通过调用Mat类的create函数初始化目标图像的Mat对象,同时创建该对象的副本;最后createBoxFilter函数会调用makePtr<FilterEngine>函数来创建FilterEngine滤波引擎,再执行它的apply函数完成对图像的方框滤波操作。需要注意的是,FilterEngine类是OpenCV关于图像滤波核心类,它几乎可以将所有滤波操作应用到图像上。FilterEngine.cpp源码(..\openCV3.3.0\opencv\sources\modules\imgproc\src目录)如下:

class FilterEngine
{
public:
    //! 默认构造函数
    FilterEngine();
    //! 带参构造函数
    FilterEngine(const Ptr<BaseFilter>& _filter2D,
                 const Ptr<BaseRowFilter>& _rowFilter,
                 const Ptr<BaseColumnFilter>& _columnFilter,
                 int srcType, int dstType, int bufType,
                 int _rowBorderType = BORDER_REPLICATE,
                 int _columnBorderType = -1,
                 const Scalar& _borderValue = Scalar());
    //! 析构函数
    virtual ~FilterEngine();
    //! 释放已分配的滤波器,重新初始化引擎
    void init(const Ptr<BaseFilter>& _filter2D,
              const Ptr<BaseRowFilter>& _rowFilter,
              const Ptr<BaseColumnFilter>& _columnFilter,
              int srcType, int dstType, int bufType,
              int _rowBorderType = BORDER_REPLICATE,
              int _columnBorderType = -1,
              const Scalar& _borderValue = Scalar());

    //! 开始对指定了ROI区域(size)和尺寸(wholeSize)的图像进行滤波操作
    virtual int start(const cv::Size &wholeSize, const cv::Size &sz, const cv::Point &ofs);
    //! 开始对指定了ROI区域(size)的图像进行滤波操作
    virtual int start(const Mat& src, const cv::Size &wsz, const cv::Point &ofs);
    //! 处理图像的下一个srcCount行
    virtual int proceed(const uchar* src, int srcStep, int srcCount,
                        uchar* dst, int dstStep);
    //! 对图像指定的ROI区域进行滤波操作,若srcRoi=(0,0,-1,-1),则对整个图像进行滤波操作
    virtual void apply(const Mat& src, Mat& dst, const cv::Size &wsz, const cv::Point &ofs);

    //! 如果滤波器可分离,则返回true
    bool isSeparable() const { return !filter2D; }
    //! 返回输入和输出的行数
    int remainingInputRows() const;
    int remainingOutputRows() const;

    int srcType;
    int dstType;
    int bufType;
    Size ksize;
    Point anchor;
    int maxWidth;
    Size wholeSize;
    Rect roi;
    int dx1;
    int dx2;
    int rowBorderType;
    int columnBorderType;
    std::vector<int> borderTab;
    int borderElemSize;
    std::vector<uchar> ringBuf;
    std::vector<uchar> srcRow;
    std::vector<uchar> constBorderValue;
    std::vector<uchar> constBorderRow;
    int bufStep;
    int startY;
    int startY0;
    int endY;
    int rowCount;
    int dstY;
    std::vector<uchar*> rows;

    Ptr<BaseFilter> filter2D;
    Ptr<BaseRowFilter> rowFilter;
    Ptr<BaseColumnFilter> columnFilter;
};

 从代码上来看,FilterEngine代码量非常少,但往往越是简单反而越难理解。这里我们暂时不再继续进一步深入源码,只分析下FilterEngine类大概的实现原理。FilterEngine有几个关键的函数:start、proceed和apply,它们均被virtual关键字标志,即虚函数。关于虚函数,在C++开发中大家应该都不陌生,它充分体现了面向对象中的继承和多态两大特性,当我们用一个基类指针或引用指向一个继承类对象时,然后调用继承类中重载于基类的被virtual标记的成员函数时,该基类指针或引用实质调用的是继承类的版本。也就是说,FilterEngine应该是图像滤波处理中的一个基类,apply等函数的具体实现功能,即选用哪种滤波算法由它的继承类决定。我想正是这个原因,便赋予了FilterEngine强大的通用功能。

实战演练

 (1) C++实现代码(Visual Studio2015)

void ImageSmoothing::boxFilterImage(Mat srcImage, Mat &dstImage,int ksize) {
  if (ksize <= 0) {
     printf("ksize should be > 0, and the best to be odd number like 1,3,5,7...\n");
     return;
  }
  if (!srcImage.data) {
    printf("open source image error!\n");
    return;
  }
  boxFilter(srcImage, dstImage, -1, Size(ksize, ksize));
}

 (2) 效果演示:
这里写图片描述

2.2 均值滤波

1. 基本理论
均值滤波是归一化后的方框滤波,输出图像的每一个像素是核窗口内输入图像对应像素的像素的平均值( 所有像素加权系数相等),它的核为:
这里写图片描述
需要注意的是,由于均值滤波所求的目标像素值是其周围像素(包含自身)的平均值,如果周围像素与目标像素存在较大差异,比如目标像素原来值为200,而周围的像素值均小于50,这样计算出来的均值将远低于200,从而导致一定程度的失真。换句话说,均值滤波不能很好地保护图像细节,在图像去噪的同时也破坏了图像的细节部分,从而使图像变得模糊,不能很好地去除噪声。 **2. blur函数源码解析 **  (1) blur函数原型

/**blur函数 
    src:输入图像,即源图像,填Mat类对象;
    dst:输出图像,需要和源图像有一样的尺寸和深度;
    ksize:平滑处理内核的大小,通常为正奇数1、3、5...;
    anchor:Point类型的anchor表示锚点,即被平滑处理的那个像素点,
        其默认值为(-1,-1),表示锚点位于内核的正中心;
    normalize:归一化标志,默认为true;
    borderType:处理图像边缘像素的方法,默认为BORDER_DEFAULT,其他还
    包括BORDER_CONSTANT、BORDER_REPLICATE、BORDER_REFLECT等,这部分我们下一节详细分析。
*/

void blur( InputArray src, OutputArray dst,
                        Size ksize, Point anchor = Point(-1,-1),
                        int borderType = BORDER_DEFAULT );

 (2) 源码分析

 blur函数源码位于...\opencv-3.3.0-win-sdk\sources\modules\imgproc\src目录下smooth.cpp源文件中。根据方块滤波和均值滤波的理论,均值滤波实质上就是方框滤波的一种特殊情况,即输出图像深度取于源图像的深度、使用归一化。具体源代码如下:

void cv::blur( InputArray src, OutputArray dst,
           Size ksize, Point anchor, int borderType )
{
    CV_INSTRUMENT_REGION()
    // 调用方块滤波函数boxFilter
    boxFilter( src, dst, -1, ksize, anchor, true, borderType );
}

实战演练

 (1) C++实现(Visual Studio2015)

void ImageSmoothing::blurImage(Mat srcImage, Mat &dstImage, int ksize) {
	if (ksize <= 0) {
		printf("ksize should be > 0, and the best to be odd number 
		        like 1,3,5,7...\n");
		return;
	}
	if (!srcImage.data) {
		printf("open source image error!\n");
		return;
	}
	blur(srcImage, dstImage, Size(ksize, ksize));;
}

 (2) 效果演示:
这里写图片描述

2.3 高斯滤波

1. 基本理论
(1) 高斯分布
高斯分布,又名正态分布,是具有两个参数μ和σ^2的连续型随机变量的概率分布,若随机变量X服从一个数学期望(均值)为μ、方差为σ^2的正态分布,记为N(μ,σ^2)。一维正态分布公式为:
这里写图片描述  正态分布曲线反映了随机变量的分布规律,理论上的正态分布曲线是一条中间高,两端逐渐下降且完全对称的钟形曲线。 这里写图片描述  从上图可知,该曲线以μ为对称轴,它是服从正态分布的随机变量的均值/期望,因此描述正态分布的集中趋势位置,概率规律为取与μ邻近的值的概率大,而取离μ越远的值的概率越小;σ^2描述的是正态分布资料数据分布的离散程度,σ越大,数据分布越分散,σ越小,数据分布越集中。(σ(读作sigma)为标准差,可借助而曲线的波峰为1/(σ√2π)理解)

(2) 高斯滤波
高斯滤波是一种线性平滑滤波,它的核心是利用高斯函数来计算得到滤波器系数,即核。高斯滤波的处理思路是对整幅图像进行加权平均的过程,每一个像素点的值,都由其本身和邻域内的其他像素值经过加权平均后得到,适用于消除高斯噪声,对抑制服从正态分布的噪声非常有效。通俗的讲,高斯滤波就是用一个模板(或称卷积、掩模)扫描图像中的每一个像素,用模板确定的邻域内像素的加权平均灰度值去替代模板中心像素点的值。
由于图像是一个二维矩阵,因此在图像处理中,我们使用的是二维高斯函数来实现高斯滤波。二维高斯函数如下:
这里写图片描述
其中,A表示的是幅值1/(σ√2π)、x。和y。是中心点坐标、σx 、σy是为x轴和y轴 方向上的方差,根据上文对σ的描述,它们分别表示正态分布资料数据在x轴和y轴上分布的离散程度。下图所示A = 1, xo = 0, yo = 0, σx = σy = 1时二维正态分布曲线:
这里写图片描述

2. GaussianBlur函数源码解析
(1) GaussianBlur函数原型

/**blur函数 
    src:输入图像,即源图像,填Mat类对象;
    dst:输出图像,需要和源图像有一样的尺寸和深度;
    ksize:平滑处理内核的大小,通常为正奇数1、3、5...;
    sigmaX:高斯核函数在X方向上的标准偏差,值越小方向模糊程度越小,反正越大
    sigmaY:高斯核函数在Y方向上的标准偏差,如果sigmaY是0,则函数会自动将sigmaY的值设置为与sigmaX相同的值,值越小,数值越集中,该方向上模糊程度越小
       如果sigmaX和sigmaY都是0,这两个值将由ksize.width和ksize.height计算而来,
       即sigma = 0.3*((ksize-1)*0.5 - 1) + 0.8
    borderType:处理图像边缘像素的方法,默认为BORDER_DEFAULT,其他还
       包括BORDER_CONSTANT、BORDER_REPLICATE、BORDER_REFLECT等,
       这部分我们下一节详细分析。
*/

void GaussianBlur( InputArray src, OutputArray dst, Size ksize,
                                double sigmaX, double sigmaY = 0,
                                int borderType = BORDER_DEFAULT );

 在图像处理中,根据线性滤波原理,σ越小,波峰越大,数值越集中,因此模糊程度小;σ越大,波峰越小,数值越分散,因此模糊程度越大。以下是sigma=1.5和sigma=0.5计算得到的高斯滤波器系数(二维高斯分布,满足加权和为1),可以看出sigma=0.5数值越集中在波峰处(矩阵中心),数值分布集中,对图像进行加权和后的目标像素,其值越接近原来被平滑之前的数值。
这里写图片描述 这里写图片描述  (2) 源码分析

 GaussianBlur函数源码位于...\opencv-3.3.0-win-sdk\sources\modules\imgproc\src目录下smooth.cpp源文件中。具体源代码如下:

void cv::GaussianBlur( InputArray _src, OutputArray _dst, Size ksize,
                   double sigma1, double sigma2,
                   int borderType )
{
    CV_INSTRUMENT_REGION()

    int type = _src.type();
    Size size = _src.size();
    _dst.create( size, type );
    // 图像边缘处理
    if( borderType != BORDER_CONSTANT && (borderType & BORDER_ISOLATED) != 0 )
    {
        if( size.height == 1 )
            ksize.height = 1;
        if( size.width == 1 )
            ksize.width = 1;
    }

    if( ksize.width == 1 && ksize.height == 1 )
    {
        _src.copyTo(_dst);
        return;
    }

    CV_OVX_RUN(true,
               openvx_gaussianBlur(_src, _dst, ksize, sigma1, sigma2, borderType))

#ifdef HAVE_TEGRA_OPTIMIZATION
    Mat src = _src.getMat();
    Mat dst = _dst.getMat();
    if(sigma1 == 0 && sigma2 == 0 && tegra::useTegra() && tegra::gaussian(src, dst, ksize, borderType))
        return;
#endif
    bool useOpenCL = (ocl::useOpenCL() && _dst.isUMat() && _src.dims() <= 2 &&
               ((ksize.width == 3 && ksize.height == 3) ||
               (ksize.width == 5 && ksize.height == 5)) &&
               _src.rows() > ksize.height && _src.cols() > ksize.width);
    (void)useOpenCL;

    CV_IPP_RUN(!useOpenCL, ipp_GaussianBlur( _src,  _dst,  ksize, sigma1,  sigma2, borderType));
   // 创建滤波器系数,即核,其表现形式参考上文所示
    Mat kx, ky;
    createGaussianKernels(kx, ky, type, ksize, sigma1, sigma2);

    CV_OCL_RUN(useOpenCL, ocl_GaussianBlur_8UC1(_src, _dst, ksize, CV_MAT_DEPTH(type), kx, ky, borderType));
   // 调用sepFilter2D函数实现高斯滤波
   // 该方法是处理二维图像滤波通用方法,通过自定义核并结合sepFilter2D实现滤波
    sepFilter2D(_src, _dst, CV_MAT_DEPTH(type), kx, ky, Point(-1,-1), 0, borderType );
}

实战演练

 (1) C++实现(Visual Studio2015)

void ImageSmoothing::gaussianBlurImage(Mat srcImage, Mat &dstImage, int ksize) {
	if (ksize <= 0) {
		printf("ksize should be > 0, and the best to be odd number like 1,3,5,7...\n");
		return;
	}
	if (!srcImage.data) {
		printf("open source image error!\n");
		return;
	}
	GaussianBlur(srcImage, dstImage, Size(ksize, ksize), 0, 0);
}

 (4) 效果演示:
这里写图片描述

3. Android Studio代码

/** 图像滤波,包括线性、非线性
 * Created by jiangdongguo on 2018/5/4.
 */

public class ImageSmoothActivity extends AppCompatActivity {
    private String TAG;
    private static final String imagePath = Environment.getExternalStorageDirectory().getAbsolutePath()
            + File.separator+"yuwenwenn.jpg";
    private Mat mSrcImage;
    private Bitmap srcBm,boxBm,blurBm,gaussianBm;

    // 加载OpenCV库
    static {
        System.loadLibrary("opencv341");
    }

    private LoaderCallbackInterface mLoaderCallback = new BaseLoaderCallback(this) {
        @Override
        public void onManagerConnected(int status) {
            switch (status) {
                // OpenCV引擎加载成功
                case LoaderCallbackInterface.SUCCESS:
                    Log.i(TAG, "OpenCV loaded successfully.");
                    // 读取图像,将BRG转换为RGB,否则会出现颜色问题
                    mSrcImage = Imgcodecs.imread(imagePath);
                    Imgproc.cvtColor(mSrcImage,mSrcImage,Imgproc.COLOR_BGR2RGB);

                    int width = mSrcImage.width();
                    int height = mSrcImage.height();
                    srcBm = Bitmap.createBitmap(width,height, Bitmap.Config.RGB_565);
                    boxBm = Bitmap.createBitmap(width,height, Bitmap.Config.RGB_565);
                    blurBm = Bitmap.createBitmap(width,height, Bitmap.Config.RGB_565);
                    gaussianBm = Bitmap.createBitmap(width,height, Bitmap.Config.RGB_565);

                    // 将滤波处理后的mat转换为Bitmap
                    Utils.matToBitmap(mSrcImage,srcBm);
                    Utils.matToBitmap(boxFilterOperator(),boxBm);
                    Utils.matToBitmap(blurFilterOperator(),blurBm);
                    Utils.matToBitmap(GaussianBlurOperator(),gaussianBm);
                    // 显示效果
                    ((ImageView)findViewById(R.id.image1)).setImageBitmap(srcBm);
                    ((ImageView)findViewById(R.id.image2)).setImageBitmap(boxBm);
                    ((ImageView)findViewById(R.id.image3)).setImageBitmap(blurBm);
                    ((ImageView)findViewById(R.id.image4)).setImageBitmap(gaussianBm);
                    break;
                default:
                    super.onManagerConnected(status);
                    break;
            }
        }
    };

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
        setContentView(R.layout.activity_smooth);
    }

    @Override
    protected void onResume() {
        super.onResume();
        // 静态加载
        if(!OpenCVLoader.initDebug()) {
            Log.w(TAG,"static loading library fail,Using Manager for initialization");
            OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_3_3_0, this, mLoaderCallback);
        } else {
            Log.w(TAG, "OpenCV library found inside package. Using it!");
            mLoaderCallback.onManagerConnected(LoaderCallbackInterface.SUCCESS);
        }
    }

    /** 方框滤波
     *  ddepth -1输出图像深度同输入图像深度一致
     *  size  核大小为5x5
     * */
    private Mat boxFilterOperator() {
        double ksize = 5.0f;
        int ddepth = -1;
        Mat dstImage = mSrcImage.clone();
        Imgproc.boxFilter(mSrcImage,dstImage,ddepth,new Size(ksize,ksize));
        return dstImage;
    }

    /** 均值滤波
     *  size  核大小为3x3
     * */
    private Mat blurFilterOperator() {
        double ksize = 3.0f;
        Mat dstImage = mSrcImage.clone();
        Imgproc.blur(mSrcImage,dstImage,new Size(ksize,ksize));
        return dstImage;
    }

    /** 高斯平滑滤波
     *  size 核大小为7x7
     *  sigmaX
     * */
    private Mat GaussianBlurOperator() {
        double ksize = 7.0f;
        double sigmaX = 0;
        Mat dstImage = mSrcImage.clone();
        Imgproc.GaussianBlur(mSrcImage,dstImage,new Size(ksize,ksize),sigmaX);
        return dstImage;
    }
}

效果演示:
这里写图片描述

C++源码:OpenCVImageProc (ImageSmoothing.cpp)
Android源码:OpenCVImageProc(ImageSmoothActivity.java)