第3章 Opencv图像处理进阶-【第一部分 图像处理C】(imgproc组件、feature2D组件)

来源:互联网 发布:借钱软件 编辑:程序博客网 时间:2024/05/18 01:57

1.5漫水填充

1.5.1漫水填充的定义

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

1.5.2漫水填充法的基本思想

所谓漫水填充,简单来说,就是自动选中了和种子点相连的区域,接着将该区域替换成指定的颜色,这是个非常有用的功能,经常用来标记或者分离图像的一部分进行处理或分析.漫水填充也可以用来从输入图像获取掩码区域,掩码会加速处理过程,或者只处理掩码指定的像素点.
以此填充算法为基础,类似photoshop的魔术棒选择工具就很容易实现了。漫水填充(FloodFill)是查找和种子点联通的颜色相同的点,魔术棒选择工具则是查找和种子点联通的颜色相近的点,将和初始种子像素颜色相近的点压进栈作为新种子
在OpenCV中,漫水填充是填充算法中最通用的方法。且在OpenCV 2.X中,使用C++重写过的FloodFill函数有两个版本。一个不带掩膜mask的版本,和一个带mask的版本。这个掩膜mask,就是用于进一步控制哪些区域将被填充颜色(比如说当对同一图像进行多次填充时)。这两个版本的FloodFill,都必须在图像中选择一个种子点,然后把临近区域所有相似点填充上同样的颜色,不同的是,不一定将所有的邻近像素点都染上同一颜色,漫水填充操作的结果总是某个连续的区域。当邻近像素点位于给定的范围(从loDiff到upDiff)内或在原始seedPoint像素值范围内时,FloodFill函数就会为这个点涂上颜色。

1.5.3 floodFill函数详解

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

C++: int floodFill(InputOutputArray image,                    Point seed,                    Scalar newVal,                    Rect* rect=0,                    Scalar loDiff=Scalar(),                   Scalar upDiff=Scalar(),                    int flags=4 )

第二个版本的floodFill:

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

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

这里写图片描述

图1

看看效果你就知道能干什么了,通过此种方法可以不想要的内容。

1.5.5 floodFill函数在OpenCV中的实现源代码

这个部分贴出OpenCV中本文相关函数的源码实现细节,来给想了解实现细节的小伙伴们参考。
<1>OpenCV2.X中两个版本的floodFill函数源码
第一个,不带mask版本的floodFill:

int cv::floodFill( InputOutputArray _image,Point seedPoint,  Scalar newVal, Rect* rect,  Scalar loDiff, ScalarupDiff, int flags )  {     CvConnectedComp ccomp;     CvMat c_image = _image.getMat();     cvFloodFill(&c_image, seedPoint, newVal, loDiff, upDiff, &ccomp,flags, 0);     if( rect )         *rect = ccomp.rect;     return cvRound(ccomp.area);  }  

第二个,带mask版本的floodFill:

int cv::floodFill( InputOutputArray _image,InputOutputArray _mask,  Point seedPoint, ScalarnewVal, Rect* rect,  Scalar loDiff, ScalarupDiff, int flags )  {     CvConnectedComp ccomp;     CvMat c_image = _image.getMat(), c_mask = _mask.getMat();      cvFloodFill(&c_image, seedPoint, newVal,loDiff, upDiff, &ccomp, flags, c_mask.data.ptr ? &c_mask : 0);     if( rect )         *rect = ccomp.rect;     return cvRound(ccomp.area);  }  

我们依然可以发现,其内部实现是基于OpenCV 1.X旧版的cvFloodFill函数,我们再来看看其旧版函数的源码。
<2>OpenCV2.X中cvFloodFill()函数的实现源码

CV_IMPL void  cvFloodFill( CvArr* arr, CvPointseed_point,              CvScalar newVal, CvScalar lo_diff, CvScalar up_diff,              CvConnectedComp* comp, int flags, CvArr* maskarr )  {     cv::Ptr<CvMat> tempMask;     std::vector<CvFFillSegment> buffer;     if( comp )         memset( comp, 0, sizeof(*comp) );     int i, type, depth, cn, is_simple;     int buffer_size, connectivity = flags & 255;     union {         uchar b[4];         int i[4];         float f[4];         double _[4];      }nv_buf;     nv_buf._[0] = nv_buf._[1] = nv_buf._[2] = nv_buf._[3] = 0;     struct { cv::Vec3b b; cv::Vec3i i; cv::Vec3f f; } ld_buf, ud_buf;     CvMat stub, *img = cvGetMat(arr, &stub);     CvMat maskstub, *mask = (CvMat*)maskarr;     CvSize size;     type = CV_MAT_TYPE( img->type );     depth = CV_MAT_DEPTH(type);     cn = CV_MAT_CN(type);     if( connectivity == 0 )         connectivity = 4;     else if( connectivity != 4 && connectivity != 8 )         CV_Error( CV_StsBadFlag, "Connectivity must be 4, 0(=4) or 8");     is_simple = mask == 0 && (flags & CV_FLOODFILL_MASK_ONLY) ==0;     for( i = 0; i < cn; i++ )      {         if( lo_diff.val[i] < 0 || up_diff.val[i] < 0 )             CV_Error( CV_StsBadArg, "lo_diff and up_diff must benon-negative" );         is_simple &= fabs(lo_diff.val[i]) < DBL_EPSILON &&fabs(up_diff.val[i]) < DBL_EPSILON;      }     size = cvGetMatSize( img );     if( (unsigned)seed_point.x >= (unsigned)size.width ||          (unsigned)seed_point.y >=(unsigned)size.height )         CV_Error( CV_StsOutOfRange, "Seed point is outside of image");     cvScalarToRawData( &newVal, &nv_buf, type, 0 );     buffer_size = MAX( size.width, size.height ) * 2;     buffer.resize( buffer_size );     if( is_simple )      {         int elem_size = CV_ELEM_SIZE(type);         const uchar* seed_ptr = img->data.ptr + img->step*seed_point.y +elem_size*seed_point.x;         for(i = 0; i < elem_size; i++)             if (seed_ptr[i] != nv_buf.b[i])                  break;         if (i != elem_size)         {             if( type == CV_8UC1 )                 icvFloodFill_CnIR(img->data.ptr, img->step, size, seed_point,nv_buf.b[0],                                    comp, flags, &buffer);             else if( type == CV_8UC3 )                 icvFloodFill_CnIR(img->data.ptr, img->step, size, seed_point,cv::Vec3b(nv_buf.b),                                    comp, flags,&buffer);             else if( type == CV_32SC1 )                 icvFloodFill_CnIR(img->data.ptr, img->step, size, seed_point,nv_buf.i[0],                                    comp, flags,&buffer);             else if( type == CV_32FC1 )                 icvFloodFill_CnIR(img->data.ptr, img->step, size, seed_point,nv_buf.f[0],                                    comp, flags,&buffer);             else if( type == CV_32SC3 )                 icvFloodFill_CnIR(img->data.ptr, img->step, size, seed_point,cv::Vec3i(nv_buf.i),                                    comp, flags, &buffer);             else if( type == CV_32FC3 )                 icvFloodFill_CnIR(img->data.ptr, img->step, size, seed_point,cv::Vec3f(nv_buf.f),                                    comp, flags,&buffer);             else                  CV_Error(CV_StsUnsupportedFormat, "" );             return;         }      }     if( !mask )      {         /* created mask will be 8-byte aligned */         tempMask = cvCreateMat( size.height + 2, (size.width + 9) & -8,CV_8UC1 );         mask = tempMask;      }     else      {         mask = cvGetMat( mask, &maskstub );         if( !CV_IS_MASK_ARR( mask ))             CV_Error( CV_StsBadMask, "" );         if( mask->width != size.width + 2 || mask->height != size.height +2 )             CV_Error( CV_StsUnmatchedSizes, "mask must be 2 pixel wider "                                     "and 2pixel taller than filled image" );      }     int width = tempMask ? mask->step : size.width + 2;     uchar* mask_row = mask->data.ptr + mask->step;      memset(mask_row - mask->step, 1, width );     for( i = 1; i <= size.height; i++, mask_row += mask->step )      {         if( tempMask )             memset( mask_row, 0, width );         mask_row[0] = mask_row[size.width+1] = (uchar)1;      }     memset( mask_row, 1, width );     if( depth == CV_8U )         for( i = 0; i < cn; i++ )         {             int t = cvFloor(lo_diff.val[i]);             ld_buf.b[i] = CV_CAST_8U(t);             t = cvFloor(up_diff.val[i]);             ud_buf.b[i] = CV_CAST_8U(t);         }     else if( depth == CV_32S )         for( i = 0; i < cn; i++ )         {             int t = cvFloor(lo_diff.val[i]);             ld_buf.i[i] = t;             t = cvFloor(up_diff.val[i]);             ud_buf.i[i] = t;         }      else if( depth == CV_32F )         for( i = 0; i < cn; i++ )         {             ld_buf.f[i] = (float)lo_diff.val[i];             ud_buf.f[i] = (float)up_diff.val[i];         }     else         CV_Error( CV_StsUnsupportedFormat, "" );     if( type == CV_8UC1 )         icvFloodFillGrad_CnIR<uchar, int, Diff8uC1>(                                img->data.ptr,img->step, mask->data.ptr, mask->step,                                size, seed_point,nv_buf.b[0],                                Diff8uC1(ld_buf.b[0],ud_buf.b[0]),                                comp, flags,&buffer);     else if( type == CV_8UC3 )         icvFloodFillGrad_CnIR<cv::Vec3b, cv::Vec3i, Diff8uC3>(                                img->data.ptr,img->step, mask->data.ptr, mask->step,                                size, seed_point,cv::Vec3b(nv_buf.b),                               Diff8uC3(ld_buf.b, ud_buf.b),                                comp, flags,&buffer);     else if( type == CV_32SC1 )         icvFloodFillGrad_CnIR<int, int, Diff32sC1>(                                img->data.ptr,img->step, mask->data.ptr, mask->step,                                size, seed_point,nv_buf.i[0],                               Diff32sC1(ld_buf.i[0], ud_buf.i[0]),                                comp, flags, &buffer);     else if( type == CV_32SC3 )         icvFloodFillGrad_CnIR<cv::Vec3i, cv::Vec3i, Diff32sC3>(                                img->data.ptr,img->step, mask->data.ptr, mask->step,                                size, seed_point,cv::Vec3i(nv_buf.i),                               Diff32sC3(ld_buf.i, ud_buf.i),                                comp, flags,&buffer);     else if( type == CV_32FC1 )         icvFloodFillGrad_CnIR<float, float, Diff32fC1>(                                img->data.ptr,img->step, mask->data.ptr, mask->step,                                size, seed_point,nv_buf.f[0],                               Diff32fC1(ld_buf.f[0], ud_buf.f[0]),                                comp, flags,&buffer);     else if( type == CV_32FC3 )         icvFloodFillGrad_CnIR<cv::Vec3f, cv::Vec3f, Diff32fC3>(                                img->data.ptr,img->step, mask->data.ptr, mask->step,                                size, seed_point,cv::Vec3f(nv_buf.f),                               Diff32fC3(ld_buf.f,ud_buf.f),                                comp, flags,&buffer);     else         CV_Error(CV_StsUnsupportedFormat, "");  }  

1.5.4关于SetMouseCallback函数

因为下面示例程序中有用到SetMouseCallback函数,我们在这里讲一讲。SetMouseCallback函数为指定的窗口设置鼠标回调函数。

 C++: void setMouseCallback(conststring& winname,                            MouseCallback onMouse,                             void* userdata=0 )  

【参数】
第一个参数,const string&类型的winname,为窗口的名字。
第二个参数,MouseCallback类型的onMouse,指定窗口里每次鼠标时间发生的时候,被调用的函数指针。这个函数的原型应该为voidFoo(int event, int x, int y, int flags, void* param);其中event是 CV_EVENT_*变量之一, x和y是鼠标指针在图像坐标系的坐标(不是窗口坐标系), flags是CV_EVENT_FLAG的组合, param是用户定义的传递到cvSetMouseCallback函数调用的参数。
第三个参数,void*类型的userdata,用户定义的传递到回调函数的参数,有默认值0。

1.5.5漫水填充综合实例

本次的综合示例为OpenCV文档中自带的一个程序。笔者将其做了适当的修改并详细注释,放出来供大家消化理解。此程序着不少的按键功能。而我们拿着鼠标对窗口中的图形一顿狂点,就可以得到类似PhotoShop中魔棒的效果,当然,就这短短的两百来行代码写出来的东西,体验是比不上PS的魔棒工具的。
参见附件【demo2】。

这里写图片描述

图2程序功能

这里写图片描述

图3灰度图

这里写图片描述

图4漫水填充

1.6图像金字塔

1.6.1引言

我们经常会将某种尺寸的图像转换为其他尺寸的图像,如果放大或者缩小图片的尺寸,笼统来说的话,可以使用opencv为我们提供的如下两种方式:
<1>resize函数。这是最直接的方式,
<2>pyrUp( )、pyrDown( )函数。即图像金字塔相关的两个函数,对图像进行向上采样,向下采样的操作。

pyrUp、pyrDown其实和专门用作放大缩小图像尺寸的resize在功能上差不多,披着图像金字塔的皮,说白了还是在对图像进行放大和缩小操作。另外需要指出的是,pyrUp、pyrDown在OpenCV的imgproc模块中的Image Filtering子模块里。而resize在imgproc 模块的Geometric Image Transformations子模块里。
这篇文章中,我们将先介绍图像金字塔的原理,接着介绍resize函数,然后是pyrUp和pyrDown函数,最后是一个综合示例程序。

1.6.2关于图像金字塔

图像金字塔是图像中多尺度表达的一种,最主要用于图像的分割,是一种以多分辨率来解释图像的有效但概念简单的结构。
图像金字塔最初用于机器视觉和图像压缩,一幅图像的金字塔是一系列以金字塔形状排列的分辨率逐步降低,且来源于同一张原始图的图像集合。其通过梯次向下采样获得,直到达到某个终止条件才停止采样。
金字塔的底部是待处理图像的高分辨率表示,而顶部是低分辨率的近似。我们将一层一层的图像比喻成金字塔,层级越高,则图像越小,分辨率越低。

这里写图片描述

图5

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

两者的简要区别:高斯金字塔用来向下降采样图像,而拉普拉斯金字塔则用来从金字塔底层图像中向上采样重建一个图像。
要从金字塔第i层生成第i+1层(我们表示第i+1层为G_i+1),我们先要用高斯核对G_1进行卷积,然后删除所有偶数行和偶数列。当然的是,新得到图像面积会变为源图像的四分之一。按上述过程对输入图像G_0执行操作就可产生出整个金字塔。
当图像向金字塔的上层移动时,尺寸和分辨率就降低。OpenCV中,从金字塔中上一级图像生成下一级图像的可以用PryDown。而通过PryUp将现有的图像在每个维度都放大两遍。
图像金字塔中的向上和向下采样分别通过OpenCV函数 pyrUp 和 pyrDown 实现,概括起来就是:
对图像向上采样:pyrUp函数
对图像向下采样:pyrDown函数
这里的向下与向上采样,是对图像的尺寸而言的(和金字塔的方向相反),向上就是图像尺寸加倍,向下就是图像尺寸减半。而如果我们按上图中演示的金字塔方向来理解,金字塔向上图像其实在缩小,这样刚好是反过来了。
但需要注意的是,PryUp和PryDown不是互逆的,即PryUp不是降采样的逆操作。这种情况下,图像首先在每个维度上扩大为原来的两倍,新增的行(偶数行)以0填充。然后给指定的滤波器进行卷积(实际上是一个在每个维度都扩大为原来两倍的过滤器)去估计“丢失”像素的近似值。PryDown( )是一个会丢失信息的函数。为了恢复原来更高的分辨率的图像,我们要获得由降采样操作丢失的信息,这些数据就和拉普拉斯金字塔有关系了。

1.6.2.1高斯金字塔

高斯金字塔是通过高斯平滑和亚采样获得一些列下采样图像,也就是说第K层高斯金字塔通过平滑、亚采样就可以获得K+1层高斯图像,高斯金字塔包含了一系列低通滤波器,其截至频率从上一层到下一层是以因子2逐渐增加,所以高斯金字塔可以跨越很大的频率范围。金字塔的图像如下:

这里写图片描述

图 6

另外,每一层都按从下到上的次序编号, 层级 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)与放大后的图像卷积,获得 “新增像素”的近似值
得到的图像即为放大后的图像,但是与原来的图像相比会发觉比较模糊,因为在缩放的过程中已经丢失了一些信息,如果想在缩小和放大整个过程中减少信息的丢失,这些数据形成了拉普拉斯金字塔。
那么,我们接下来一起看一看拉普拉斯金字塔的概念吧。

1.6.2.2拉普拉斯金字塔

下式是拉普拉斯金字塔第i层的数学定义:

这里写图片描述

式中G_i的表示第i层的图像。而UP()操作是将源图像中位置为(x,y)的像素映射到目标图像的(2x+1,2y+1)位置,即在进行向上取样。 符号表示卷积,g_(5×5)为5x5的高斯内核。
我们下文将要介绍的pryUp,就是在进行上面这个式子的运算。
因此,我们可以直接用OpenCV进行拉普拉斯运算:
Li=GiPyrUp(Gi+1)

也就是说,拉普拉斯金字塔是通过源图像减去先缩小后再放大的图像的一系列图像构成的。个拉普拉斯金字塔运算过程可以通过下图来概括:
这里写图片描述

图7

所以,我们可以将拉普拉斯金字塔理解为高斯金字塔的逆形式。另外再提一点,关于图像金字塔非常重要的一个应用就是实现图像分割。图像分割的话,先要建立一个图像金字塔,然后在G_i和G_i+1的像素直接依照对应的关系,建立起”父与子“关系。而快速初始分割可以先在金字塔高层的低分辨率图像上完成,然后逐层对分割加以优化。

1.6.3 resize( )函数剖析

resize( )为OpenCV中专职调整图像大小的函数。此函数将源图像精确地转换为指定尺寸的目标图像。如果源图像中设置了ROI(Region Of Interest ,感兴趣区域),那么resize( )函数会对源图像的ROI区域进行调整图像尺寸的操作,来输出到目标图像中。若目标图像中已经设置ROI区域,不难理解resize( )将会对源图像进行尺寸调整并填充到目标图像的ROI中。
很多时候,我们并不用考虑第二个参数dst的初始图像尺寸和类型(即直接定义一个Mat类型,不用对其初始化),因为其尺寸和类型可以由“`
src,dsize,fx和fy这其他的几个参数来确定。

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

【参数】
第一个参数,InputArray类型的src,输入图像,即源图像,填Mat类的对象即可。
第二个参数,OutputArray类型的dst,输出图像,当其非零时,有着dsize(第三个参数)的尺寸,或者由src.size()计算出来。
第三个参数,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(效率较高,速度较快,推荐使用)。
关于插值,我们看几张图就能更好地理解。先看原图:

这里写图片描述

图8

当进行6次图像缩小接着6次图像放大操作后,两种不同的插值方式得到的效果图:
这里写图片描述

图9

效果很明显,第一张全是一个个的像素,非常影响美观。另外一张却有雾化的朦胧美感,所以插值方式的选择,对经过多次放大缩小的图片最终得到的效果是有很大影响的。
接着我们来看两种resize的调用范例。
方式一,调用范例:

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

方式二、调用范例:

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

接着我们看看完整的示例程序。
代码参看附件【demo3】,程序运行截图。

这里写图片描述

图10

1.6.4 pyrUp()函数剖析

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

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

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

第四个参数,int类型的borderType,又来了,边界模式,一般我们不用去管它。
pyrUp函数执行高斯金字塔的采样操作,其实它也可以用于拉普拉斯金字塔的。首先,它通过插入可为零的行与列,对源图像进行向上取样操作,然后将结果与pyrDown()乘以4的内核做卷积,就是这样。直接看完整的示例程序。
代码参看附件【demo4】,程序运行截图。

这里写图片描述

图11

1.6.5 pyrDown()函数剖析

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

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

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

|dstsize.width2src.cols|2|dstsize.height2src.rows|2

该pyrDown函数执行了高斯金字塔建造的向下采样的步骤。首先,它将源图像与如下内核做卷积运算:

12561464141624164624362464162416414641

接着,它便通过对图像的偶数行和列做插值来进行向下采样操作。依然是看看完整的示例程序。
代码参看【demo5】,程序运行截图。

这里写图片描述

图12

1.6.6图像金字塔综合实例

这个示例程序中,分别演示了用resize,pryUp,pryDown来让源图像进行放大缩小的操作,分别用键盘按键1、2、3、4、A、D、W、S来控制图片的放大与缩小。
代码参看附件【demo6】。

这里写图片描述

图13经过多次按键后的效果

参考:
英文:https://docs.opencv.org/master/d4/d1f/tutorial_pyramids.html
中文:
http://www.opencv.org.cn/opencvdoc/2.3.2/html/doc/tutorials/imgproc/pyramids/pyramids.html#pyramids

1.7阈值化

在对图像的分割中,阈值化是最简单的图像分割的方法。比如我们从一副图像中利用阈值分割出我们需要的物体部分(当然这里的物体可以是一部分或者整体)。这样的图像分割方法是基于图像中物体与背景之间的灰度差异,而且此分割属于像素级的分割。为了从一副图像中提取出我们需要的部分,应该用图像中的每一个像素点的灰度值与选取的阈值进行比较,并作出相应的判断。(注意:阈值的选取依赖于具体的问题。即:物体在不同的图像中有可能会有不同的灰度值。一旦找到了需要分割的物体的像素点,我们可以对这些像素点设定一些特定的值来表示。(例如:可以将该物体的像素点的灰度值设定为:‘0’(黑色),其他的像素点的灰度值为:‘255’(白色);当然像素点的灰度值可以任意,但最好设定的两种颜色对比度较强,方便观察结果)。

1.7.1阈值化的类型

OpenCV中提供了阈值(threshold)函数: threshold 。这个函数有5种阈值化类型,在接下来具体介绍。为了解释阈值分割的过程,我们来看一个简单有关像素灰度的图片,该图如下。该图中的蓝色水平线代表着具体的一个阈值。

这里写图片描述

图14

阈值类型1:二进制阈值化
该阈值化类型如下式所示:
dst(x,y)={maxVal,0,if src(x,y) > threshotherwise

解释:在运用该阈值类型的时候,先要选定一个特定的阈值量,比如:125,这样,新的阈值产生规则可以解释为大于125的像素点的灰度值设定为最大值(如8位灰度值最大为255),灰度值小于125的像素点的灰度值设定为0。
这里写图片描述

图15

阈值类型2:反二进制阈值化
该阈值类型如下式所示:
dst(x,y)={0,maxVal,if src(x,y) > threshotherwise

解释:该阈值化与二进制阈值化相似,先选定一个特定的灰度值作为阈值,不过最后的设定值相反。(在8位灰度图中,例如大于阈值的设定为0,而小于该阈值的设定为255)。
这里写图片描述

图16

阈值类型3:截断阈值化
该阈值化类型如下式所示:
dst(x,y)={threshold,src(x,y),if src(x,y) > threshotherwise

解释:同样首先需要选定一个阈值,图像中大于该阈值的像素点被设定为该阈值,小于该阈值的保持不变。(例如:阈值选取为125,那小于125的阈值不改变,大于125的灰度值(230)的像素点就设定为该阈值)。

这里写图片描述

图17

阈值类型4:阈值化为0
该阈值类型如下式所示:
dst(x,y)={src(x,y),0,if src(x,y) > threshotherwise

解释:先选定一个阈值,然后对图像做如下处理:1 像素点的灰度值大于该阈值的不进行任何改变;2 像素点的灰度值小于该阈值的,其灰度值全部变为0。

这里写图片描述

图18

阈值类型5:反阈值化为0
该阈值类型如下式所示:
dst(x,y)={0,src(x,y),if src(x,y) > threshotherwise

解释:原理类似于0阈值,但是在对图像做处理的时候相反,即:像素点的灰度值小于该阈值的不进行任何改变,而大于该阈值的部分,其灰度值全部变为0。

这里写图片描述

图19

参考:
http://www.opencv.org.cn/opencvdoc/2.3.2/html/doc/tutorials/imgproc/threshold/threshold.html#basic-threshold

1.7.2固定阈值操作:threshold()函数

threshold 方法是通过遍历灰度图中点,将图像信息二值化,处理过后的图片只有二种色值。
其函数原型如下:

C++: double threshold(InputArray src,                       OutputArray dst,                       double thresh,                       double maxVal,                       int thresholdType)

【参数】
第一个参数,InputArray类型的src,输入数组,填单通道 , 8或32位浮点类型的Mat即可。
第二个参数,OutputArray类型的dst,函数调用后的运算结果存在这里,即这个参数用于存放输出结果,且和第一个参数中的Mat变量有一样的尺寸和类型。
第三个参数,double类型的thresh,阈值的具体值。
第四个参数,double类型的maxval,当第五个参数阈值类型type取 THRESH_BINARY 或THRESH_BINARY_INV阈值类型时的最大值.
第五个参数,int类型的type,阈值类型。其它参数很好理解,我们来看看第五个参数,第五参数有以下几种类型
0: THRESH_BINARY 当前点值大于阈值时,取Maxval,也就是第四个参数,下面再不说明,否则设置为0
1: THRESH_BINARY_INV 当前点值大于阈值时,设置为0,否则设置为Maxval
2: THRESH_TRUNC 当前点值大于阈值时,设置为阈值,否则不改变
3: THRESH_TOZERO 当前点值大于阈值时,不改变,否则设置为0
4: THRESH_TOZERO_INV 当前点值大于阈值时,设置为0,否则不改变

这里写图片描述

图 20各个类型选项的对应操作

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

函数原型:

C++: void adaptiveThreshold(InputArray src,                             OutputArray dst,                             double maxValue,                            int adaptiveMethod,                             int thresholdType,                             int blockSize, double C)
C: void cvAdaptiveThreshold(const CvArr* src,                             CvArr* dst,                             double max_value,                             int adaptive_method=CV_ADAPTIVE_THRESH_MEAN_C,                             int threshold_type=CV_THRESH_BINARY,                             int block_size=3,                             double param1=5 )

【参数】
第一个参数,src – Source 8-bit single-channel image.
第二个参数,dst – Destination image of the same size and the same type as src .
第三个参数,maxValue – Non-zero value assigned to the pixels for which the condition is satisfied. See the details below.
第四个参数adaptiveMethod – Adaptive thresholding algorithm to use, ADAPTIVE_THRESH_MEAN_C or ADAPTIVE_THRESH_GAUSSIAN_C . See the details below.
第五个参数,thresholdType – Thresholding type that must be either THRESH_BINARY or THRESH_BINARY_INV .
第六个参数,blockSize – Size of a pixel neighborhood that is used to calculate a threshold value for the pixel: 3, 5, 7, and so on.
第七个参数,C – Constant subtracted from the mean or weighted mean (see the details below). Normally, it is positive but may be zero or negative as well.
The function transforms a grayscale image to a binary image according to the formulae:(第五个参数)
THRESH_BINARY

dst(x,y)={maxValue,0,if src(x,y) > T(x,y)otherwise

THRESH_BINARY_INV
dst(x,y)={0,maxValue,if src(x,y) > T(x,y)otherwise

where T(x,y) is a threshold calculated individually for each pixel.
For the method ADAPTIVE_THRESH_MEAN_C , the threshold value is a mean of the neighborhood of minus C .
For the method ADAPTIVE_THRESH_GAUSSIAN_C , the threshold value is a weighted sum (cross-correlation with a Gaussian window) of the neighborhood of minus C . The default sigma (standard deviation) is used for the specified blockSize . See getGaussianKernel() .

1.7.3基本阈值操作实例

代码参看附件【demo7】

这里写图片描述

图21按键提示

这里写图片描述

图22二进制阈值

这里写图片描述

图23反二进制阈值

这里写图片描述

图24截断阈值

这里写图片描述

图25反阈值化为0
这里写图片描述

图26阈值化为0

参考:
英文:https://docs.opencv.org/master/db/d8e/tutorial_threshold.html
中文:
http://www.opencv.org.cn/opencvdoc/2.3.2/html/doc/tutorials/imgproc/threshold/threshold.html#basic-threshold

本章附件:

请点击参考链接

【注意】博主在附件中的代码只有Linux版本的,如何使用Windows使用该代码请参看博主的另一篇博文
Opencv环境搭建(Visual Studio+Windows)- 请点击
有任何问题请联系博主。

阅读全文
0 0
原创粉丝点击