《Opencv3编程入门》学习笔记—第六章

《Opencv3编程入门》学习笔记

记录一下在学习《Opencv3编程入门》这本书时遇到的问题或重要的知识点。

第六章 图像处理

一、线性滤波:方框滤波、均值滤波、高斯滤波

(一)平滑处理

平滑处理也称模糊处理,是一种简单且使用频率很高的图像处理方法。
用途:最常见的是用来减少图像上的噪点或失真。在涉及到降低图像分辨率时,平滑处理是非常好用的方法。

(二)图像滤波与滤波器

  • 图像滤波,即在尽量保留图像细节特征的条件下对目标图像的噪声进行抑制,是图像预处理中不可缺少的操作,其处理效果的好坏将直接影响到后续图像处理和分析的有效性和可靠性。

  • 消除图像中的噪声成分叫作图像的平滑化或滤波操作。信号或图像的能量大部分集中在幅度谱的低频和中频段是很常见的,而在较高频段,感兴趣的信息经常被噪声淹没。因此一个能降低高频成分幅度的滤波器就能够减弱噪声的影响。

  • 图像滤波的目的有两个:一是抽出对象的特征作为图像识别的特征模式;另一个是为适应图像处理的要求,消除图像数字化时所混入的噪声。而对滤波处理的要求也有两条:一是不能损坏图像的轮廓及边缘等重要信息;二是使图像清晰视觉效果好。

  • 平滑滤波是低频增强的空间域滤波技术。它的目的有两类:一类是模糊;另一类是消除噪音。

  • 空间域的平滑滤波一般采用简单平均法进行,就是求邻近像元点的平均亮度值。邻域的大小与平滑的效果直接相关,邻域越大平滑的效果越好,但邻域过大,平滑会使边缘信息损失的越大,从而使输出的图像变得模糊,因此需合理选择邻域的大小。

  • 滤波器:一种形象的比喻法是:我们可以把滤波器想象成一个包含加权系数的窗口,当使用这个滤波器平滑处理图像时,就把这个窗口放到图像之上,透过这个窗口来看我们得到的图像。

    滤波器的种类:
    1、方框滤波——boxblur函数
    2、均值滤波(邻域平均滤波)——blur函数
    3、高斯滤波——GaussianBlur函数
    4、中值滤波——medianBlur函数
    5、双边滤波——bilateralFilter函数

(三)线性滤波器的简介

线性滤波器:线性滤波器经常用于剔除输入信号中不想要的频率或者从许多频率中选择一个想要的频率。

几种常见的线性滤波器:

  • 允许低频率通过的低通滤波器。
  • 允许高频率通过的高通滤波器。
  • 允许一定范围频率通过的带通滤波器。
  • 阻止一定范围频率通过并且允许其它频率通过的带阻滤波器。
  • 允许所有频率通过、仅仅改变相位关系的全通滤波器。
  • 阻止一个狭窄频率范围通过的特殊带阻滤波器,陷波滤波器(Band-stop filter)。

(四)滤波与模糊

拿高斯滤波作例子:滤波可分低通滤波和高通滤波两种。而高斯滤波是指用高斯函数作为滤波函数的滤波操作,至于是不是模糊,要看是高斯低通还是高斯高通,低通就是模糊,高通就是锐化。
简单来说就是:

  • 高斯滤波是指用高斯函数作为滤波函数的滤波操作。
  • 高斯模糊就是高斯低通滤波。

(五)领域算子与线性邻域滤波

  • 邻域算子(局部算子)是利用给定像素周围的像素值的决定此像素的最终输出值的一种算子。而线性邻域滤波是一种常用的邻域算子,像素的输出值取决于输入像素的加权和,具体过程如下图。

  • 邻域算子除了用于局部色调调整以外,还可以用于图像滤波,实现图像的平滑和锐化,图像边缘增强或者图像噪声的去除。

  • 线性邻域滤波算子:即用不同的权重去结合一个小邻域内的像素,来得到应有的处理效果。

《Opencv3编程入门》学习笔记—第六章_第1张图片
图注:邻域滤波(卷积):左边图像与中间图像的卷积产生右边图像。目标图像中蓝色标记的像素是利用原图像中红色标记的像素计算得到的。

  • 线性滤波处理的输出像素值g(i,j)是输入像素值f(i+k,j+l)的加权和 :
    《Opencv3编程入门》学习笔记—第六章_第2张图片

    其中的加权和为 ,我们称其为“核”,滤波器的加权系数,即滤波器的“滤波系数”。
    上面的式子可以简单写作:
    在这里插入图片描述
    其中f表示输入像素值,h表示加权系数“核“,g表示输出像素值

  • 三种常用的线性滤波操作:
    1、方框滤波——boxblur函数
    2、均值滤波——blur函数
    3、高斯滤波——GaussianBlur函数

(六)方框滤波(box Filter)

方框滤波(box Filter)被封装在一个名为boxblur的函数中,即boxblur函数的作用是使用方框滤波器(box filter)来模糊一张图片,从src输入,从dst输出。

void boxFilter(InputArray src,OutputArray dst, int ddepth, Size ksize, Point anchor=Point(-1,-1), boolnormalize=true, int borderType=BORDER_DEFAULT )

1、第一个参数,InputArray类型的src,输入图像,即源图像,填Mat类的对象即可。该函数对通道是独立处理的,且可以处理任意通道数的图片,但需要注意,待处理的图片深度应该为CV_8U, CV_16U, CV_16S, CV_32F 以及 CV_64F之一。
2、第二个参数,OutputArray类型的dst,即目标图像,需要和源图片有一样的尺寸和类型。
3、第三个参数,int类型的ddepth,输出图像的深度,-1代表使用原图深度,即src.depth()。
4、第四个参数,Size类型(对Size类型稍后有讲解)的ksize,内核的大小。一般这样写Size( w,h )来表示内核的大小( 其中,w 为像素宽度, h为像素高度)。Size(3,3)就表示3x3的核大小,Size(5,5)就表示5x5的核大小
5、第五个参数,Point类型的anchor,表示锚点(即被平滑的那个点),注意他有默认值Point(-1,-1)。如果这个点坐标是负值的话,就表示取核的中心为锚点,所以默认值Point(-1,-1)表示这个锚点在核的中心。
6、第六个参数,bool类型的normalize,默认值为true,一个标识符,表示内核是否被其区域归一化(normalized)了。
7、第七个参数,int类型的borderType,用于推断图像外部像素的某种边界模式。有默认值BORDER_DEFAULT,我们一般不去管它。
boxFilter()函数方框滤波所用的核为:
《Opencv3编程入门》学习笔记—第六章_第3张图片
其中:
《Opencv3编程入门》学习笔记—第六章_第4张图片

  • 其中f表示原图,h表示核,g表示目标图,当normalize=true的时候,方框滤波就变成了我们熟悉的均值滤波。也就是说,均值滤波是方框滤波归一化(normalized)后的特殊情况。其中,归一化就是把要处理的量都缩放到一个范围内,比如(0,1),以便统一处理和直观量化。

  • 而非归一化(Unnormalized)的方框滤波用于计算每个像素邻域内的积分特性,比如密集光流算法(dense optical flow algorithms)中用到的图像倒数的协方差矩阵(covariance matrices of image derivatives)

  • 如果我们要在可变的窗口中计算像素总和,可以使用integral()函数。

(七)均值滤波

均值滤波,是最简单的一种滤波操作,输出图像的每一个像素是核窗口内输入图像对应像素的像素的平均值( 所有像素加权系数相等),其实说白了它就是归一化后的方框滤波。

1、均值滤波的理论简析

  • 均值滤波是典型的线性滤波算法,主要方法为邻域平均法,即用一片图像区域的各个像素的均值来代替原图像中的各个像素值。一般需要在图像上对目标像素给出一个模板(内核),该模板包括了其周围的临近像素(比如以目标像素为中心的周围8(3x3-1)个像素,构成一个滤波模板,即去掉目标像素本身)。再用模板中的全体像素的平均值来代替原来像素值。即对待处理的当前像素点(x,y),选择一个模板,该模板由其近邻的若干像素组成,求模板中所有像素的均值,再把该均值赋予当前像素点(x,y),作为处理后图像在该点上的灰度个g(x,y),即个g(x,y)=1/m ∑f(x,y) ,其中m为该模板中包含当前像素在内的像素总个数。

2、均值滤波的缺陷

  • 均值滤波本身存在着固有的缺陷,即它不能很好地保护图像细节,在图像去噪的同时也破坏了图像的细节部分,从而使图像变得模糊,不能很好地去除噪声点。

3、在OpenCV中使用均值滤波——blur函数

  • blur函数的作用是,对输入的图像src进行均值滤波后用dst输出。
  • blur函数文档中,给出的其核是这样的:
    《Opencv3编程入门》学习笔记—第六章_第5张图片

这个内核一看就明了,就是在求均值,即blur函数封装的就是均值滤波。
blur函数的原型:

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

1、第一个参数,InputArray类型的src,输入图像,即源图像,填Mat类的对象即可。该函数对通道是独立处理的,且可以处理任意通道数的图片,但需要注意,待处理的图片深度应该为CV_8U, CV_16U, CV_16S, CV_32F 以及 CV_64F之一。
2、第二个参数,OutputArray类型的dst,即目标图像,需要和源图片有一样的尺寸和类型。比如可以用Mat::Clone,以源图片为模板,来初始化得到如假包换的目标图。
3、第三个参数,Size类型(对Size类型稍后有讲解)的ksize,内核的大小。一般这样写Size( w,h )来表示内核的大小( 其中,w 为像素宽度, h为像素高度)。Size(3,3)就表示3x3的核大小,Size(5,5)就表示5x5的核大小
4、第四个参数,Point类型的anchor,表示锚点(即被平滑的那个点),注意他有默认值Point(-1,-1)。如果这个点坐标是负值的话,就表示取核的中心为锚点,所以默认值Point(-1,-1)表示这个锚点在核的中心。
5、第五个参数,int类型的borderType,用于推断图像外部像素的某种边界模式。有默认值BORDER_DEFAULT,我们一般不去管它。

(八)高斯滤波

1、高斯滤波的理论简析

  • 高斯滤波是一种线性平滑滤波,适用于消除高斯噪声,广泛应用于图像处理的减噪过程。通俗的讲,高斯滤波就是对整幅图像进行加权平均的过程,每一个像素点的值,都由其本身和邻域内的其他像素值经过加权平均后得到。

  • 高斯滤波的具体操作是:用一个模板(或称卷积、掩模)扫描图像中的每一个像素,用模板确定的邻域内像素的加权平均灰度值去替代模板中心像素点的值。

  • 大家常常说高斯滤波最有用的滤波操作,虽然它用起来,效率往往不是最高的。

  • 高斯模糊技术生成的图像,其视觉效果就像是经过一个半透明屏幕在观察图像,这与镜头焦外成像效果散景以及普通照明阴影中的效果都明显不同。高斯平滑也用于计算机视觉算法中的预先处理阶段,以增强图像在不同比例大小下的图像效果(参见尺度空间表示以及尺度空间实现)。从数学的角度来看,图像的高斯模糊过程就是图像与正态分布做卷积。由于正态分布又叫作高斯分布,所以这项技术就叫作高斯模糊。

  • 图像与圆形方框模糊做卷积将会生成更加精确的焦外成像效果。由于高斯函数的傅立叶变换是另外一个高斯函数,所以高斯模糊对于图像来说就是一个低通滤波操作。

  • 高斯滤波器是一类根据高斯函数的形状来选择权值的线性平滑滤波器。高斯平滑滤波器对于抑制服从正态分布的噪声非常有效。一维零均值高斯函数为:
    在这里插入图片描述
    其中,高斯分布参数Sigma决定了高斯函数的宽度。对于图像处理来说,常用二维零均值离散高斯函数作平滑滤波器。

  • 二维高斯函数为:
    在这里插入图片描述

2、在OpenCV中使用高斯滤波——GaussianBlur函数

GaussianBlur函数的作用是用高斯滤波器来模糊一张图片,对输入的图像src进行高斯滤波后用dst输出。它将源图像和指定的高斯核函数做卷积运算,并且支持就地过滤(In-placefiltering)。

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

1、第一个参数,InputArray类型的src,输入图像,即源图像,填Mat类的对象即可。它可以是单独的任意通道数的图片,但需要注意,图片深度应该为CV_8U,CV_16U, CV_16S, CV_32F 以及 CV_64F之一。
2、第二个参数,OutputArray类型的dst,即目标图像,需要和源图片有一样的尺寸和类型。比如可以用Mat::Clone,以源图片为模板,来初始化得到如假包换的目标图。
3、第三个参数,Size类型的ksize高斯内核的大小。其中ksize.width和ksize.height可以不同,但他们都必须为正数和奇数。或者,它们可以是零的,它们都是由sigma计算而来。
4、第四个参数,double类型的sigmaX,表示高斯核函数在X方向的的标准偏差。
5、第五个参数,double类型的sigmaY,表示高斯核函数在Y方向的的标准偏差。若sigmaY为零,就将它设为sigmaX,如果sigmaX和sigmaY都是0,那么就由ksize.width和ksize.height计算出来。
为了结果的正确性着想,最好是把第三个参数Size,第四个参数sigmaX和第五个参数sigmaY全部指定到。
6、第六个参数,int类型的borderType,用于推断图像外部像素的某种边界模式。有默认值BORDER_DEFAULT,我们一般不去管它。

(九)线性滤波相关OpenCV源码剖析

对OpenCV中的线性滤波函数——boxFilter,blur和GaussianBlur函数以及周边的涉及到的源码进行适当的剖析。

1、OpenCV中boxFilter函数源码解析

可以在OpenCV的安装路径的/sources/modules/imgproc/src下的smooth.cpp源文件的第711行找到boxFilter函数的源代码。

//-----------------------------------【boxFilter()函数中文注释版源代码】----------------------------
//     代码作用:进行box Filter滤波操作的函数
//     说明:以下代码为来自于计算机开源视觉库OpenCV的官方源代码
//     OpenCV源代码版本:2.4.8
//     源码路径:…\opencv\sources\modules\imgproc\src\smooth.cpp
//     源文件中如下代码的起始行数:711行
//--------------------------------------------------------------------------------------------------------
void cv::boxFilter( InputArray _src,OutputArray _dst, int ddepth,
                Size ksize, Point anchor,
                bool normalize, int borderType)
{
   Mat src = _src.getMat();//拷贝源图的形参Mat数据到临时变量,用于稍后的操作
int sdepth =src.depth(), cn = src.channels();//定义int型临时变量,代表源图深度的sdepth,源图通道的引用cn
 
//处理ddepth小于零的情况
   if( ddepth < 0 )
       ddepth = sdepth;
   _dst.create( src.size(), CV_MAKETYPE(ddepth, cn) );//初始化目标图
Mat dst =_dst.getMat();//拷贝目标图的形参Mat数据到临时变量,用于稍后的操作
 
//处理 borderType不为 BORDER_CONSTANT 且normalize为真的情况
   if( borderType != BORDER_CONSTANT && normalize )    {
       if( src.rows == 1 )
           ksize.height = 1;
       if( src.cols == 1 )
           ksize.width = 1;
}
 
//若之前有过HAVE_TEGRA_OPTIMIZATION优化选项的定义,则执行宏体中的tegra优化版函数并返回
#ifdef HAVE_TEGRA_OPTIMIZATION
   if ( tegra::box(src, dst, ksize, anchor, normalize, borderType) )
       return;
#endif
 
   //调用FilterEngine滤波引擎,正式开始滤波操作
   Ptr<FilterEngine> f = createBoxFilter( src.type(), dst.type(),
                        ksize, anchor,normalize, borderType );
   f->apply( src, dst );
}

其中的Ptr是用来动态分配的对象的智能指针模板类。可以发现,函数的内部代码思路是很清晰的,先拷贝源图的形参Mat数据到临时变量,定义一些临时变量,在处理ddepth小于零的情况,接着处理 borderType不为 BORDER_CONSTANT 且normalize为真的情况,最终调用FilterEngine滤波引擎创建一个BoxFilter,正式开始滤波操作。

2、FilterEngine类解析:OpenCV图像滤波核心引擎

FilterEngine类是OpenCV关于图像滤波的主力军类,OpenCV图像滤波功能的核心引擎。各种滤波函数比如blur, GaussianBlur,到头来其实是就是在函数末尾处定义了一个Ptr类型的f,然后f->apply( src, dst )了一下而已。

这个类可以把几乎是所有的滤波操作施加到图像上。它包含了所有必要的中间缓存器。有很多和滤波相关的create系函数的返回值直接就是Ptr。比如

  • cv::createSeparableLinearFilter(),
  • cv::createLinearFilter(),
  • cv::createGaussianFilter(),
  • cv::createDerivFilter(),
  • cv::createBoxFilter(),
  • cv::createMorphologyFilter()

这里给出其中一个函数的原型吧:

Ptr<FilterEngine> createLinearFilter(int srcType, int dstType, InputArray kernel, Point_anchor=Point(-1,-1), double delta=0, int rowBorderType=BORDER_DEFAULT, intcolumnBorderType=-1, const Scalar& borderValue=Scalar() )

上面我们提到过了,其中的Ptr是用来动态分配的对象的智能指针模板类,而上面的尖括号里面的模板参数就是FilterEngine。

使用FilterEngine类可以分块处理大量的图像,构建复杂的管线,其中就包含一些进行滤波阶段。如果我们需要使用预先定义好的的滤波操作,cv::filter2D(), cv::erode(),以及cv::dilate(),可以选择,他们不依赖于FilterEngine,自立自强,在自己函数体内部就实现了FilterEngine提供的功能。不像其他的诸如我们今天讲的blur系列函数,依赖于FilterEngine引擎。

我们看下其类声明经过浅墨大神详细注释的源码:

//-----------------------------------【FilterEngine类中文注释版源代码】----------------------------
//     代码作用:FilterEngine类,OpenCV图像滤波功能的核心引擎
//     说明:以下代码为来自于计算机开源视觉库OpenCV的官方源代码
//     OpenCV源代码版本:2.4.8
//     源码路径:…\opencv\sources\modules\imgproc\include\opencv2\imgproc\imgproc.hpp
//     源文件中如下代码的起始行数:222行
 
class CV_EXPORTS FilterEngine
{
public:
   //默认构造函数
   FilterEngine();
   //完整的构造函数。 _filter2D 、_rowFilter 和 _columnFilter之一,必须为非空
   FilterEngine(const Ptr<BaseFilter>& _filter2D,
                 constPtr<BaseRowFilter>& _rowFilter,
                constPtr<BaseColumnFilter>& _columnFilter,
                 int srcType, int dstType, intbufType,
                 int_rowBorderType=BORDER_REPLICATE,
                 int _columnBorderType=-1,
                 const Scalar&_borderValue=Scalar());
   //默认析构函数
   virtual ~FilterEngine();
   //重新初始化引擎。释放之前滤波器申请的内存。
   void init(const Ptr<BaseFilter>& _filter2D,
              constPtr<BaseRowFilter>& _rowFilter,
              constPtr<BaseColumnFilter>& _columnFilter,
              int srcType, int dstType, intbufType,
              int_rowBorderType=BORDER_REPLICATE, int _columnBorderType=-1,
              const Scalar&_borderValue=Scalar());
   //开始对指定了ROI区域和尺寸的图片进行滤波操作
   virtual int start(Size wholeSize, Rect roi, int maxBufRows=-1);
    //开始对指定了ROI区域的图片进行滤波操作
   virtual int start(const Mat& src, const Rect&srcRoi=Rect(0,0,-1,-1),
                      bool isolated=false, intmaxBufRows=-1);
   //处理图像的下一个srcCount行(函数的第三个参数)
   virtual int proceed(const uchar* src, int srcStep, int srcCount,
                        uchar* dst, intdstStep);
   //对图像指定的ROI区域进行滤波操作,若srcRoi=(0,0,-1,-1),则对整个图像进行滤波操作
   virtual void apply( const Mat& src, Mat& dst,
                        const Rect&srcRoi=Rect(0,0,-1,-1),
                        Point dstOfs=Point(0,0),
                        bool isolated=false);
 
   //如果滤波器可分离,则返回true
boolisSeparable() const { return (const BaseFilter*)filter2D == 0; }
 
   //返回输入和输出行数
   int remainingInputRows() const;
intremainingOutputRows() const;
 
   //一些成员参数定义
   int srcType, dstType, bufType;
   Size ksize;
   Point anchor;
   int maxWidth;
   Size wholeSize;
   Rect roi;
   int dx1, dx2;
   int rowBorderType, columnBorderType;
   vector<int> borderTab;
   int borderElemSize;
   vector<uchar> ringBuf;
   vector<uchar> srcRow;
   vector<uchar> constBorderValue;
   vector<uchar> constBorderRow;
   int bufStep, startY, startY0, endY, rowCount, dstY;
   vector<uchar*> rows;
 
   Ptr<BaseFilter> filter2D;
   Ptr<BaseRowFilter> rowFilter;
   Ptr<BaseColumnFilter> columnFilter;
};
3、OpenCV中blur函数源码剖析

我们可以在OpenCV的安装路径的\sources\modules\imgproc\src下的smooth.cpp源文件中找到blur的源代码。

//-----------------------------------【blur()函数中文注释版源代码】----------------------------
//     代码作用:进行blur均值滤波操作的函数
//     说明:以下代码为来自于计算机开源视觉库OpenCV的官方源代码
//     OpenCV源代码版本:2.4.8
//     源码路径:…\opencv\sources\modules\imgproc\src\smooth.cpp
//     源文件中如下代码的起始行数:738行
//--------------------------------------------------------------------------------------------------------
 
void cv::blur(InputArray src, OutputArray dst,
          Size ksize, Point anchor, int borderType )
{
//调用boxFilter函数进行处理
   boxFilter( src, dst, -1, ksize, anchor, true, borderType );
}

可以看到在blur函数内部就是调用了一个boxFilter函数,且第六个参数为true,即我们上文所说的normalize=true,即均值滤波是均一化后的方框滤波。

(十)OpenCV中GaussianBlur函数源码剖析

OpenCV中GaussianBlur函数源代码:

//-----------------------------------【GaussianBlur()函数中文注释版源代码】-----------------------
//     代码作用:封装高斯滤波的GaussianBlur()函数
//     说明:以下代码为来自于计算机开源视觉库OpenCV的官方源代码
//     OpenCV源代码版本:2.4.8
//     源码路径:…\opencv\sources\modules\imgproc\src\smooth.cpp
//     源文件中如下代码的起始行数:832行
//--------------------------------------------------------------------------------------------------------
 
void cv::GaussianBlur( InputArray _src,OutputArray _dst, Size ksize,
                   double sigma1, doublesigma2,
                   int borderType )
{
//拷贝形参Mat数据到临时变量,用于稍后的操作
   Mat src = _src.getMat();
   _dst.create( src.size(), src.type() );
Mat dst =_dst.getMat();
 
//处理边界选项不为BORDER_CONSTANT时的情况
   if( borderType != BORDER_CONSTANT )
    {
       if( src.rows == 1 )
           ksize.height = 1;
       if( src.cols == 1 )
           ksize.width = 1;
}
 
       //若ksize长宽都为1,将源图拷贝给目标图
   if( ksize.width == 1 && ksize.height == 1 )
    {
       src.copyTo(dst);
       return;
}
 
//若之前有过HAVE_TEGRA_OPTIMIZATION优化选项的定义,则执行宏体中的tegra优化版函数并返回
#ifdef HAVE_TEGRA_OPTIMIZATION
   if(sigma1 == 0 && sigma2 == 0 && tegra::gaussian(src,dst, ksize, borderType))
       return;
#endif
 
//如果HAVE_IPP&& (IPP_VERSION_MAJOR >= 7为真,则执行宏体中语句
#if defined HAVE_IPP &&(IPP_VERSION_MAJOR >= 7)
   if(src.type() == CV_32FC1 && sigma1 == sigma2 &&ksize.width == ksize.height && sigma1 != 0.0 )
    {
       IppiSize roi = {src.cols, src.rows};
       int bufSize = 0;
       ippiFilterGaussGetBufferSize_32f_C1R(roi, ksize.width, &bufSize);
       AutoBuffer<uchar> buf(bufSize+128);
       if( ippiFilterGaussBorder_32f_C1R((const Ipp32f *)src.data,(int)src.step,
                                         (Ipp32f *)dst.data, (int)dst.step,
                                          roi,ksize.width, (Ipp32f)sigma1,
                                         (IppiBorderType)borderType, 0.0,
                                         alignPtr(&buf[0],32)) >= 0 )
           return;
    }
#endif
 
   //调动滤波引擎,正式进行高斯滤波操作
   Ptr<FilterEngine> f = createGaussianFilter( src.type(), ksize,sigma1, sigma2, borderType );
   f->apply( src, dst );
}

(十一)线性滤波核心API函数

快速上手boxFilter、blur、GaussianBlur这三个函数

1、方框滤波:boxFilter函数

作用:使用方框滤波(box filter)来模糊一张图片,由src输入,dst输出。

void boxFilter(InputArray src,OutputArray dst, int ddepth, Size ksize, Point anchor=Point(-1,-1), boolnormalize=true, int borderType=BORDER_DEFAULT )

参数详解如下:
1、第一个参数,InputArray类型的src,输入图像,即源图像,填Mat类的对象即可。该函数对通道是独立处理的,且可以处理任意通道数的图片,但需要注意,待处理的图片深度应该为CV_8U, CV_16U, CV_16S, CV_32F 以及 CV_64F之一。
2、第二个参数,OutputArray类型的dst,即目标图像,需要和源图片有一样的尺寸和类型。
3、第三个参数,int类型的ddepth,输出图像的深度,-1代表使用原图深度,即src.depth()。
4、第四个参数,Size类型的ksize,内核的大小。一般这样写Size( w,h )来表示内核的大小( 其中,w 为像素宽度, h为像素高度)。Size(3,3)就表示3x3的核大小,Size(5,5)就表示5x5的核大小
5、第五个参数,Point类型的anchor,表示锚点(即被平滑的那个点),注意他有默认值Point(-1,-1)。如果这个点坐标是负值的话,就表示取核的中心为锚点,所以默认值Point(-1,-1)表示这个锚点在核的中心。
6、第六个参数,bool类型的normalize,默认值为true,一个标识符,表示内核是否被其区域归一化(normalized)了。
7、第七个参数,int类型的borderType,用于推断图像外部像素的某种边界模式。有默认值BORDER_DEFAULT,我们一般不去管它。

示例代码

//-----------------------------------【头文件包含部分】---------------------------------------
//     描述:包含程序所依赖的头文件
//----------------------------------------------------------------------------------------------
#include "opencv2/core/core.hpp"
#include"opencv2/highgui/highgui.hpp"
#include"opencv2/imgproc/imgproc.hpp"
 
//-----------------------------------【命名空间声明部分】---------------------------------------
//     描述:包含程序所使用的命名空间
//----------------------------------------------------------------------------------------------- 
using namespace cv;
 
//-----------------------------------【main( )函数】--------------------------------------------
//     描述:控制台应用程序的入口函数,我们的程序从这里开始
//-----------------------------------------------------------------------------------------------
int main( )
{
       //载入原图
       Mat image=imread("D://lili/Desktop/jpg/opencv/4.jpg");
 
       //创建窗口
       namedWindow("方框滤波【原图】" );
       namedWindow("方框滤波【效果图】");
 
       //显示原图
       imshow("方框滤波【原图】", image );
 
       //进行滤波操作
       Mat out;
       boxFilter(image, out, -1,Size(10, 10));
 
       //显示效果图
       imshow("方框滤波【效果图】" ,out );
 
       waitKey(0 );    
}

运行效果

2、均值滤波:blur函数

作用:对输入的图像src进行均值滤波后用dst输出。

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

参数详解如下:
1、第一个参数,InputArray类型的src,输入图像,即源图像,填Mat类的对象即可。该函数对通道是独立处理的,且可以处理任意通道数的图片,但需要注意,待处理的图片深度应该为CV_8U, CV_16U, CV_16S, CV_32F 以及 CV_64F之一。
2、第二个参数,OutputArray类型的dst,即目标图像,需要和源图片有一样的尺寸和类型。比如可以用Mat::Clone,以源图片为模板,来初始化得到如假包换的目标图。
3、第三个参数,Size类型(对Size类型稍后有讲解)的ksize,内核的大小。一般这样写Size( w,h )来表示内核的大小( 其中,w 为像素宽度, h为像素高度)。Size(3,3)就表示3x3的核大小,Size(5,5)就表示5x5的核大小
4、第四个参数,Point类型的anchor,表示锚点(即被平滑的那个点),注意他有默认值Point(-1,-1)。如果这个点坐标是负值的话,就表示取核的中心为锚点,所以默认值Point(-1,-1)表示这个锚点在核的中心。
5、第五个参数,int类型的borderType,用于推断图像外部像素的某种边界模式。有默认值BORDER_DEFAULT,我们一般不去管它。

示例代码

//-----------------------------------【头文件包含部分】---------------------------------------
//     描述:包含程序所依赖的头文件
//----------------------------------------------------------------------------------------------
#include "opencv2/core/core.hpp"
#include"opencv2/highgui/highgui.hpp"
#include"opencv2/imgproc/imgproc.hpp"
#include 
 
//-----------------------------------【命名空间声明部分】---------------------------------------
//     描述:包含程序所使用的命名空间
//----------------------------------------------------------------------------------------------- 
using namespace cv;
 
//-----------------------------------【main( )函数】--------------------------------------------
//     描述:控制台应用程序的入口函数,我们的程序从这里开始
//-----------------------------------------------------------------------------------------------
int main( )
{
       //载入原图
       Mat image=imread("D://lili/Desktop/jpg/opencv/4.jpg");
 
       //创建窗口
       namedWindow("均值滤波【原图】" );
       namedWindow("均值滤波【效果图】");
 
       //显示原图
       imshow("均值滤波【原图】", image );
 
       //进行滤波操作
       Mat out;
       blur(image, out, Size(10, 10));
 
       //显示效果图
       imshow("均值滤波【效果图】" ,out );
 
       waitKey(0 );    
}

运行效果

3、高斯滤波:GaussianBlur函数

作用:用高斯滤波器来模糊一张图片,对输入的图像src进行高斯滤波后用dst输出。

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

参数详解如下:
1、第一个参数,InputArray类型的src,输入图像,即源图像,填Mat类的对象即可。它可以是单独的任意通道数的图片,但需要注意,图片深度应该为CV_8U,CV_16U, CV_16S, CV_32F 以及 CV_64F之一。
2、第二个参数,OutputArray类型的dst,即目标图像,需要和源图片有一样的尺寸和类型。比如可以用Mat::Clone,以源图片为模板,来初始化得到如假包换的目标图。
3、第三个参数,Size类型的ksize高斯内核的大小。其中ksize.width和ksize.height可以不同,但他们都必须为正数和奇数。或者,它们可以是零的,它们都是由sigma计算而来。
4、第四个参数,double类型的sigmaX,表示高斯核函数在X方向的的标准偏差。
5、第五个参数,double类型的sigmaY,表示高斯核函数在Y方向的的标准偏差。若sigmaY为零,就将它设为sigmaX,如果sigmaX和sigmaY都是0,那么就由ksize.width和ksize.height计算出来。
为了结果的正确性着想,最好是把第三个参数Size,第四个参数sigmaX和第五个参数sigmaY全部指定到。
6、第六个参数,int类型的borderType,用于推断图像外部像素的某种边界模式。注意它有默认值BORDER_DEFAULT。

示例代码

//-----------------------------------【头文件包含部分】---------------------------------------
//     描述:包含程序所依赖的头文件
//----------------------------------------------------------------------------------------------
#include "opencv2/core/core.hpp"
#include"opencv2/highgui/highgui.hpp"
#include"opencv2/imgproc/imgproc.hpp"
#include 
 
//-----------------------------------【命名空间声明部分】---------------------------------------
//     描述:包含程序所使用的命名空间
//----------------------------------------------------------------------------------------------- 
using namespace cv;
 
//-----------------------------------【main( )函数】--------------------------------------------
//     描述:控制台应用程序的入口函数,我们的程序从这里开始
//-----------------------------------------------------------------------------------------------
int main( )
{
       //载入原图
       Mat image=imread("D://lili/Desktop/jpg/opencv/4.jpg");
 
       //创建窗口
       namedWindow("高斯滤波【原图】" );
       namedWindow("高斯滤波【效果图】");
 
       //显示原图
       imshow("高斯滤波【原图】", image );
 
       //进行均值滤波操作
       Mat out;
       GaussianBlur(image, out, Size(10,10), 0, 0 );
 
       //显示效果图
       imshow("高斯滤波【效果图】" ,out );
 
       waitKey(0);    
}

运行效果

(十二)图像线性滤波综合示例

//-----------------------------------【程序说明】----------------------------------------------
//            程序名称::【OpenCV入门教程之八】线性滤波专场:方框滤波、均值滤波与高斯滤波 
//            开发所用OpenCV版本:2.4.8
//------------------------------------------------------------------------------------------------
 
//-----------------------------------【头文件包含部分】---------------------------------------
//     描述:包含程序所依赖的头文件
//----------------------------------------------------------------------------------------------
#include 
#include
#include 
#include 
 
//-----------------------------------【命名空间声明部分】---------------------------------------
//     描述:包含程序所使用的命名空间
//----------------------------------------------------------------------------------------------- 
using namespace std;
using namespace cv;
 
 
//-----------------------------------【全局变量声明部分】--------------------------------------
//     描述:全局变量声明
//-----------------------------------------------------------------------------------------------
Mat g_srcImage,g_dstImage1,g_dstImage2,g_dstImage3;//存储图片的Mat类型
int g_nBoxFilterValue=3;  //方框滤波参数值
int g_nMeanBlurValue=3;  //均值滤波参数值
int g_nGaussianBlurValue=3;  //高斯滤波参数值
 
 
//-----------------------------------【全局函数声明部分】--------------------------------------
//     描述:全局函数声明
//-----------------------------------------------------------------------------------------------
//轨迹条的回调函数
static void on_BoxFilter(int, void *);            //方框滤波
static void on_MeanBlur(int, void *);           //均值滤波
static void on_GaussianBlur(int, void *);                    //高斯滤波
 
 
 
//-----------------------------------【main( )函数】--------------------------------------------
//     描述:控制台应用程序的入口函数,我们的程序从这里开始
//-----------------------------------------------------------------------------------------------
int main(  )
{
       //改变console字体颜色
       system("color5E"); 
 
       //载入原图
       g_srcImage= imread( "D://lili/Desktop/jpg/opencv/4.jpg", 1 );
       if(!g_srcImage.data ) { printf("读取srcImage错误~!\n"); return false; }
 
       //克隆原图到三个Mat类型中
       g_dstImage1= g_srcImage.clone( );
       g_dstImage2= g_srcImage.clone( );
       g_dstImage3= g_srcImage.clone( );
 
       //显示原图
       namedWindow("【<0>原图窗口】", 1);
       imshow("【<0>原图窗口】",g_srcImage);
 
 
       //=================【<1>方框滤波】==================
       //创建窗口
       namedWindow("【<1>方框滤波】", 1);
       //创建轨迹条
       createTrackbar("内核值:", "【<1>方框滤波】",&g_nBoxFilterValue, 40,on_BoxFilter );
       on_MeanBlur(g_nBoxFilterValue,0);
       imshow("【<1>方框滤波】", g_dstImage1);
       //================================================
 
       //=================【<2>均值滤波】==================
       //创建窗口
       namedWindow("【<2>均值滤波】", 1);
       //创建轨迹条
       createTrackbar("内核值:", "【<2>均值滤波】",&g_nMeanBlurValue, 40,on_MeanBlur );
       on_MeanBlur(g_nMeanBlurValue,0);
       //================================================
 
       //=================【<3>高斯滤波】=====================
       //创建窗口
       namedWindow("【<3>高斯滤波】", 1);
       //创建轨迹条
       createTrackbar("内核值:", "【<3>高斯滤波】",&g_nGaussianBlurValue, 40,on_GaussianBlur );
       on_GaussianBlur(g_nGaussianBlurValue,0);
       //================================================
 
 
       //输出一些帮助信息
       cout<<endl<<"\t嗯。好了,请调整滚动条观察图像效果~\n\n"
              <<"\t按下“q”键时,程序退出~!\n"
              <<"\n\n\t\t\t\t";
 
       //按下“q”键时,程序退出
       while(char(waitKey(1))!= 'q') {}
 
       return 0;
}
 
 
//-----------------------------【on_BoxFilter( )函数】------------------------------------
//     描述:方框滤波操作的回调函数
//-----------------------------------------------------------------------------------------------
static void on_BoxFilter(int, void *)
{
       //方框滤波操作
       boxFilter(g_srcImage, g_dstImage1, -1,Size( g_nBoxFilterValue+1, g_nBoxFilterValue+1));
       //显示窗口
       imshow("【<1>方框滤波】", g_dstImage1);
}
 
 
//-----------------------------【on_MeanBlur( )函数】------------------------------------
//     描述:均值滤波操作的回调函数
//-----------------------------------------------------------------------------------------------
static void on_MeanBlur(int, void *)
{
       //均值滤波操作
       blur(g_srcImage, g_dstImage2, Size( g_nMeanBlurValue+1, g_nMeanBlurValue+1),Point(-1,-1));
       //显示窗口
       imshow("【<2>均值滤波】", g_dstImage2);
}
 
 
//-----------------------------【on_GaussianBlur( )函数】------------------------------------
//     描述:高斯滤波操作的回调函数
//-----------------------------------------------------------------------------------------------
static void on_GaussianBlur(int, void *)
{
       //高斯滤波操作
       GaussianBlur(g_srcImage, g_dstImage3, Size( g_nGaussianBlurValue*2+1,g_nGaussianBlurValue*2+1 ), 0, 0);
       //显示窗口
       imshow("【<3>高斯滤波】", g_dstImage3);
}

二、非线性滤波:中值滤波、双边滤波

线性滤波可以实现很多种不同的图像变换。而非线性滤波,如中值滤波器和双边滤波器,有时可以达到更好的实现效果。

(一)非线性滤波概述

在很多情况下,使用邻域像素的非线性滤波也许会得到更好的效果。比如在噪声是散粒噪声而不是高斯噪声,即图像偶尔会出现很大的值的时候。在这种情况下,用高斯滤波器对图像进行模糊的话,噪声像素是不会被去除的,它们只是转换为更为柔和但仍然可见的散粒。

(二)中值滤波

  • 中值滤波(Median filter)是一种典型的非线性滤波技术,基本思想是用像素点邻域灰度值的中值来代替该像素点的灰度值,该方法在去除脉冲噪声、椒盐噪声的同时又能保留图像边缘细节,.

  • 中值滤波是基于排序统计理论的一种能有效抑制噪声的非线性信号处理技术,其基本原理是把数字图像或数字序列中一点的值用该点的一个邻域中各点值的中值代替,让周围的像素值接近的真实值,从而消除孤立的噪声点,对于斑点噪声(speckle noise)和椒盐噪声(salt-and-pepper noise)来说尤其有用,因为它不依赖于邻域内那些与典型值差别很大的值。中值滤波器在处理连续图像窗函数时与线性滤波器的工作方式类似,但滤波过程却不再是加权运算。

  • 中值滤波在一定的条件下可以克服常见线性滤波器如最小均方滤波、方框滤波器、均值滤波等带来的图像细节模糊,而且对滤除脉冲干扰及图像扫描噪声非常有效,也常用于保护边缘信息, 保存边缘的特性使它在不希望出现边缘模糊的场合也很有用,是非常经典的平滑噪声处理方法。

  • 中值滤波与均值滤波器比较
    1、中值滤波器与均值滤波器比较的优势:在均值滤波器中,由于噪声成分被放入平均计算中,所以输出受到了噪声的影响,但是在中值滤波器中,由于噪声成分很难选上,所以几乎不会影响到输出。因此同样用3x3区域进行处理,中值滤波消除的噪声能力更胜一筹。中值滤波无论是在消除噪声还是保存边缘方面都是一个不错的方法。
    2、中值滤波器与均值滤波器比较的劣势:中值滤波花费的时间是均值滤波的5倍以上。

  • 顾名思义,中值滤波选择每个像素的邻域像素中的中值作为输出,或者说中值滤波将每一像素点的灰度值设置为该点某邻域窗口内的所有像素点灰度值的中值。

  • 例如,取3 x 3的函数窗,计算以点[i,j]为中心的函数窗像素中值步骤如下:
    (1) 按强度值大小排列像素点.
    (2) 选择排序像素集的中间值作为点[i,j]的新值.
    这一过程如图下图所示.
    《Opencv3编程入门》学习笔记—第六章_第6张图片

一般采用奇数点的邻域来计算中值,但如果像素点数为偶数时,中值就取排序像素中间两点的平均值.采用大小不同邻域的中值滤波器的结果如图。

(三)双边滤波

【待学习】看不懂

  • 双边滤波(Bilateral filter)是一种非线性的滤波方法,是结合图像的空间邻近度和像素值相似度的一种折衷处理,同时考虑空域信息和灰度相似性,达到保边去噪的目的。具有简单、非迭代、局部的特点。

  • 双边滤波器的好处是可以做边缘保存(edge preserving),一般过去用的维纳滤波或者高斯滤波去降噪,都会较明显地模糊边缘,对于高频细节的保护效果并不明显。双边滤波器顾名思义比高斯滤波多了一个高斯方差sigma-d,它是基于空间分布的高斯滤波函数,所以在边缘附近,离的较远的像素不会太多影响到边缘上的像素值,这样就保证了边缘附近像素值的保存。但是由于保存了过多的高频信息,对于彩色图像里的高频噪声,双边滤波器不能够干净的滤掉,只能够对于低频信息进行较好的滤波。

  • 在双边滤波器中,输出像素的值依赖于邻域像素值的加权值组合:
    《Opencv3编程入门》学习笔记—第六章_第7张图片
    而加权系数w(i,j,k,l)取决于定义域核和值域核的乘积。
    其中定义域核表示如下(如图):
    《Opencv3编程入门》学习笔记—第六章_第8张图片
    定义域滤波对应图示:
    《Opencv3编程入门》学习笔记—第六章_第9张图片
    值域核表示为:
    《Opencv3编程入门》学习笔记—第六章_第10张图片
    值域滤波:
    《Opencv3编程入门》学习笔记—第六章_第11张图片
    两者相乘后,就会产生依赖于数据的双边滤波权重函数:
    在这里插入图片描述

(四)非线性滤波相关核心API函数

1、中值滤波:medianBlur函数

void medianBlur(InputArray src,OutputArray dst, int ksize)

1、第一个参数,InputArray类型的src,函数的输入参数,填1、3或者4通道的Mat类型的图像;当ksize为3或者5的时候,图像深度需为CV_8U,CV_16U,或CV_32F其中之一,而对于较大孔径尺寸的图片,它只能是CV_8U。
2、第二个参数,OutputArray类型的dst,即目标图像,函数的输出参数,需要和源图片有一样的尺寸和类型。我们可以用Mat::Clone,以源图片为模板,来初始化得到如假包换的目标图。
3、第三个参数,int类型的ksize,孔径的线性尺寸(aperture linear size),注意这个参数必须是大于1的奇数,比如:3,5,7,9 …

示例代码

//-----------------------------------【程序说明】----------------------------------------------
//            说明:【中值滤波medianBlur函数的使用示例程序】
//            开发所用OpenCV版本:2.4.8
//------------------------------------------------------------------------------------------------
 
//-----------------------------------【头文件包含部分】---------------------------------------
//     描述:包含程序所依赖的头文件
//----------------------------------------------------------------------------------------------
#include "opencv2/core/core.hpp"
#include"opencv2/highgui/highgui.hpp"
#include"opencv2/imgproc/imgproc.hpp"
 
//-----------------------------------【命名空间声明部分】---------------------------------------
//     描述:包含程序所使用的命名空间
//----------------------------------------------------------------------------------------------- 
using namespace cv;
 
//-----------------------------------【main( )函数】--------------------------------------------
//     描述:控制台应用程序的入口函数,我们的程序从这里开始
//-----------------------------------------------------------------------------------------------
int main( )
{
       //载入原图
       Mat image=imread("D://lili/Desktop/jpg/opencv/4.jpg");
 
       //创建窗口
       namedWindow("中值滤波【原图】" );
       namedWindow("中值滤波【效果图】");
 
       //显示原图
       imshow("中值滤波【原图】", image );
 
       //进行中值滤波操作
       Mat out;
       medianBlur( image, out, 7);
 
       //显示效果图
       imshow("中值滤波【效果图】" ,out );
 
       waitKey(0 );    
}

运行效果

2、双边滤波

void bilateralFilter(InputArray src, OutputArraydst, int d, double sigmaColor, double sigmaSpace, int borderType=BORDER_DEFAULT)

1、第一个参数,InputArray类型的src,输入图像,即源图像,需要为8位或者浮点型单通道、三通道的图像。
2、第二个参数,OutputArray类型的dst,即目标图像,需要和源图片有一样的尺寸和类型。
3、第三个参数,int类型的d,表示在过滤过程中每个像素邻域的直径。如果这个值我们设其为非正数,那么OpenCV会从第五个参数sigmaSpace来计算出它来。
4、第四个参数,double类型的sigmaColor,颜色空间滤波器的sigma值。这个参数的值越大,就表明该像素邻域内有更宽广的颜色会被混合到一起,产生较大的半相等颜色区域。
5、第五个参数,double类型的sigmaSpace坐标空间中滤波器的sigma值,坐标空间的标注方差。他的数值越大,意味着越远的像素会相互影响,从而使更大的区域足够相似的颜色获取相同的颜色。当d>0,d指定了邻域大小且与sigmaSpace无关。否则,d正比于sigmaSpace。
6、第六个参数,int类型的borderType,用于推断图像外部像素的某种边界模式。注意它有默认值BORDER_DEFAULT。

示例代码

//-----------------------------------【程序说明】----------------------------------------------
//            说明:【双边滤波bilateralFilter函数的使用示例程序】
//            开发所用OpenCV版本:2.4.8
//------------------------------------------------------------------------------------------------
 
//-----------------------------------【头文件包含部分】---------------------------------------
//     描述:包含程序所依赖的头文件
//----------------------------------------------------------------------------------------------
#include "opencv2/core/core.hpp"
#include"opencv2/highgui/highgui.hpp"
#include"opencv2/imgproc/imgproc.hpp"
 
//-----------------------------------【命名空间声明部分】---------------------------------------
//     描述:包含程序所使用的命名空间
//----------------------------------------------------------------------------------------------- 
using namespace cv;
 
//-----------------------------------【main( )函数】--------------------------------------------
//     描述:控制台应用程序的入口函数,我们的程序从这里开始
//-----------------------------------------------------------------------------------------------
int main( )
{
       //载入原图
       Mat image=imread("D://lili/Desktop/jpg/opencv/4.jpg");
 
       //创建窗口
       namedWindow("双边滤波【原图】" );
       namedWindow("双边滤波【效果图】");
 
       //显示原图
       imshow("双边滤波【原图】", image );
 
       //进行双边滤波操作
       Mat out;
       bilateralFilter( image, out, 25, 25*2, 25/2 );
 
       //显示效果图
       imshow("双边滤波【效果图】" ,out );
 
       waitKey(0 );    
}

运行效果

(五)OpenCV中的5种图像滤波综合示例

这个示例程序中可以用轨迹条来控制各种滤波(方框滤波、均值滤波、高斯滤波、中值滤波、双边滤波)的参数值,通过滑动滚动条,就可以控制图像在各种平滑处理下的模糊度。

//-----------------------------------【程序说明】----------------------------------------------
//            程序名称::《【OpenCV入门教程之九】非线性滤波专场:中值滤波、双边滤波  》 博文配套源码
//            开发所用IDE版本:Visual Studio 2010
//          开发所用OpenCV版本: 2.4.8
//            2014年4月8日 Create by 浅墨
//------------------------------------------------------------------------------------------------
 
//-----------------------------------【头文件包含部分】---------------------------------------
//            描述:包含程序所依赖的头文件
//----------------------------------------------------------------------------------------------
#include 
#include
#include
#include 
 
//-----------------------------------【命名空间声明部分】---------------------------------------
//            描述:包含程序所使用的命名空间
//----------------------------------------------------------------------------------------------- 
using namespace std;
using namespace cv;
 
 
//-----------------------------------【全局变量声明部分】--------------------------------------
//            描述:全局变量声明
//-----------------------------------------------------------------------------------------------
Mat g_srcImage,g_dstImage1,g_dstImage2,g_dstImage3,g_dstImage4,g_dstImage5;
int g_nBoxFilterValue=6;  //方框滤波内核值
int g_nMeanBlurValue=10;  //均值滤波内核值
int g_nGaussianBlurValue=6;  //高斯滤波内核值
int g_nMedianBlurValue=10;  //中值滤波参数值
int g_nBilateralFilterValue=10;  //双边滤波参数值
 
 
//-----------------------------------【全局函数声明部分】--------------------------------------
//            描述:全局函数声明
//-----------------------------------------------------------------------------------------------
//轨迹条回调函数
static void on_BoxFilter(int, void *);            //方框滤波
static void on_MeanBlur(int, void *);           //均值块滤波器
static void on_GaussianBlur(int, void *);                    //高斯滤波器
static void on_MedianBlur(int, void *);               //中值滤波器
static void on_BilateralFilter(int, void*);                    //双边滤波器
 
 
 
//-----------------------------------【main( )函数】--------------------------------------------
//            描述:控制台应用程序的入口函数,我们的程序从这里开始
//-----------------------------------------------------------------------------------------------
int main(  )
{
       system("color 5E"); 
 
       //载入原图
       g_srcImage= imread( "D://lili/Desktop/jpg/opencv/4.jpg", 1 );
       if(!g_srcImage.data ) { printf("Oh,no,读取srcImage错误~!\n"); return false; }
 
       //克隆原图到四个Mat类型中
       g_dstImage1= g_srcImage.clone( );
       g_dstImage2= g_srcImage.clone( );
       g_dstImage3= g_srcImage.clone( );
       g_dstImage4= g_srcImage.clone( );
       g_dstImage5= g_srcImage.clone( );
 
       //显示原图
       namedWindow("【<0>原图窗口】", 1);
       imshow("【<0>原图窗口】",g_srcImage);
 
 
       //=================【<1>方框滤波】=========================
       //创建窗口
       namedWindow("【<1>方框滤波】", 1);
       //创建轨迹条
       createTrackbar("内核值:", "【<1>方框滤波】",&g_nBoxFilterValue, 50,on_BoxFilter );
       on_MeanBlur(g_nBoxFilterValue,0);
       imshow("【<1>方框滤波】", g_dstImage1);
       //=====================================================
 
 
       //=================【<2>均值滤波】==========================
       //创建窗口
       namedWindow("【<2>均值滤波】", 1);
       //创建轨迹条
       createTrackbar("内核值:", "【<2>均值滤波】",&g_nMeanBlurValue, 50,on_MeanBlur );
       on_MeanBlur(g_nMeanBlurValue,0);
       //======================================================
 
 
       //=================【<3>高斯滤波】===========================
       //创建窗口
       namedWindow("【<3>高斯滤波】", 1);
       //创建轨迹条
       createTrackbar("内核值:", "【<3>高斯滤波】",&g_nGaussianBlurValue, 50,on_GaussianBlur );
       on_GaussianBlur(g_nGaussianBlurValue,0);
       //=======================================================
 
 
       //=================【<4>中值滤波】===========================
       //创建窗口
       namedWindow("【<4>中值滤波】", 1);
       //创建轨迹条
       createTrackbar("参数值:", "【<4>中值滤波】",&g_nMedianBlurValue, 50,on_MedianBlur );
       on_MedianBlur(g_nMedianBlurValue,0);
       //=======================================================
 
 
       //=================【<5>双边滤波】===========================
       //创建窗口
       namedWindow("【<5>双边滤波】", 1);
       //创建轨迹条
       createTrackbar("参数值:", "【<5>双边滤波】",&g_nBilateralFilterValue, 50,on_BilateralFilter);
       on_BilateralFilter(g_nBilateralFilterValue,0);
       //=======================================================
 
 
       //输出一些帮助信息
       cout<<endl<<"\t嗯。好了,请调整滚动条观察图像效果~\n\n"
              <<"\t按下“q”键时,程序退出~!\n"
              <<"\n\n\t\t\t\tby浅墨";
       while(char(waitKey(1))!= 'q') {}
 
       return 0;
}
 
//-----------------------------【on_BoxFilter( )函数】------------------------------------
//            描述:方框滤波操作的回调函数
//-----------------------------------------------------------------------------------------------
static void on_BoxFilter(int, void *)
{
       //方框滤波操作
       boxFilter(g_srcImage, g_dstImage1, -1,Size( g_nBoxFilterValue+1, g_nBoxFilterValue+1));
       //显示窗口
       imshow("【<1>方框滤波】", g_dstImage1);
}
 
//-----------------------------【on_MeanBlur( )函数】------------------------------------
//            描述:均值滤波操作的回调函数
//-----------------------------------------------------------------------------------------------
static void on_MeanBlur(int, void *)
{
       blur(g_srcImage, g_dstImage2, Size( g_nMeanBlurValue+1, g_nMeanBlurValue+1),Point(-1,-1));
       imshow("【<2>均值滤波】", g_dstImage2);
 
}
 
//-----------------------------【on_GaussianBlur( )函数】------------------------------------
//            描述:高斯滤波操作的回调函数
//-----------------------------------------------------------------------------------------------
static void on_GaussianBlur(int, void *)
{
       GaussianBlur(g_srcImage, g_dstImage3, Size( g_nGaussianBlurValue*2+1,g_nGaussianBlurValue*2+1 ), 0, 0);
       imshow("【<3>高斯滤波】", g_dstImage3);
}
 
 
//-----------------------------【on_MedianBlur( )函数】------------------------------------
//            描述:中值滤波操作的回调函数
//-----------------------------------------------------------------------------------------------
static void on_MedianBlur(int, void *)
{
       medianBlur( g_srcImage, g_dstImage4, g_nMedianBlurValue*2+1 );
       imshow("【<4>中值滤波】", g_dstImage4);
}
 
 
//-----------------------------【on_BilateralFilter( )函数】------------------------------------
//            描述:双边滤波操作的回调函数
//-----------------------------------------------------------------------------------------------
static void on_BilateralFilter(int, void *)
{
       bilateralFilter( g_srcImage, g_dstImage5, g_nBilateralFilterValue, g_nBilateralFilterValue*2,g_nBilateralFilterValue/2 );
       imshow("【<5>双边滤波】", g_dstImage5);
}

运行效果

三、形态学滤波(1):腐蚀与膨胀

(一)形态学概述

  • 形态学(morphology)一词通常表示生物学的一个分支,该分支主要研究动植物的形态和结构。而我们图像处理中指的形态学,往往表示的是数学形态学。下面一起来了解数学形态学的概念。

  • 数学形态学(Mathematical morphology) 是一门建立在格论和拓扑学基础之上的图像分析学科,是数学形态学图像处理的基本理论。其基本的运算包括:二值腐蚀和膨胀、二值开闭运算、骨架抽取、极限腐蚀、击中击不中变换、形态学梯度、Top-hat变换、颗粒分析、流域变换、灰值腐蚀和膨胀、灰值开闭运算、灰值形态学梯度等。

  • 简单来讲,形态学操作就是基于形状的一系列图像处理操作。OpenCV为进行图像的形态学变换提供了快捷、方便的函数。最基本的形态学操作有二种,他们是:膨胀与腐蚀(Dilation与Erosion)。

  • 膨胀与腐蚀能实现多种多样的功能,主要如下:
    1、消除噪声
    2、分割(isolate)出独立的图像元素,在图像中连接(join)相邻的元素。
    3、寻找图像中的明显的极大值区域或极小值区域
    4、求出图像的梯度

《Opencv3编程入门》学习笔记—第六章_第12张图片

腐蚀和膨胀是对白色部分(高亮部分)而言的,不是黑色部分。膨胀就是图像中的高亮部分进行膨胀,“领域扩张”,效果图拥有比原图更大的高亮区域。腐蚀就是原图中的高亮部分被腐蚀,“领域被蚕食”,效果图拥有比原图更小的高亮区域。

(二)膨胀

  • 其实,膨胀就是求局部最大值的操作。
  • 按数学方面来说,膨胀或者腐蚀操作就是将图像(或图像的一部分区域,我们称之为A)与核(我们称之为B)进行卷积。
  • 核可以是任何的形状和大小,它拥有一个单独定义出来的参考点,我们称其为锚点(anchorpoint)。多数情况下,核是一个小的中间带有参考点和实心正方形或者圆盘,其实,我们可以把核视为模板或者掩码。
  • 而膨胀就是求局部最大值的操作,核B与图形卷积,即计算核B覆盖的区域的像素点的最大值,并把这个最大值赋值给参考点指定的像素。这样就会使图像中的高亮区域逐渐增长。如下图所示,这就是膨胀操作的初衷。
    《Opencv3编程入门》学习笔记—第六章_第13张图片
    膨胀的数学表达式:
    在这里插入图片描述
    膨胀效果图(毛笔字):
    《Opencv3编程入门》学习笔记—第六章_第14张图片

照片膨胀效果图:

(三)腐蚀

膨胀和腐蚀是相反的一对操作,所以腐蚀就是求局部最小值的操作。
《Opencv3编程入门》学习笔记—第六章_第15张图片
腐蚀的数学表达式:
在这里插入图片描述
腐蚀效果图(毛笔字):
《Opencv3编程入门》学习笔记—第六章_第16张图片

照片腐蚀效果图:

(四)相关OpenCV源码分析溯源

路径:…\opencv\sources\modules\imgproc\src\ morph.cpp路径中的第1353行开始就为erode(腐蚀)函数的源码,1361行为dilate(膨胀)函数的源码。

//-------------------【erode()函数中文注释版源代码】---------------------------- 
//    说明:以下代码为来自于计算机开源视觉库OpenCV的官方源代码 
//    OpenCV源代码版本:2.4.8 
//    源码路径:…\opencv\sources\modules\imgproc\src\ morph.cpp 
//    源文件中如下代码的起始行数:1353行  
//--------------------------------------------------------------------------------------------------------  
void cv::erode( InputArray src, OutputArraydst, InputArray kernel,
                Point anchor, int iterations,
                int borderType, constScalar& borderValue )
{
//调用morphOp函数,并设定标识符为MORPH_ERODE
   morphOp( MORPH_ERODE, src, dst, kernel, anchor, iterations, borderType,borderValue );
}
//-------------------【dilate()函数中文注释版源代码】---------------------------- 
//    说明:以下代码为来自于计算机开源视觉库OpenCV的官方源代码 
//    OpenCV源代码版本:2.4.8 
//    源码路径:…\opencv\sources\modules\imgproc\src\ morph.cpp 
//    源文件中如下代码的起始行数:1361行 
//-------------------------------------------------------------------------------------------------------- 
void cv::dilate( InputArray src,OutputArray dst, InputArray kernel,
                 Point anchor, int iterations,
                 int borderType, constScalar& borderValue )
{
//调用morphOp函数,并设定标识符为MORPH_DILATE
   morphOp( MORPH_DILATE, src, dst, kernel, anchor, iterations, borderType,borderValue );
}

erode和dilate这两个函数内部其实是调用了一下morphOp,只是他们调用morphOp时,第一个参数标识符不同,一个为MORPH_ERODE(腐蚀),一个为MORPH_DILATE(膨胀)。

morphOp函数的源码在…\opencv\sources\modules\imgproc\src\morph.cpp中的第1286行,有兴趣的可以自己看一下。

(五)相关核心API函数讲解

1、形态学膨胀——dilate函数

dilate函数:使用像素邻域内的局部极大运算符来膨胀一张图片,从src输入,由dst输出。支持就地(in-place)操作。

C++: void dilate(
	InputArray src,
	OutputArray dst,
	InputArray kernel,
	Point anchor=Point(-1,-1),
	int iterations=1,
	int borderType=BORDER_CONSTANT,
	const Scalar& borderValue=morphologyDefaultBorderValue() 
);

参数详解

  • 第一个参数,InputArray类型的src,输入图像,即源图像,填Mat类的对象即可。图像通道的数量可以是任意的,但图像深度应为CV_8U,CV_16U,CV_16S,CV_32F或 CV_64F其中之一。
  • 第二个参数,OutputArray类型的dst,即目标图像,需要和源图片有一样的尺寸和类型。
  • 第三个参数,InputArray类型的kernel,膨胀操作的核。若为NULL时,表示的是使用参考点位于中心3x3的核。

我们一般使用函数 getStructuringElement配合这个参数的使用。getStructuringElement函数会返回指定形状和尺寸的结构元素(内核矩阵)。其中,getStructuringElement函数的第一个参数表示内核的形状,我们可以选择如下三种形状之一:

  • 矩形: MORPH_RECT
  • 交叉形: MORPH_CROSS
  • 椭圆形: MORPH_ELLIPSE

而getStructuringElement函数的第二和第三个参数分别是内核的尺寸以及锚点的位置。

一般在调用erode以及dilate函数之前,先定义一个Mat类型的变量来获得getStructuringElement函数的返回值。对于锚点的位置,有默认值Point(-1,-1),表示锚点位于中心。且需要注意,十字形的element形状唯一依赖于锚点的位置。而在其他情况下,锚点只是影响了形态学运算结果的偏移。

getStructuringElement函数相关的调用示例代码如下:

int g_nStructElementSize = 3; //结构元素(内核矩阵)的尺寸
 
//获取自定义核
Mat element = getStructuringElement(MORPH_RECT,
	Size(2*g_nStructElementSize+1,2*g_nStructElementSize+1),
	Point( g_nStructElementSize, g_nStructElementSize ));

调用这样之后,我们便可以在接下来调用erode或dilate函数时,第三个参数填保存了getStructuringElement返回值的Mat类型变量。对应于我们上面的示例,就是填element变量。

  • 第四个参数,Point类型的anchor,锚的位置,其有默认值(-1,-1),表示锚位于中心。
  • 第五个参数,int类型的iterations,迭代使用erode()函数的次数,默认值为1。
  • 第六个参数,int类型的borderType,用于推断图像外部像素的某种边界模式。注意它有默认值BORDER_DEFAULT。
  • 第七个参数,const Scalar&类型的borderValue,当边界为常数时的边界值,有默认值morphologyDefaultBorderValue(),一般我们不用去管他。需要用到它时,可以看官方文档中的createMorphologyFilter()函数得到更详细的解释。

使用dilate函数,一般我们只需要填前面的三个参数,后面的四个参数都有默认值。而且往往结合getStructuringElement一起使用。

调用范例

   	//载入原图 
   	Mat image = imread("1.jpg");
//获取自定义核
   	Mat element = getStructuringElement(MORPH_RECT, Size(15, 15));
   	Mat out;
   	//进行膨胀操作
   	dilate(image, out, element);

示例程序

//-----------------------------------【头文件包含部分】---------------------------------------
//     描述:包含程序所依赖的头文件
//----------------------------------------------------------------------------------------------
#include 
#include
#include
#include 
 
//-----------------------------------【命名空间声明部分】---------------------------------------
//     描述:包含程序所使用的命名空间
//----------------------------------------------------------------------------------------------- 
using namespace std;
using namespace cv;
 
//-----------------------------------【main( )函数】--------------------------------------------
//     描述:控制台应用程序的入口函数,我们的程序从这里开始
//-----------------------------------------------------------------------------------------------
int main(  )
{
 
       //载入原图 
       Mat image = imread("D://lili/Desktop/jpg/opencv/1.png");
 
       //创建窗口 
       namedWindow("【原图】膨胀操作");
       namedWindow("【效果图】膨胀操作");
 
       //显示原图
       imshow("【原图】膨胀操作", image);
 
	//获取自定义核
       Mat element = getStructuringElement(MORPH_RECT, Size(3, 3));
       Mat out;
	//进行膨胀操作
       dilate(image,out, element);
 
       //显示效果图
       imshow("【效果图】膨胀操作", out);
 
       waitKey(0);
 
       return 0;
}

运行效果
《Opencv3编程入门》学习笔记—第六章_第17张图片

2、形态学腐蚀——erode函数

erode函数,使用像素邻域内的局部极小运算符来腐蚀一张图片,从src输入,由dst输出。支持就地(in-place)操作。

void erode(
	InputArray src,
	OutputArray dst,
	InputArray kernel,
	Point anchor=Point(-1,-1),
	int iterations=1,
	int borderType=BORDER_CONSTANT,
	const Scalar& borderValue=morphologyDefaultBorderValue()
 );

参数详解

  • 第一个参数,InputArray类型的src,输入图像,即源图像,填Mat类的对象即可。图像通道的数量可以是任意的,但图像深度应为CV_8U,CV_16U,CV_16S,CV_32F或 CV_64F其中之一。
  • 第二个参数,OutputArray类型的dst,即目标图像,需要和源图片有一样的尺寸和类型。
  • 第三个参数,InputArray类型的kernel,腐蚀操作的内核。若为NULL时,表示的是使用参考点位于中心3x3的核。我们一般使用函数 getStructuringElement配合这个参数的使用。getStructuringElement函数会返回指定形状和尺寸的结构元素(内核矩阵)。
  • 第四个参数,Point类型的anchor,锚的位置,其有默认值(-1,-1),表示锚位于单位(element)的中心,我们一般不用管它。
  • 第五个参数,int类型的iterations,迭代使用erode()函数的次数,默认值为1。
  • 第六个参数,int类型的borderType,用于推断图像外部像素的某种边界模式。注意它有默认值BORDER_DEFAULT。
  • 第七个参数,const Scalar&类型的borderValue,当边界为常数时的边界值,有默认值morphologyDefaultBorderValue(),一般我们不用去管他。需要用到它时,可以看官方文档中的createMorphologyFilter()函数得到更详细的解释。
    同样的,使用erode函数,一般我们只需要填前面的三个参数,后面的四个参数都有默认值。而且往往结合getStructuringElement一起使用。

调用范例

   	//载入原图 
   	Mat image = imread("1.jpg");
//获取自定义核
   	Mat element = getStructuringElement(MORPH_RECT, Size(15, 15));
   	Mat out;
   	//进行腐蚀操作
   	erode(image,out, element);

示例代码

//-----------------------------------【头文件包含部分】---------------------------------------
//     描述:包含程序所依赖的头文件
//----------------------------------------------------------------------------------------------
#include 
#include
#include
#include 
 
//-----------------------------------【命名空间声明部分】---------------------------------------
//     描述:包含程序所使用的命名空间
//----------------------------------------------------------------------------------------------- 
using namespace std;
using namespace cv;
 
//-----------------------------------【main( )函数】--------------------------------------------
//     描述:控制台应用程序的入口函数,我们的程序从这里开始
//-----------------------------------------------------------------------------------------------
int main(  )
{
       //载入原图 
       Mat image = imread("D://lili/Desktop/jpg/opencv/1.png");
 
        //创建窗口 
       namedWindow("【原图】腐蚀操作");
       namedWindow("【效果图】腐蚀操作");
 
       //显示原图
       imshow("【原图】腐蚀操作", image);
 
        
//获取自定义核
       Mat element = getStructuringElement(MORPH_RECT, Size(10, 10));
       Mat out;
 
//进行腐蚀操作
       erode(image,out, element);
 
       //显示效果图
       imshow("【效果图】腐蚀操作", out);
 
       waitKey(0);
 
       return 0;
}

运行效果
《Opencv3编程入门》学习笔记—第六章_第18张图片

(六)综合示例:腐蚀与膨胀

示例代码

//-----------------------------------【程序说明】----------------------------------------------
//            程序名称::《【OpenCV入门教程之十】形态学图像处理(一):膨胀与腐蚀  》 博文配套源码
//------------------------------------------------------------------------------------------------
 
//-----------------------------------【头文件包含部分】---------------------------------------
//            描述:包含程序所依赖的头文件
//----------------------------------------------------------------------------------------------
#include 
#include 
#include
#include 
 
//-----------------------------------【命名空间声明部分】---------------------------------------
//            描述:包含程序所使用的命名空间
//-----------------------------------------------------------------------------------------------
using namespace std;
using namespace cv;
 
 
//-----------------------------------【全局变量声明部分】--------------------------------------
//            描述:全局变量声明
//-----------------------------------------------------------------------------------------------
Mat g_srcImage, g_dstImage;//原始图和效果图
int g_nTrackbarNumer = 0;//0表示腐蚀erode, 1表示膨胀dilate
int g_nStructElementSize = 3; //结构元素(内核矩阵)的尺寸
 
 
//-----------------------------------【全局函数声明部分】--------------------------------------
//            描述:全局函数声明
//-----------------------------------------------------------------------------------------------
void Process();//膨胀和腐蚀的处理函数
void on_TrackbarNumChange(int, void *);//回调函数
void on_ElementSizeChange(int, void *);//回调函数
 
 
//-----------------------------------【main( )函数】--------------------------------------------
//            描述:控制台应用程序的入口函数,我们的程序从这里开始
//-----------------------------------------------------------------------------------------------
int main( )
{
       //改变console字体颜色
       system("color5E"); 
 
       //载入原图
       g_srcImage= imread("D://lili/Desktop/jpg/opencv/1.jpg");
       if(!g_srcImage.data ) { printf("读取srcImage错误!\n"); return false; }
      
       //显示原始图
       namedWindow("【原始图】");
       imshow("【原始图】", g_srcImage);
      
       //进行初次腐蚀操作并显示效果图
       namedWindow("【效果图】");
 
       //创建轨迹条
       createTrackbar("腐蚀/膨胀", "【效果图】", &g_nTrackbarNumer, 1, on_TrackbarNumChange);
       createTrackbar("内核尺寸", "【效果图】",&g_nStructElementSize, 21, on_ElementSizeChange);

	    //获取自定义核
       Mat element = getStructuringElement(MORPH_RECT, Size(2*g_nStructElementSize+1,2*g_nStructElementSize+1),Point( g_nStructElementSize, g_nStructElementSize ));
       erode(g_srcImage,g_dstImage, element);
       imshow("【效果图】", g_dstImage);
 
       //输出一些帮助信息
       cout<<endl<<"\t嗯。运行成功,请调整滚动条观察图像效果~\n\n"
              <<"\t按下“q”键时,程序退出~!\n";
 
       //轮询获取按键信息,若下q键,程序退出
       while(char(waitKey(1))!= 'q') {}
 
       return 0;
}
 
//-----------------------------【Process( )函数】------------------------------------
//            描述:进行自定义的腐蚀和膨胀操作
//-----------------------------------------------------------------------------------------
void Process()
{
       //获取自定义核
       Mat element = getStructuringElement(MORPH_RECT, Size(2*g_nStructElementSize+1,2*g_nStructElementSize+1),Point( g_nStructElementSize, g_nStructElementSize ));
 
       //进行腐蚀或膨胀操作
       if(g_nTrackbarNumer == 0) {   
              erode(g_srcImage,g_dstImage, element);
       }
       else{
              dilate(g_srcImage,g_dstImage, element);
       }
 
       //显示效果图
       imshow("【效果图】", g_dstImage);
}
 
 
//-----------------------------【on_TrackbarNumChange( )函数】------------------------------------
//            描述:腐蚀和膨胀之间切换开关的回调函数
//-----------------------------------------------------------------------------------------------------
void on_TrackbarNumChange(int, void *)
{
       //腐蚀和膨胀之间效果已经切换,回调函数体内需调用一次Process函数,使改变后的效果立即生效并显示出来
       Process();
}
 
 
//-----------------------------【on_ElementSizeChange( )函数】-------------------------------------
//            描述:腐蚀和膨胀操作内核改变时的回调函数
//-----------------------------------------------------------------------------------------------------
void on_ElementSizeChange(int, void *)
{
       //内核尺寸已改变,回调函数体内需调用一次Process函数,使改变后的效果立即生效并显示出来
       Process();
}

运行效果
腐蚀效果图:

膨胀效果图:

四、形态学滤波(2):开运算、闭运算、形态学梯度、顶帽、黑帽

morphologyEx函数:它利用基本的膨胀和腐蚀技术,来执行更加高级的形态学变换,如开闭运算、形态学梯度、“顶帽”、黑帽“”等。

  • 简单理解开运算和闭运算
    开运算:先腐蚀后膨胀,能够消除图像区域外的小白点(噪声)。
    闭运算:先膨胀后腐蚀,能够消除图像区域内的小黑点(噪声)。
    《Opencv3编程入门》学习笔记—第六章_第19张图片
  • 简单理解形态学梯度:结果看起来像对象轮廓
    在这里插入图片描述
  • 简单理解顶帽和黑帽
    顶帽:原图-开运算结果
    黑帽:闭运算结果-原图
    《Opencv3编程入门》学习笔记—第六章_第20张图片

毛笔图像原图
《Opencv3编程入门》学习笔记—第六章_第21张图片

(一)开运算:消除图像区域外的小白点

开运算(Opening Operation),其实就是先腐蚀后膨胀的过程。其数学表达式如下:
在这里插入图片描述
开运算可以用来消除小物体、在纤细点处分离物体、平滑较大物体的边界的同时并不明显改变其面积。效果图是这样的:
《Opencv3编程入门》学习笔记—第六章_第22张图片
实际效果图:
《Opencv3编程入门》学习笔记—第六章_第23张图片

(二)闭运算:消除图像区域内的小黑点

先膨胀后腐蚀的过程称为闭运算(Closing Operation),其数学表达式如下:
在这里插入图片描述
闭运算能够排除小型黑洞(黑色区域)。效果图如下所示:
《Opencv3编程入门》学习笔记—第六章_第24张图片

实际效果图:
《Opencv3编程入门》学习笔记—第六章_第25张图片

(三)形态学梯度:结果看起来像对象轮廓

形态学梯度(Morphological Gradient)为膨胀图与腐蚀图之差,数学表达式如下:
在这里插入图片描述
对二值图像进行这一操作可以将团块(blob)的边缘突出出来。我们可以用形态学梯度来保留物体的边缘轮廓,如下所示:
《Opencv3编程入门》学习笔记—第六章_第26张图片
实际素材效果图:
《Opencv3编程入门》学习笔记—第六章_第27张图片

(四)顶帽:得到噪声图像

顶帽运算(Top Hat)又常常被译为”礼帽“运算。为原图像与“开运算“的结果图之差,数学表达式如下:
在这里插入图片描述
因为开运算带来的结果是放大了裂缝或者局部低亮度的区域,因此,从原图中减去开运算后的图,得到的效果图突出了比原图轮廓周围的区域更明亮的区域,且这一操作和选择的核的大小相关。

顶帽运算往往用来分离比邻近点亮一些的斑块。当一幅图像具有大幅的背景的时候,而微小物品比较有规律的情况下,可以使用顶帽运算进行背景提取。如下所示:
《Opencv3编程入门》学习笔记—第六章_第28张图片
素材效果图:

(五)黑帽:得到图像内部的小孔,或前景色中的小黑点

黑帽(Black Hat)运算为”闭运算“的结果图与原图像之差。数学表达式为:
在这里插入图片描述
黑帽运算后的效果图突出了比原图轮廓周围的区域更暗的区域,且这一操作和选择的核的大小相关。
所以,黑帽运算用来分离比邻近点暗一些的斑块。非常完美的轮廓效果图:
《Opencv3编程入门》学习笔记—第六章_第29张图片
实际素材效果图:

(六)形态学滤波OpenCV源码分析溯源

morphologyEx函数的源代码
它利用基本的膨胀和腐蚀技术,来执行更加高级的形态学变换,如开闭运算,形态学梯度,“顶帽”、“黑帽”等等。

//-----------------------------------【erode()函数中文注释版源代码】----------------------------  
//   说明:以下代码为来自于计算机开源视觉库OpenCV的官方源代码  
//   OpenCV源代码版本:2.4.8  
//   源码路径:…\opencv\sources\modules\imgproc\src\morph.cpp  
//   源文件中如下代码的起始行数:1369行  
//--------------------------------------------------------------------------------------------------------   
void cv::morphologyEx( InputArray _src,OutputArray _dst, int op,
                       InputArray kernel, Pointanchor, int iterations,
                       int borderType, constScalar& borderValue )
{
//拷贝Mat数据到临时变量
   Mat src = _src.getMat(), temp;
   _dst.create(src.size(), src.type());
   Mat dst = _dst.getMat();
 
//一个大switch,根据不同的标识符取不同的操作
   switch( op )
    {
   case MORPH_ERODE:
       erode( src, dst, kernel, anchor, iterations, borderType, borderValue );
       break;
   case MORPH_DILATE:
       dilate( src, dst, kernel, anchor, iterations, borderType, borderValue );
       break;
   case MORPH_OPEN:
       erode( src, dst, kernel, anchor, iterations, borderType, borderValue );
       dilate( dst, dst, kernel, anchor, iterations, borderType, borderValue );
       break;
   case CV_MOP_CLOSE:
       dilate( src, dst, kernel, anchor, iterations, borderType, borderValue );
       erode( dst, dst, kernel, anchor, iterations, borderType, borderValue );
       break;
   case CV_MOP_GRADIENT:
       erode( src, temp, kernel, anchor, iterations, borderType, borderValue );
       dilate( src, dst, kernel, anchor, iterations, borderType, borderValue );
       dst -= temp;
       break;
   case CV_MOP_TOPHAT:
       if( src.data != dst.data )
           temp = dst;
       erode( src, temp, kernel, anchor, iterations, borderType, borderValue );
        dilate( temp, temp, kernel, anchor,iterations, borderType, borderValue );
       dst = src - temp;
       break;
   case CV_MOP_BLACKHAT:
       if( src.data != dst.data )
           temp = dst;
       dilate( src, temp, kernel, anchor, iterations, borderType, borderValue);
       erode( temp, temp, kernel, anchor, iterations, borderType, borderValue);
       dst = temp - src;
       break;
   default:
       CV_Error( CV_StsBadArg, "unknown morphological operation" );
    }
}

其实morphologyEx函数其实就是内部一个大switch而已。根据不同的标识符取不同的操作。比如开运算MORPH_OPEN,就是先腐蚀后膨胀,即依次调用erode和dilate函数。

(七)核心API函数:morphologyEx()

morphologyEx函数利用基本的膨胀和腐蚀技术,来执行更加高级形态学变换,如开闭运算,形态学梯度,“顶帽”、“黑帽”等等。

void morphologyEx(
InputArray src,
OutputArray dst,
int op,
InputArraykernel,
Pointanchor=Point(-1,-1),
intiterations=1,
intborderType=BORDER_CONSTANT,
constScalar& borderValue=morphologyDefaultBorderValue() );

1、第一个参数,InputArray类型的src,输入图像,即源图像,填Mat类的对象即可。图像位深应该为以下五种之一:CV_8U, CV_16U,CV_16S, CV_32F 或CV_64F。
2、第二个参数,OutputArray类型的dst,即目标图像,函数的输出参数,需要和源图片有一样的尺寸和类型。
3、第三个参数,int类型的op,表示形态学运算的类型,可以是如下之一的标识符:

  • MORPH_OPEN – 开运算(Opening operation)
  • MORPH_CLOSE – 闭运算(Closing operation)
  • MORPH_GRADIENT -形态学梯度(Morphological gradient)
  • MORPH_TOPHAT - “顶帽”(“Top hat”)
  • MORPH_BLACKHAT - “黑帽”(“Black hat“)
  • MORPH_ERODE - “腐蚀”(“erode”)
  • MORPH_ERODE - “腐蚀”(“erode”)

4、第四个参数,InputArray类型的kernel,形态学运算的内核。若为NULL时,表示的是使用参考点位于中心3x3的核。我们一般使用函数 getStructuringElement配合这个参数的使用。getStructuringElement函数会返回指定形状和尺寸的结构元素(内核矩阵)。

  • getStructuringElement函数的第一个参数表示内核的形状,我们可以选择如下三种形状之一:
    (1)矩形: MORPH_RECT
    (2)交叉形: MORPH_CROSS
    (3)椭圆形: MORPH_ELLIPSE
    而getStructuringElement函数的第二和第三个参数分别是内核的尺寸以及锚点的位置。

getStructuringElement函数相关的调用示例代码如下:

int g_nStructElementSize = 3; //结构元素(内核矩阵)的尺寸
 
//获取自定义核
Mat element =getStructuringElement(MORPH_RECT,
       Size(2*g_nStructElementSize+1,2*g_nStructElementSize+1),
       Point(g_nStructElementSize, g_nStructElementSize ));

5、第五个参数,Point类型的anchor,锚的位置,其有默认值(-1,-1),表示锚位于中心。
6、第六个参数,int类型的iterations,迭代使用函数的次数,默认值为1。
7、第七个参数,int类型的borderType,用于推断图像外部像素的某种边界模式。注意它有默认值BORDER_ CONSTANT。
8、第八个参数,const Scalar&类型的borderValue,当边界为常数时的边界值,有默认值morphologyDefaultBorderValue(),一般我们不用去管他。

(八)各形态学操作使用范例一览

1、开运算示例程序

//-----------------------------------【头文件包含部分】---------------------------------------
//            描述:包含程序所依赖的头文件
//----------------------------------------------------------------------------------------------
#include 
#include
#include
 
//-----------------------------------【命名空间声明部分】---------------------------------------
//            描述:包含程序所使用的命名空间
//-----------------------------------------------------------------------------------------------
using namespace cv;
//-----------------------------------【main( )函数】--------------------------------------------
//            描述:控制台应用程序的入口函数,我们的程序从这里开始
//-----------------------------------------------------------------------------------------------
int main( )
{
       //载入原始图  
       Mat image = imread("D://lili/Desktop/jpg/opencv/5.jpg");  //工程目录下应该有一张名为1.jpg的素材图
       //创建窗口  
       namedWindow("【原始图】开运算"); 
       namedWindow("【效果图】开运算"); 
       //显示原始图 
       imshow("【原始图】开运算", image); 
       //定义核
       Mat element = getStructuringElement(MORPH_RECT, Size(15, 15)); 
       //进行形态学操作
       morphologyEx(image,image, MORPH_OPEN, element);
       //显示效果图 
       imshow("【效果图】开运算", image); 
 
       waitKey(0); 
 
       return 0; 
}

《Opencv3编程入门》学习笔记—第六章_第30张图片

2、闭运算示例程序

//-----------------------------------【头文件包含部分】---------------------------------------
//            描述:包含程序所依赖的头文件
//----------------------------------------------------------------------------------------------
#include 
#include
#include
 
//-----------------------------------【命名空间声明部分】---------------------------------------
//            描述:包含程序所使用的命名空间
//-----------------------------------------------------------------------------------------------
using namespace cv;
//-----------------------------------【main( )函数】--------------------------------------------
//            描述:控制台应用程序的入口函数,我们的程序从这里开始
//-----------------------------------------------------------------------------------------------
int main( )
{
       //载入原始图  
       Mat image = imread("D://lili/Desktop/jpg/opencv/5.jpg");  //工程目录下应该有一张名为1.jpg的素材图
       //创建窗口  
       namedWindow("【原始图】闭运算"); 
       namedWindow("【效果图】闭运算"); 
       //显示原始图 
       imshow("【原始图】闭运算", image); 
       //定义核
       Mat element = getStructuringElement(MORPH_RECT, Size(15, 15)); 
       //进行形态学操作
       morphologyEx(image,image, MORPH_CLOSE, element);
       //显示效果图 
       imshow("【效果图】闭运算", image); 
 
       waitKey(0); 
 
       return 0; 
}

《Opencv3编程入门》学习笔记—第六章_第31张图片

3、形态学梯度示例程序

//-----------------------------------【头文件包含部分】---------------------------------------
//            描述:包含程序所依赖的头文件
//----------------------------------------------------------------------------------------------
#include 
#include
#include
 
//-----------------------------------【命名空间声明部分】---------------------------------------
//            描述:包含程序所使用的命名空间
//-----------------------------------------------------------------------------------------------
using namespace cv;
//-----------------------------------【main( )函数】--------------------------------------------
//            描述:控制台应用程序的入口函数,我们的程序从这里开始
//-----------------------------------------------------------------------------------------------
int main( )
{
       //载入原始图  
       Mat image = imread("D://lili/Desktop/jpg/opencv/5.jpg");  //工程目录下应该有一张名为1.jpg的素材图
       //创建窗口  
       namedWindow("【原始图】形态学梯度"); 
       namedWindow("【效果图】形态学梯度"); 
       //显示原始图 
       imshow("【原始图】形态学梯度", image); 
       //定义核
       Mat element = getStructuringElement(MORPH_RECT, Size(15, 15)); 
       //进行形态学操作
       morphologyEx(image,image, MORPH_GRADIENT, element);
       //显示效果图 
       imshow("【效果图】形态学梯度", image); 
 
       waitKey(0); 
 
       return 0; 
}

《Opencv3编程入门》学习笔记—第六章_第32张图片

4、顶帽运算示例程序

//-----------------------------------【头文件包含部分】---------------------------------------
//            描述:包含程序所依赖的头文件
//----------------------------------------------------------------------------------------------
#include 
#include
#include
 
//-----------------------------------【命名空间声明部分】---------------------------------------
//            描述:包含程序所使用的命名空间
//-----------------------------------------------------------------------------------------------
using namespace cv;
//-----------------------------------【main( )函数】--------------------------------------------
//            描述:控制台应用程序的入口函数,我们的程序从这里开始
//-----------------------------------------------------------------------------------------------
int main( )
{
       //载入原始图  
       Mat image = imread("1.jpg");  //工程目录下应该有一张名为1.jpg的素材图
       //创建窗口  
       namedWindow("【原始图】顶帽运算"); 
       namedWindow("【效果图】顶帽运算"); 
       //显示原始图 
       imshow("【原始图】顶帽运算", image); 
       //定义核
       Mat element = getStructuringElement(MORPH_RECT, Size(15, 15)); 
       //进行形态学操作
       morphologyEx(image,image, MORPH_TOPHAT, element);
       //显示效果图 
       imshow("【效果图】顶帽运算", image); 
 
       waitKey(0); 
 
       return 0; 
}

5、黑帽运算示例程序

//-----------------------------------【头文件包含部分】---------------------------------------
//            描述:包含程序所依赖的头文件
//----------------------------------------------------------------------------------------------
#include 
#include 
#include
 
//-----------------------------------【命名空间声明部分】---------------------------------------
//            描述:包含程序所使用的命名空间
//-----------------------------------------------------------------------------------------------
using namespace cv;
//-----------------------------------【main( )函数】--------------------------------------------
//            描述:控制台应用程序的入口函数,我们的程序从这里开始
//-----------------------------------------------------------------------------------------------
int main( )
{
       //载入原始图  
       Mat image = imread("D://lili/Desktop/jpg/opencv/1.png");  //工程目录下应该有一张名为1.jpg的素材图
       //创建窗口  
       namedWindow("【原始图】黑帽运算"); 
       namedWindow("【效果图】黑帽运算"); 
       //显示原始图 
       imshow("【原始图】黑帽运算", image); 
       //定义核
       Mat element = getStructuringElement(MORPH_RECT, Size(15, 15)); 
       //进行形态学操作
       morphologyEx(image,image, MORPH_BLACKHAT, element);
       //显示效果图 
       imshow("【效果图】黑帽运算", image); 
 
       waitKey(0); 
 
       return 0; 
}

6、腐蚀

Mat element = getStructuringElement(MORPH_RECT, Size(15, 15)); 
morphologyEx(image,image, MORPH_ERODE, element);

7、膨胀

//定义核
Mat element = getStructuringElement(MORPH_RECT, Size(15, 15)); 
 //进行形态学操作
morphologyEx(image,image, MORPH_DILATE, element);

(九)综合示例:形态学滤波

//-----------------------------------【程序说明】----------------------------------------------
//		程序名称::《【OpenCV入门教程之十一】形态学图像处理(一):膨胀与腐蚀 
//		开发所用IDE版本:Visual Studio
//   	开发所用OpenCV版本:	2.4.8
//----------------------------------------------------------------------------------------------
 
//-----------------------------------【头文件包含部分】---------------------------------------
//		描述:包含程序所依赖的头文件
//---------------------------------------------------------------------------------------------- 
#include 
#include 
#include 
 
//-----------------------------------【命名空间声明部分】--------------------------------------
//		描述:包含程序所使用的命名空间
//----------------------------------------------------------------------------------------------- 
using namespace std;
using namespace cv;
 
 
//-----------------------------------【全局变量声明部分】--------------------------------------
//		描述:全局变量声明
//-----------------------------------------------------------------------------------------------
Mat g_srcImage, g_dstImage;//原始图和效果图
int g_nElementShape = MORPH_RECT;//元素结构的形状
 
//变量接收的TrackBar位置参数
int g_nMaxIterationNum = 10;
int g_nOpenCloseNum = 0;
int g_nErodeDilateNum = 0;
int g_nTopBlackHatNum = 0;
 
 
 
//-----------------------------------【全局函数声明部分】--------------------------------------
//		描述:全局函数声明
//-----------------------------------------------------------------------------------------------
static void on_OpenClose(int, void*);//回调函数
static void on_ErodeDilate(int, void*);//回调函数
static void on_TopBlackHat(int, void*);//回调函数
static void ShowHelpText();//帮助文字显示
 
 
//-----------------------------------【main( )函数】--------------------------------------------
//		描述:控制台应用程序的入口函数,我们的程序从这里开始
//-----------------------------------------------------------------------------------------------
int main( )
{
	//改变console字体颜色
	system("color 2F");  
 
	ShowHelpText();
 
	//载入原图
	g_srcImage = imread("D://lili/Desktop/jpg/opencv/5.jpg");//工程目录下需要有一张名为1.jpg的素材图
	if( !g_srcImage.data ) { printf("读取srcImage错误! \n"); return false; }
 
	//显示原始图
	namedWindow("【原始图】");
	imshow("【原始图】", g_srcImage);
 
	//创建三个窗口
	namedWindow("【开运算/闭运算】",1);
	namedWindow("【腐蚀/膨胀】",1);
	namedWindow("【顶帽/黑帽】",1);
 
	//参数赋值
	g_nOpenCloseNum=9;
	g_nErodeDilateNum=9;
	g_nTopBlackHatNum=2;
 
	//分别为三个窗口创建滚动条
	createTrackbar("迭代值", "【开运算/闭运算】",&g_nOpenCloseNum,g_nMaxIterationNum*2+1,on_OpenClose);
	createTrackbar("迭代值", "【腐蚀/膨胀】",&g_nErodeDilateNum,g_nMaxIterationNum*2+1,on_ErodeDilate);
	createTrackbar("迭代值", "【顶帽/黑帽】",&g_nTopBlackHatNum,g_nMaxIterationNum*2+1,on_TopBlackHat);
 
	//轮询获取按键信息
	while(1)
	{
		int c;
 
		//执行回调函数
		on_OpenClose(g_nOpenCloseNum, 0);
		on_ErodeDilate(g_nErodeDilateNum, 0);   
		on_TopBlackHat(g_nTopBlackHatNum,0);
 
		//获取按键
		c = waitKey(0);
 
		//按下键盘按键Q或者ESC,程序退出
		if( (char)c == 'q'||(char)c == 27 )
			break;
		//按下键盘按键1,使用椭圆(Elliptic)结构元素结构元素MORPH_ELLIPSE
		if( (char)c == 49 )//键盘按键1的ASII码为49
			g_nElementShape = MORPH_ELLIPSE;
		//按下键盘按键2,使用矩形(Rectangle)结构元素MORPH_RECT
		else if( (char)c == 50 )//键盘按键2的ASII码为50
			g_nElementShape = MORPH_RECT;
		//按下键盘按键3,使用十字形(Cross-shaped)结构元素MORPH_CROSS
		else if( (char)c == 51 )//键盘按键3的ASII码为51
			g_nElementShape = MORPH_CROSS;
		//按下键盘按键space,在矩形、椭圆、十字形结构元素中循环
		else if( (char)c == ' ' )
			g_nElementShape = (g_nElementShape + 1) % 3;
	}
 
	return 0;
}
 
 
//-----------------------------------【on_OpenClose( )函数】----------------------------------
//		描述:【开运算/闭运算】窗口的回调函数
//-----------------------------------------------------------------------------------------------
static void on_OpenClose(int, void*)
{
	//偏移量的定义
	int offset = g_nOpenCloseNum - g_nMaxIterationNum;//偏移量
	int Absolute_offset = offset > 0 ? offset : -offset;//偏移量绝对值
	//自定义核
	Mat element = getStructuringElement(g_nElementShape, Size(Absolute_offset*2+1, Absolute_offset*2+1), Point(Absolute_offset, Absolute_offset) );
	//进行操作
	if( offset < 0 )
		morphologyEx(g_srcImage, g_dstImage, CV_MOP_OPEN, element);
	else
		morphologyEx(g_srcImage, g_dstImage, CV_MOP_CLOSE, element);
	//显示图像
	imshow("【开运算/闭运算】",g_dstImage);
}
 
 
//-----------------------------------【on_ErodeDilate( )函数】----------------------------------
//		描述:【腐蚀/膨胀】窗口的回调函数
//-----------------------------------------------------------------------------------------------
static void on_ErodeDilate(int, void*)
{
	//偏移量的定义
	int offset = g_nErodeDilateNum - g_nMaxIterationNum;	//偏移量
	int Absolute_offset = offset > 0 ? offset : -offset;//偏移量绝对值
	//自定义核
	Mat element = getStructuringElement(g_nElementShape, Size(Absolute_offset*2+1, Absolute_offset*2+1), Point(Absolute_offset, Absolute_offset) );
	//进行操作
	if( offset < 0 )
		erode(g_srcImage, g_dstImage, element);
	else
		dilate(g_srcImage, g_dstImage, element);
	//显示图像
	imshow("【腐蚀/膨胀】",g_dstImage);
}
 
 
//-----------------------------------【on_TopBlackHat( )函数】--------------------------------
//		描述:【顶帽运算/黑帽运算】窗口的回调函数
//----------------------------------------------------------------------------------------------
static void on_TopBlackHat(int, void*)
{
	//偏移量的定义
	int offset = g_nTopBlackHatNum - g_nMaxIterationNum;//偏移量
	int Absolute_offset = offset > 0 ? offset : -offset;//偏移量绝对值
	//自定义核
	Mat element = getStructuringElement(g_nElementShape, Size(Absolute_offset*2+1, Absolute_offset*2+1), Point(Absolute_offset, Absolute_offset) );
	//进行操作
	if( offset < 0 )
		morphologyEx(g_srcImage, g_dstImage, MORPH_TOPHAT , element);
	else
		morphologyEx(g_srcImage, g_dstImage, MORPH_BLACKHAT, element);
	//显示图像
	imshow("【顶帽/黑帽】",g_dstImage);
}
 
//-----------------------------------【ShowHelpText( )函数】----------------------------------
//		描述:输出一些帮助信息
//----------------------------------------------------------------------------------------------
static void ShowHelpText()
{
//输出一些帮助信息
	printf("\n\n\n\t请调整滚动条观察图像效果\n\n");
	printf( "\n\n\t按键操作说明: \n\n"
		"\t\t键盘按键【ESC】或者【Q】- 退出程序\n"
		"\t\t键盘按键【1】- 使用椭圆(Elliptic)结构元素\n"
		"\t\t键盘按键【2】- 使用矩形(Rectangle )结构元素\n"
		"\t\t键盘按键【3】- 使用十字型(Cross-shaped)结构元素\n"
		"\t\t键盘按键【空格SPACE】- 在矩形、椭圆、十字形结构元素中循环\n"
		);
}

五、漫水填充

(一)漫水填充的定义

漫水填充法是一种用特定的颜色填充联通区域,通过设置可连通像素的上下限以及连通方式来达到不同的填充效果的方法。漫水填充经常被用来标记或分离图像的一部分以便对其进行进一步处理或分析,也可以用来从输入图像获取掩码区域,掩码会加速处理过程,或只处理掩码指定的像素点,操作的结果总是某个连续的区域。

(二)漫水填充的基本思想

  • 所谓漫水填充,简单来说,就是自动选中了和种子点相连的区域,接着将该区域替换成指定的颜色,这是个非常有用的功能,经常用来标记或者分离图像的一部分进行处理或分析。漫水填充也可以用来从输入图像获取掩码区域,掩码会加速处理过程,或者只处理掩码指定的像素点。

  • 以此填充算法为基础,类似photoshop的魔术棒选择工具就很容易实现了。漫水填充(FloodFill)是查找和种子点联通的颜色相同的点,魔术棒选择工具则是查找和种子点联通的颜色相近的点,将和初始种子像素颜色相近的点压进栈作为新种子

(三)实现漫水填充算法:floodFill函数

在OpenCV中,漫水填充算法由floodFill函数实现,其作用是用我们指定的颜色从种子点开始填充一个连接域。连通性由像素值的接近程度来衡量。OpenCV2.X有两个C++重写版本的floodFill。

第一个版本的floodFill:

 int floodFill(InputOutputArray image, Point seedPoint, Scalar newVal, Rect* rect=0, Scalar loDiff=Scalar(), Scalar upDiff=Scalar(), int flags=4 )

第二个版本的floodFill:

int floodFill(InputOutputArray image, InputOutputArray mask, Point seedPoint,Scalar newVal, Rect* rect=0, Scalar loDiff=Scalar(), Scalar upDiff=Scalar(), int flags=4 )

参数详解。除了第二个参数外,其他的参数都是共用的。

1、第一个参数,InputOutputArray类型的image, 输入/输出1通道或3通道,8位或浮点图像,具体参数由之后的参数具体指明。
2、第二个参数, InputOutputArray类型的mask,这是第二个版本的floodFill独享的参数,表示操作掩模,。它应该为单通道、8位、长和宽上都比输入图像 image 大两个像素点的图像。第二个版本的floodFill需要使用以及更新掩膜,所以这个mask参数我们一定要将其准备好并填在此处。需要注意的是,漫水填充不会填充掩膜mask的非零像素区域。例如,一个边缘检测算子的输出可以用来作为掩膜,以防止填充到边缘。同样的,也可以在多次的函数调用中使用同一个掩膜,以保证填充的区域不会重叠。另外需要注意的是,掩膜mask会比需填充的图像大,所以 mask 中与输入图像(x,y)像素点相对应的点的坐标为(x+1,y+1)。
3、第三个参数,Point类型的seedPoint,漫水填充算法的起始点。
4、第四个参数,Scalar类型的newVal,像素点被染色的值,即在重绘区域像素的新值。
5、第五个参数,Rect*类型的rect,有默认值0,一个可选的参数,用于设置floodFill函数将要重绘区域的最小边界矩形区域。
6、第六个参数,Scalar类型的loDiff,有默认值Scalar( ),表示当前观察像素值与其部件邻域像素值或者待加入该部件的种子像素之间的亮度或颜色之负差(lower brightness/color difference)的最大值。
7、第七个参数,Scalar类型的upDiff,有默认值Scalar( ),表示当前观察像素值与其部件邻域像素值或者待加入该部件的种子像素之间的亮度或颜色之正差(lower brightness/color difference)的最大值。
8、第八个参数,int类型的flags,操作标志符,此参数包含三个部分。

  • 低八位(第0~7位)用于控制算法的连通性,可取4 (4为缺省值) 或者 8。
    如果设为4,表示填充算法只考虑当前像素水平方向和垂直方向的相邻点;
    如果设为 8,除上述相邻点外,还会包含对角线方向的相邻点。

  • 高八位部分(16~23位)可以为0 或者如下两种选项标识符的组合:
    FLOODFILL_FIXED_RANGE - 如果设置为这个标识符的话,就会考虑当前像素与种子像素之间的差,否则就考虑当前像素与其相邻像素的差。也就是说,这个范围是浮动的。
    FLOODFILL_MASK_ONLY - 如果设置为这个标识符的话,函数不会去填充改变原始图像 (也就是忽略第三个参数newVal), 而是去填充掩模图像(mask)。这个标识符只对第二个版本的floodFill有用,因第一个版本里面压根就没有mask参数。

  • 中间八位部分,上面关于高八位FLOODFILL_MASK_ONLY标识符中已经说的很明显,需要输入符合要求的掩码。Floodfill的flags参数的中间八位的值就是用于指定填充掩码图像的值的。但如果flags中间八位的值为0,则掩码会用1来填充。

而所有flags可以用or操作符连接起来,即“|”。例如,如果想用8邻域填充,并填充固定像素值范围,填充掩码而不是填充源图像,以及设填充值为38,那么输入的参数是这样:

flags=8 | FLOODFILL_MASK_ONLY | FLOODFILL_FIXED_RANGE |38<<8

示例代码

//-----------------------------------【头文件包含部分】---------------------------------------  
//      描述:包含程序所依赖的头文件  
//----------------------------------------------------------------------------------------------   
#include   
#include   
 
//-----------------------------------【命名空间声明部分】---------------------------------------  
//      描述:包含程序所使用的命名空间  
//-----------------------------------------------------------------------------------------------   
using namespace cv;  
//-----------------------------------【main( )函数】--------------------------------------------  
//      描述:控制台应用程序的入口函数,我们的程序从这里开始  
//----------------------------------------------------------------------------------------------- 
 
int main( )
{    
	Mat src = imread("D://lili/Desktop/jpg/opencv/2.png"); 
	imshow("【原始图】",src);
	Rect ccomp;
	floodFill(src, Point(50,300), Scalar(155, 255,55), &ccomp, Scalar(20, 20, 20),Scalar(20, 20, 20));
	imshow("【效果图】",src);
	waitKey(0);
	return 0;    
}  

运行效果

(四)综合示例:漫水填充

【待学习】代码有些难以理解,有待进一步学习
示例代码

//-----------------------------------【程序说明】----------------------------------------------
//		程序名称::《【OpenCV入门教程之十五】水漫金山:OpenCV漫水填充算法(Floodfill)》 博文配套源码 
//----------------------------------------------------------------------------------------------
 
//-----------------------------------【头文件包含部分】---------------------------------------  
//      描述:包含程序所依赖的头文件  
//----------------------------------------------------------------------------------------------
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include 
 
//-----------------------------------【命名空间声明部分】---------------------------------------  
//      描述:包含程序所使用的命名空间  
//-----------------------------------------------------------------------------------------------   
using namespace cv;
using namespace std;
 
 
//-----------------------------------【全局变量声明部分】--------------------------------------  
//      描述:全局变量声明  
//-----------------------------------------------------------------------------------------------  
Mat g_srcImage, g_dstImage, g_grayImage, g_maskImage;//定义原始图、目标图、灰度图、掩模图
int g_nFillMode = 1;//漫水填充的模式
int g_nLowDifference = 20, g_nUpDifference = 20;//负差最大值、正差最大值
int g_nConnectivity = 4;//表示floodFill函数标识符低八位的连通值
int g_bIsColor = true;//是否为彩色图的标识符布尔值
bool g_bUseMask = false;//是否显示掩膜窗口的布尔值
int g_nNewMaskVal = 255;//新的重新绘制的像素值
 
 
//-----------------------------------【ShowHelpText( )函数】----------------------------------  
//      描述:输出一些帮助信息  
//----------------------------------------------------------------------------------------------  
static void ShowHelpText()  
{  
	//输出一些帮助信息  
	printf("\n\n\n\t欢迎来到漫水填充示例程序~\n\n");  
	printf( "\n\n\t按键操作说明: \n\n"  
		"\t\t鼠标点击图中区域- 进行漫水填充操作\n"  
		"\t\t键盘按键【ESC】- 退出程序\n"  
		"\t\t键盘按键【1】-  切换彩色图/灰度图模式\n"  
		"\t\t键盘按键【2】- 显示/隐藏掩膜窗口\n"  
		"\t\t键盘按键【3】- 恢复原始图像\n"  
		"\t\t键盘按键【4】- 使用空范围的漫水填充\n"  
		"\t\t键盘按键【5】- 使用渐变、固定范围的漫水填充\n"  
		"\t\t键盘按键【6】- 使用渐变、浮动范围的漫水填充\n"  
		"\t\t键盘按键【7】- 操作标志符的低八位使用4位的连接模式\n"  
		"\t\t键盘按键【8】- 操作标志符的低八位使用8位的连接模式\n"  
		);  
}  
 
 
//-----------------------------------【onMouse( )函数】--------------------------------------  
//      描述:鼠标消息onMouse回调函数
//---------------------------------------------------------------------------------------------
static void onMouse( int event, int x, int y, int, void* )
{
	// 若鼠标左键没有按下,便返回
	if( event != CV_EVENT_LBUTTONDOWN )
		return;
 
	//-------------------【<1>调用floodFill函数之前的参数准备部分】---------------
	Point seed = Point(x,y);
	int LowDifference = g_nFillMode == 0 ? 0 : g_nLowDifference;//空范围的漫水填充,此值设为0,否则设为全局的g_nLowDifference
	int UpDifference = g_nFillMode == 0 ? 0 : g_nUpDifference;//空范围的漫水填充,此值设为0,否则设为全局的g_nUpDifference
	int flags = g_nConnectivity + (g_nNewMaskVal << 8) +
		(g_nFillMode == 1 ? CV_FLOODFILL_FIXED_RANGE : 0);//标识符的0~7位为g_nConnectivity,8~15位为g_nNewMaskVal左移8位的值,16~23位为CV_FLOODFILL_FIXED_RANGE或者0。
 
	//随机生成bgr值
	int b = (unsigned)theRNG() & 255;//随机返回一个0~255之间的值
	int g = (unsigned)theRNG() & 255;//随机返回一个0~255之间的值
	int r = (unsigned)theRNG() & 255;//随机返回一个0~255之间的值
	Rect ccomp;//定义重绘区域的最小边界矩形区域
 
	Scalar newVal = g_bIsColor ? Scalar(b, g, r) : Scalar(r*0.299 + g*0.587 + b*0.114);//在重绘区域像素的新值,若是彩色图模式,取Scalar(b, g, r);若是灰度图模式,取Scalar(r*0.299 + g*0.587 + b*0.114)
 
	Mat dst = g_bIsColor ? g_dstImage : g_grayImage;//目标图的赋值
	int area;
 
	//--------------------【<2>正式调用floodFill函数】-----------------------------
	if( g_bUseMask )
	{
		threshold(g_maskImage, g_maskImage, 1, 128, CV_THRESH_BINARY);
		area = floodFill(dst, g_maskImage, seed, newVal, &ccomp, Scalar(LowDifference, LowDifference, LowDifference),
			Scalar(UpDifference, UpDifference, UpDifference), flags);
		imshow( "mask", g_maskImage );
	}
	else
	{
		area = floodFill(dst, seed, newVal, &ccomp, Scalar(LowDifference, LowDifference, LowDifference),
			Scalar(UpDifference, UpDifference, UpDifference), flags);
	}
 
	imshow("效果图", dst);
	cout << area << " 个像素被重绘\n";
}
 
 
//-----------------------------------【main( )函数】--------------------------------------------  
//      描述:控制台应用程序的入口函数,我们的程序从这里开始  
//-----------------------------------------------------------------------------------------------  
int main( int argc, char** argv )
{
	//改变console字体颜色  
	system("color 2F");    
 
	//载入原图
	g_srcImage = imread("D://lili/Desktop/jpg/opencv/5.jpg", 1);
 
    if( !g_srcImage.data ) { printf("读取图片image0错误! \n"); return false; }  
 
	//显示帮助文字
	ShowHelpText();
 
	g_srcImage.copyTo(g_dstImage);//拷贝源图到目标图
	cvtColor(g_srcImage, g_grayImage, COLOR_BGR2GRAY);//转换三通道的image0到灰度图
	g_maskImage.create(g_srcImage.rows+2, g_srcImage.cols+2, CV_8UC1);//利用image0的尺寸来初始化掩膜mask
 
	namedWindow( "效果图",CV_WINDOW_AUTOSIZE );
 
	//创建Trackbar
	createTrackbar( "负差最大值", "效果图", &g_nLowDifference, 255, 0 );
	createTrackbar( "正差最大值" ,"效果图", &g_nUpDifference, 255, 0 );
 
	//鼠标回调函数
	setMouseCallback( "效果图", onMouse, 0 );
 
	//循环轮询按键
	while(1)
	{
		//先显示效果图
		imshow("效果图", g_bIsColor ? g_dstImage : g_grayImage);
 
		//获取键盘按键
		int c = waitKey(0);
		//判断ESC是否按下,若按下便退出
		if( (c & 255) == 27 )
		{
			cout << "程序退出...........\n";
			break;
		}
 
		//根据按键的不同,进行各种操作
		switch( (char)c )
		{
		//如果键盘“1”被按下,效果图在在灰度图,彩色图之间互换
		case '1':
			if( g_bIsColor )//若原来为彩色,转为灰度图,并且将掩膜mask所有元素设置为0
			{
				cout << "键盘“1”被按下,切换彩色/灰度模式,当前操作为将【彩色模式】切换为【灰度模式】\n";
				cvtColor(g_srcImage, g_grayImage, COLOR_BGR2GRAY);
				g_maskImage = Scalar::all(0);	//将mask所有元素设置为0
				g_bIsColor = false;	//将标识符置为false,表示当前图像不为彩色,而是灰度
			}
			else//若原来为灰度图,便将原来的彩图image0再次拷贝给image,并且将掩膜mask所有元素设置为0
			{
				cout << "键盘“1”被按下,切换彩色/灰度模式,当前操作为将【彩色模式】切换为【灰度模式】\n";
				g_srcImage.copyTo(g_dstImage);
				g_maskImage = Scalar::all(0);
				g_bIsColor = true;//将标识符置为true,表示当前图像模式为彩色
			}
			break;
		//如果键盘按键“2”被按下,显示/隐藏掩膜窗口
		case '2':
			if( g_bUseMask )
			{
				destroyWindow( "mask" );
				g_bUseMask = false;
			}
			else
			{
				namedWindow( "mask", 0 );
				g_maskImage = Scalar::all(0);
				imshow("mask", g_maskImage);
				g_bUseMask = true;
			}
			break;
		//如果键盘按键“3”被按下,恢复原始图像
		case '3':
			cout << "按键“3”被按下,恢复原始图像\n";
			g_srcImage.copyTo(g_dstImage);
			cvtColor(g_dstImage, g_grayImage, COLOR_BGR2GRAY);
			g_maskImage = Scalar::all(0);
			break;
		//如果键盘按键“4”被按下,使用空范围的漫水填充
		case '4':
			cout << "按键“4”被按下,使用空范围的漫水填充\n";
			g_nFillMode = 0;
			break;
		//如果键盘按键“5”被按下,使用渐变、固定范围的漫水填充
		case '5':
			cout << "按键“5”被按下,使用渐变、固定范围的漫水填充\n";
			g_nFillMode = 1;
			break;
		//如果键盘按键“6”被按下,使用渐变、浮动范围的漫水填充
		case '6':
			cout << "按键“6”被按下,使用渐变、浮动范围的漫水填充\n";
			g_nFillMode = 2;
			break;
		//如果键盘按键“7”被按下,操作标志符的低八位使用4位的连接模式
		case '7':
			cout << "按键“7”被按下,操作标志符的低八位使用4位的连接模式\n";
			g_nConnectivity = 4;
			break;
		//如果键盘按键“8”被按下,操作标志符的低八位使用8位的连接模式
		case '8':
			cout << "按键“8”被按下,操作标志符的低八位使用8位的连接模式\n";
			g_nConnectivity = 8;
			break;
		}
	}
 
	return 0;
}

运行效果
原始图
《Opencv3编程入门》学习笔记—第六章_第33张图片
《Opencv3编程入门》学习笔记—第六章_第34张图片

六、图像金字塔与图片尺寸缩放

学习如何使用OpenCV函数pyrUp和pyrDown对图像进行向上和向下采样,以及了解专门用于缩放图像尺寸的resize函数的用法。

(一)引言

我们经常会将某种尺寸的图像转换为其他尺寸的图像,如果放大或者缩小图片的尺寸,笼统来说的话,可以使用OpenCV为我们提供的如下两种方式:

  • resize函数。这是最直接的方式。
  • pyrUp( )、pyrDown( )函数。即图像金字塔相关的两个函数,对图像进行向上采样,向下采样的操作。

pyrUp、pyrDown其实和专门用作放大缩小图像尺寸的resize在功能上差不多。另外,pyrUp、pyrDown在OpenCV的imgproc模块中的Image Filtering子模块里。而resize在imgproc 模块的Geometric Image Transformations子模块里。

(二)关于图像金字塔

  • 图像金字塔是图像中多尺度表达的一种,最主要用于图像的分割,是一种以多分辨率来解释图像的有效但概念简单的结构。

  • 图像金字塔最初用于机器视觉和图像压缩,一幅图像的金字塔是一系列以金字塔形状排列的分辨率逐步降低,且来源于同一张原始图的图像集合。其通过梯次向下采样获得,直到达到某个终止条件才停止采样。

  • 金字塔的底部是待处理图像的高分辨率表示,而顶部是低分辨率的近似。

  • 我们将一层一层的图像比喻成金字塔,层级越高,则图像越小,分辨率越低。
    《Opencv3编程入门》学习笔记—第六章_第35张图片

  • 一般情况下有两种类型的图像金字塔:
    (1)高斯金字塔(Gaussianpyramid): 用来向下采样,主要的图像金字塔
    (2)拉普拉斯金字塔(Laplacianpyramid): 用来从金字塔低层图像重建上层未采样图像,在数字图像处理中也即是预测残差,可以对图像进行最大程度的还原,配合高斯金字塔一起使用。

  • 两者的简要区别:高斯金字塔用来向下降采样图像,而拉普拉斯金字塔则用来从金字塔底层图像中向上采样重建一个图像。
    要从金字塔第i层生成第i+1层(我们表示第i+1层为G_i+1),我们先要用高斯核对G_1进行卷积,然后删除所有偶数行和偶数列。当然的是,新得到图像面积会变为源图像的四分之一。按上述过程对输入图像G_0执行操作就可产生出整个金字塔。

  • 当图像向金字塔的上层移动时,尺寸和分辨率就降低。OpenCV中,从金字塔中上一级图像生成下一级图像的可以用PryDown。而通过PryUp将现有的图像在每个维度都放大两遍。

  • 图像金字塔中的向上和向下采样分别通过OpenCV函数 pyrUp 和 pyrDown 实现。
    概括起来就是:
    对图像向上采样:pyrUp函数
    对图像向下采样:pyrDown函数
    这里的向下与向上采样,是对图像的尺寸而言的(和金字塔的方向相反),向上就是图像尺寸加倍,向下就是图像尺寸减半。而如果我们按上图中演示的金字塔方向来理解,金字塔向上图像其实在缩小,这样刚好是反过来了。

(三)高斯金字塔

【辅助记忆:向上取样就是放大,向下取样就是缩小】
高斯金字塔是通过高斯平滑和亚采样获得一些列下采样图像,也就是说第K层高斯金字塔通过平滑、亚采样就可以获得K+1层高斯图像,高斯金字塔包含了一系列低通滤波器,其截至频率从上一层到下一层是以因子2逐渐增加,所以高斯金字塔可以跨越很大的频率范围。金字塔的图像如下:
《Opencv3编程入门》学习笔记—第六章_第36张图片
另外,每一层都按从下到上的次序编号, 层级 G_i+1 (表示为 G_i+1尺寸小于第i层G_i)。

1、对图像的向下取样

为了获取层级为 G_i+1 的金字塔图像,我们采用如下方法:
1、对图像G_i进行高斯内核卷积
2、将所有偶数行和列去除
得到的图像即为G_i+1的图像,显而易见,结果图像只有原图的四分之一。通过对输入图像G_i(原始图像)不停迭代以上步骤就会得到整个金字塔。同时我们也可以看到,向下取样会逐渐丢失图像的信息。
即缩小图像。

2、对图像的向上取样

如果想放大图像,则需要通过向上取样操作得到,具体做法如下:
1、将图像在每个方向扩大为原来的两倍,新增的行和列以0填充
2、使用先前同样的内核(乘以4)与放大后的图像卷积,获得 “新增像素”的近似值
得到的图像即为放大后的图像,但是与原来的图像相比会发觉比较模糊,因为在缩放的过程中已经丢失了一些信息,如果想在缩小和放大整个过程中减少信息的丢失,这些数据形成了拉普拉斯金字塔。

(四)拉普拉斯金字塔

下式是拉普拉斯金字塔第i层的数学定义:
《Opencv3编程入门》学习笔记—第六章_第37张图片
式中的在这里插入图片描述表示第i层的图像。而UP()操作是将源图像中位置为(x,y)的像素映射到目标图像的(2x+1,2y+1)位置,即在进行向上取样。符号在这里插入图片描述表示卷积,在这里插入图片描述为5x5的高斯内核。pryUp,就是在进行上面这个式子的运算。因此,我们可以直接用OpenCV进行拉普拉斯运算:
《Opencv3编程入门》学习笔记—第六章_第38张图片
也就是说,拉普拉斯金字塔是通过源图像减去先缩小后再放大的图像的一系列图像构成的。
整个拉普拉斯金字塔运算过程可以通过下图来概括:
《Opencv3编程入门》学习笔记—第六章_第39张图片
所以,我们可以将拉普拉斯金字塔理解为高斯金字塔的逆形式。
**【拓展应用】**关于图像金字塔非常重要的一个应用就是实现图像分割。先要建立一个图像金字塔,然后在G_i和G_i+1的像素直接依照对应的关系,建立起”父与子“关系。而快速初始分割可以先在金字塔高层的低分辨率图像上完成,然后逐层对分割加以优化。

(五)尺寸调整:resize()函数

resize( )为OpenCV中专职调整图像大小的函数。

此函数将源图像精确地转换为指定尺寸的目标图像。如果源图像中设置了ROI(Region Of Interest ,感兴趣区域),那么resize( )函数会对源图像的ROI区域进行调整图像尺寸的操作,来输出到目标图像中。若目标图像中已经设置ROI区域,不难理解resize( )将会对源图像进行尺寸调整并填充到目标图像的ROI中。

void resize(InputArray src,OutputArray dst, Size dsize, double fx=0, double fy=0, int interpolation=INTER_LINEAR )

1、第一个参数,InputArray类型的src,输入图像,即源图像,填Mat类的对象即可。
2、第二个参数,OutputArray类型的dst,输出图像,当其非零时,有着dsize(第三个参数)的尺寸,或者由src.size()计算出来。
3、第三个参数,Size类型的dsize,输出图像的大小;如果它等于零,由下式进行计算:
在这里插入图片描述
其中,dsize,fx,fy都不能为0。

第四个参数,double类型的fx,沿水平轴的缩放系数,有默认值0,且当其等于0时,由下式进行计算:
在这里插入图片描述
第五个参数,double类型的fy,沿垂直轴的缩放系数,有默认值0,且当其等于0时,由下式进行计算:
在这里插入图片描述
第六个参数,int类型的interpolation,用于指定插值方式,默认为INTER_LINEAR(线性插值)。

可选的插值方式如下:

  • INTER_NEAREST - 最近邻插值
  • INTER_LINEAR - 线性插值(默认值)
  • INTER_AREA - 区域插值(利用像素区域关系的重采样插值)
  • INTER_CUBIC –三次样条插值(超过4×4像素邻域内的双三次插值)
  • INTER_LANCZOS4 -Lanczos插值(超过8×8像素邻域的Lanczos插值)
  • 若要缩小图像,一般情况下最好用CV_INTER_AREA来插值,
    而若要放大图像,一般情况下最好用CV_INTER_CUBIC(效率不高,慢,不推荐使用)或CV_INTER_LINEAR(效率较高,速度较快,推荐使用)。

【关于插值的理解】
原图
《Opencv3编程入门》学习笔记—第六章_第40张图片
当进行6次图像缩小接着6次图像放大操作后,两种不同的插值方式得到的效果图:
《Opencv3编程入门》学习笔记—第六章_第41张图片《Opencv3编程入门》学习笔记—第六章_第42张图片

效果很明显,第一张全是一个个的像素,非常影响美观。另外一张却有雾化的朦胧美感,所以插值方式的选择,对经过多次放大缩小的图片最终得到的效果是有很大影响的。

【范例一】

Mat dst=Mat::zeros(512 ,512, CV_8UC3 );//新建一张512x512尺寸的图片
Mat src=imread(“D://lili/Desktop/jpg/opencv/1.jpg”);
//显式指定dsize=dst.size(),那么fx和fy会其计算出来,不用额外指定。
resize(src, dst, dst.size());

【范例二】

```cpp
Mat dst;
Mat src=imread(“D://lili/Desktop/jpg/opencv/1.jpg”)
 //指定fx和fy,让函数计算出目标图像的大小。
resize(src, dst, Size(), 0.5, 0.5);

示例代码

//-----------------------------------【头文件包含部分】---------------------------------------
//		描述:包含程序所依赖的头文件
//---------------------------------------------------------------------------------------------- 
#include 
#include 
 
//-----------------------------------【命名空间声明部分】---------------------------------------
//		描述:包含程序所使用的命名空间
//----------------------------------------------------------------------------------------------- 
using namespace cv;
//-----------------------------------【main( )函数】--------------------------------------------
//		描述:控制台应用程序的入口函数,我们的程序从这里开始
//-----------------------------------------------------------------------------------------------
int main( )
{
	//载入原始图   
	Mat srcImage = imread("D://lili/Desktop/jpg/opencv/5.jpg");  //工程目录下应该有一张名为1.jpg的素材图
	Mat tmpImage,dstImage1,dstImage2;//临时变量和目标图的定义
	tmpImage=srcImage;//将原始图赋给临时变量
 
	//显示原始图  
	imshow("【原始图】", srcImage);  
 
	//进行尺寸调整操作
	resize(tmpImage,dstImage1,Size( tmpImage.cols/2, tmpImage.rows/2 ),(0,0),(0,0),3);
	resize(tmpImage,dstImage2,Size( tmpImage.cols*2, tmpImage.rows*2 ),(0,0),(0,0),3);
 
	//显示效果图  
	imshow("【效果图】之一", dstImage1);  
	imshow("【效果图】之二", dstImage2);  
 
	waitKey(0);  
	return 0;  
}

运行效果

(六)图像金字塔相关API函数

与resize()方法不同的是,图像的分辨率会有损失

1、pyrUp()函数剖析

pyrUp( )函数的作用是向上采样并模糊一张图像,说白了就是放大一张图片。

void pyrUp(InputArray src, OutputArraydst, const Size& dstsize=Size(), int borderType=BORDER_DEFAULT )

1、第一个参数,InputArray类型的src,输入图像,即源图像,填Mat类的对象即可。
2、第二个参数,OutputArray类型的dst,输出图像,和源图片有一样的尺寸和类型。
3、第三个参数,const Size&类型的dstsize,输出图像的大小;有默认值Size(),即默认情况下,由Size(src.cols2,src.rows2)来进行计算,且一直需要满足下列条件:
在这里插入图片描述

4、第四个参数,int类型的borderType,边界模式,一般不用管。

pyrUp函数执行高斯金字塔的采样操作,其实它也可以用于拉普拉斯金字塔的。
首先,它通过插入可为零的行与列,对源图像进行向上取样操作,然后将结果与pyrDown()乘以4的内核做卷积。

示例代码

//-----------------------------------【头文件包含部分】---------------------------------------
//		描述:包含程序所依赖的头文件
//---------------------------------------------------------------------------------------------- 
#include 
#include 
 
//-----------------------------------【命名空间声明部分】---------------------------------------
//		描述:包含程序所使用的命名空间
//----------------------------------------------------------------------------------------------- 
using namespace cv;
//-----------------------------------【main( )函数】--------------------------------------------
//		描述:控制台应用程序的入口函数,我们的程序从这里开始
//-----------------------------------------------------------------------------------------------
int main( )
{
	//载入原始图   
	Mat srcImage = imread("D://lili/Desktop/jpg/opencv/5.jpg");  //工程目录下应该有一张名为1.jpg的素材图
	Mat tmpImage,dstImage;//临时变量和目标图的定义
	tmpImage=srcImage;//将原始图赋给临时变量
 
	//显示原始图  
	imshow("【原始图】", srcImage);  
	//进行向上取样操作
	pyrUp( tmpImage, dstImage, Size( tmpImage.cols*2, tmpImage.rows*2 ) );
	//显示效果图  
	imshow("【效果图】", dstImage);  
 
	waitKey(0);  
 
	return 0;  
}

运行效果

2、pyrDown()函数剖析

pyrDown( )函数的作用是向下采样并模糊一张图片,说白了就是缩小一张图片。

void pyrDown(InputArray src,OutputArray dst, const Size& dstsize=Size(), int borderType=BORDER_DEFAULT)

1、第一个参数,InputArray类型的src,输入图像,即源图像,填Mat类的对象即可。
2、第二个参数,OutputArray类型的dst,输出图像,和源图片有一样的尺寸和类型。
3、第三个参数,const Size&类型的dstsize,输出图像的大小;有默认值Size(),即默认情况下,由Size Size((src.cols+1)/2, (src.rows+1)/2)来进行计算,且一直需要满足下列条件:
《Opencv3编程入门》学习笔记—第六章_第43张图片
该pyrDown函数执行了高斯金字塔建造的向下采样的步骤。首先,它将源图像与如下内核做卷积运算:
《Opencv3编程入门》学习笔记—第六章_第44张图片

接着,它便通过对图像的偶数行和列做插值来进行向下采样操作。

示例代码

//-----------------------------------【头文件包含部分】---------------------------------------
//		描述:包含程序所依赖的头文件
//---------------------------------------------------------------------------------------------- 
#include 
#include 
 
//-----------------------------------【命名空间声明部分】---------------------------------------
//		描述:包含程序所使用的命名空间
//----------------------------------------------------------------------------------------------- 
using namespace cv;
//-----------------------------------【main( )函数】--------------------------------------------
//		描述:控制台应用程序的入口函数,我们的程序从这里开始
//-----------------------------------------------------------------------------------------------
int main( )
{
	//载入原始图   
	Mat srcImage = imread("D://lili/Desktop/jpg/opencv/5.jpg");  //工程目录下应该有一张名为1.jpg的素材图
	Mat tmpImage,dstImage;//临时变量和目标图的定义
	tmpImage=srcImage;//将原始图赋给临时变量
 
	//显示原始图  
	imshow("【原始图】", srcImage);  
	//进行向下取样操作
	pyrDown( tmpImage, dstImage, Size( tmpImage.cols/2, tmpImage.rows/2 ) );
	//显示效果图  
	imshow("【效果图】", dstImage);  
 
	waitKey(0);  
 
	return 0;  
}

运行效果
《Opencv3编程入门》学习笔记—第六章_第45张图片

(七)综合示例:图像金字塔与图片尺寸缩放

示例代码

//-----------------------------------【程序说明】----------------------------------------------
//		程序名称::《 【OpenCV入门教程之十三】OpenCV图像金字塔:高斯金字塔、拉普拉斯金字塔与图片尺寸缩放》 博文配套源码 
//		开发所用IDE版本:Visual Studio 2010
//   	        开发所用OpenCV版本:	2.4.9
//		2014年5月18日 Create by 浅墨
//----------------------------------------------------------------------------------------------
 
//-----------------------------------【头文件包含部分】---------------------------------------
//		描述:包含程序所依赖的头文件
//---------------------------------------------------------------------------------------------- 
#include 
#include 
#include 
 
//-----------------------------------【宏定义部分】--------------------------------------------
//	描述:定义一些辅助宏
//------------------------------------------------------------------------------------------------
#define WINDOW_NAME "【程序窗口】"		//为窗口标题定义的宏
 
 
//-----------------------------------【命名空间声明部分】--------------------------------------
//		描述:包含程序所使用的命名空间
//----------------------------------------------------------------------------------------------- 
using namespace std;
using namespace cv;
 
 
//-----------------------------------【全局变量声明部分】--------------------------------------
//		描述:全局变量声明
//-----------------------------------------------------------------------------------------------
Mat g_srcImage, g_dstImage, g_tmpImage;
 
 
//-----------------------------------【全局函数声明部分】--------------------------------------
//		描述:全局函数声明
//-----------------------------------------------------------------------------------------------
static void ShowHelpText();
 
 
//-----------------------------------【ShowHelpText( )函数】----------------------------------
//		描述:输出一些帮助信息
//----------------------------------------------------------------------------------------------
static void ShowHelpText()
{
	//输出一些帮助信息
	printf("\n\n\n\t欢迎来到OpenCV图像金字塔和resize示例程序~\n\n");
	printf( "\n\n\t按键操作说明: \n\n"
		"\t\t键盘按键【ESC】或者【Q】- 退出程序\n"
		"\t\t键盘按键【1】或者【W】- 进行基于【resize】函数的图片放大\n"
		"\t\t键盘按键【2】或者【S】- 进行基于【resize】函数的图片缩小\n"
		"\t\t键盘按键【3】或者【A】- 进行基于【pyrUp】函数的图片放大\n"
		"\t\t键盘按键【4】或者【D】- 进行基于【pyrDown】函数的图片缩小\n"
		"\n\n\t\t\t\t\t\t\t\t by浅墨\n\n\n"
		);
}
 
//-----------------------------------【main( )函数】--------------------------------------------
//		描述:控制台应用程序的入口函数,我们的程序从这里开始
//-----------------------------------------------------------------------------------------------
int main( )
{
	//改变console字体颜色
	system("color 2F");  
 
	//显示帮助文字
	ShowHelpText();
 
	//载入原图
	g_srcImage = imread("D://lili/Desktop/jpg/opencv/5.jpg");//工程目录下需要有一张名为1.jpg的测试图像,且其尺寸需被2的N次方整除,N为可以缩放的次数
	if( !g_srcImage.data ) { printf("读取srcImage错误! \n"); return false; }
 
	// 创建显示窗口
	namedWindow( WINDOW_NAME, CV_WINDOW_AUTOSIZE );
	imshow(WINDOW_NAME, g_srcImage);
 
	//参数赋值
	g_tmpImage = g_srcImage;
	g_dstImage = g_tmpImage;
 
	 int key =0;
 
	//轮询获取按键信息
	while(1)
	{
		key=waitKey(9) ;//读取键值到key变量中
 
		//根据key变量的值,进行不同的操作
		switch(key)
		{
		 //======================【程序退出相关键值处理】=======================  
		case 27://按键ESC
			return 0;
			break; 
 
		case 'q'://按键Q
			return 0;
			break; 
 
		 //======================【图片放大相关键值处理】=======================  
		case 'a'://按键A按下,调用pyrUp函数
			pyrUp( g_tmpImage, g_dstImage, Size( g_tmpImage.cols*2, g_tmpImage.rows*2 ) );
			printf( ">检测到按键【A】被按下,开始进行基于【pyrUp】函数的图片放大:图片尺寸×2 \n" );		
			break; 
 
		case 'w'://按键W按下,调用resize函数
			resize(g_tmpImage,g_dstImage,Size( g_tmpImage.cols*2, g_tmpImage.rows*2 ));
			printf( ">检测到按键【W】被按下,开始进行基于【resize】函数的图片放大:图片尺寸×2 \n" );		
			break; 
 
		case '1'://按键1按下,调用resize函数
			resize(g_tmpImage,g_dstImage,Size( g_tmpImage.cols*2, g_tmpImage.rows*2 ));
			printf( ">检测到按键【1】被按下,开始进行基于【resize】函数的图片放大:图片尺寸×2 \n" );
			break; 
 
		case '3': //按键3按下,调用pyrUp函数
			pyrUp( g_tmpImage, g_dstImage, Size( g_tmpImage.cols*2, g_tmpImage.rows*2 ));
			printf( ">检测到按键【3】被按下,开始进行基于【pyrUp】函数的图片放大:图片尺寸×2 \n" );
			break; 
		//======================【图片缩小相关键值处理】=======================  
		case 'd': //按键D按下,调用pyrDown函数
			pyrDown( g_tmpImage, g_dstImage, Size( g_tmpImage.cols/2, g_tmpImage.rows/2 ));
			printf( ">检测到按键【D】被按下,开始进行基于【pyrDown】函数的图片缩小:图片尺寸/2\n" );
			break; 
 
		case  's' : //按键S按下,调用resize函数
			resize(g_tmpImage,g_dstImage,Size( g_tmpImage.cols/2, g_tmpImage.rows/2 ));
			printf( ">检测到按键【S】被按下,开始进行基于【resize】函数的图片缩小:图片尺寸/2\n" );
			break; 
 
		case '2'://按键2按下,调用resize函数
			resize(g_tmpImage,g_dstImage,Size( g_tmpImage.cols/2, g_tmpImage.rows/2 ),(0,0),(0,0),2);
			printf( ">检测到按键【2】被按下,开始进行基于【resize】函数的图片缩小:图片尺寸/2\n" );
			break; 
 
		case '4': //按键4按下,调用pyrDown函数
			pyrDown( g_tmpImage, g_dstImage, Size( g_tmpImage.cols/2, g_tmpImage.rows/2 ) );
			printf( ">检测到按键【4】被按下,开始进行基于【pyrDown】函数的图片缩小:图片尺寸/2\n" );
			break; 
		}
		
		//经过操作后,显示变化后的图
		imshow( WINDOW_NAME, g_dstImage );
 
		//将g_dstImage赋给g_tmpImage,方便下一次循环
		g_tmpImage = g_dstImage;
	}
 
	return 0;
}

运行效果

七、阈值化

  • 最简单的图像切割方法,剔除图像中像素高于或低于一定值的像素,用图像中每一个像素点的灰度值与选取的阈值进行比较,做出判断
  • 函数:Threshold()函数(基本阈值操作)和adaptiveThreshold()函数
  • 基本思想:给定一个数组和一个阈值,根据数组中的每一个元素的值是高于还是低于阈值而进行一些处理

(一)固定阈值操作:Threshold()函数

作用:对单通道数组应用固定阈值操作,对灰度图像进行阈值操作得到二值图像,可去掉噪声(如过滤很小或很大像素值的图像点)

double threshold(InputArray src,OutputArray dst,double thresh,double maxval,int type)
  • 参数1:输入数组,单通道,8或32位浮点类型
  • 参数2:结果数组
  • 参数3:阈值值
  • 参数4:当type取CV_THRESH_BINARY或CV_THRESH_BINARY_INV时阈值类型时的最大值
  • 参数5:阈值类型,Threshold()函数中阈值类型选项对应的操作(标识符依次为0,1,2,3,4):
    《Opencv3编程入门》学习笔记—第六章_第46张图片

(二)自适应阈值操作:adaptiveThreshold()函数

作用:对矩阵采用自适应阈值操作

void adaptiveThreshold(InputArray src, OutputArray dst, double maxValue, int adaptiveMexthod, int thresholdType, int blockSize, double C)
  • 参数1:输入图像
  • 参数2:结果图像
  • 参数3:给像素赋的满足条件的非零值
  • 参数4:指定要使用的自适应阈值算法,可取值:ADAPTIVE_THRESH_MEAN_C或ADAPTIVE_THRESH_GAUSSIAN_C
  • 参数5:阈值类型,可取值:THRESH_BINARY或THRESH_BINARY_INV
    《Opencv3编程入门》学习笔记—第六章_第47张图片
    (6)计算阈值大小的一个像素的邻域尺寸,取值3,5,7等
    (7)减去平均或加权平均值后的常数值,通常为正数

(三)示例程序:基本阈值操作

示例代码

#include
#include
#include
using namespace cv;
#define WINDOW_NAME "【程序窗口】"
//全局变量
Mat g_srcImage, g_dstImage, g_grayImage;
int g_nThresholdValue = 100;
int g_nThresholdType = 3;
//全局函数
static void ShowHelpText();
void on_Threshold(int, void*);
//滑动条操作说明
static void ShowHelpText()
{
	printf("--------------------------------------------------------------------\n");
	printf("欢迎来到OpenCV阈值化示例程序~\n");
	printf("滑动条操作说明:\n");
	printf("\t\t键盘按键[ESC]-退出程序\n");
	printf("\t\t滑动条模式0-二进制阈值\n");
	printf("\t\t滑动条模式1-反二进制阈值\n");
	printf("\t\t滑动条模式2-截断阈值\n");
	printf("\t\t滑动条模式3-反阈值化为0\n");
	printf("\t\t滑动条模式4-阈值化为0\n");
	printf("--------------------------------------------------------------------\n");
}
int main()
{
	//键盘操作说明
	ShowHelpText();

	//[1]载入原图
	g_srcImage = imread("D://lili/Desktop/jpg/opencv/6.jpg");
	if (!g_srcImage.data)
	{
		printf("载入原图失败~!\n");
		return false;
	}
	//[2]留存一份灰度图
	cvtColor(g_srcImage, g_grayImage, COLOR_BGR2GRAY);
	//[3]创建并显示窗口
	namedWindow(WINDOW_NAME, WINDOW_AUTOSIZE);
	imshow(WINDOW_NAME, g_srcImage);
	//[4]创建滑动条控制阈值
	createTrackbar("模式", WINDOW_NAME, &g_nThresholdType, 4, on_Threshold);
	createTrackbar("参数值", WINDOW_NAME, &g_nThresholdValue, 255, on_Threshold);
	//[5]初始化自定义的阈值回调函数
	on_Threshold(0, 0);
	//[6]轮询等待用户按键
	while (1)
	{
		int key = waitKey(9);
		if ((char)key == 27) break;
	}
	return 0;
}
void on_Threshold(int, void*)
{
	//调用阈值函数
	threshold(g_srcImage, g_dstImage, g_nThresholdValue, 255, g_nThresholdType);
	//更新效果图
	imshow(WINDOW_NAME, g_dstImage);
}

运行效果
《Opencv3编程入门》学习笔记—第六章_第48张图片
《Opencv3编程入门》学习笔记—第六章_第49张图片

《Opencv3编程入门》学习笔记—第六章_第50张图片
《Opencv3编程入门》学习笔记—第六章_第51张图片
《Opencv3编程入门》学习笔记—第六章_第52张图片

你可能感兴趣的:(计算机视觉,计算机视觉,opencv,c++,图像处理)