轮廓究竟是什么?一个轮廓通常对应一系列的点,也就是图像中的一条曲线.表示的方法可能根据不一样状况而有所不一样.有多重方法能够表示曲线.在openCV中通常用序列来存储轮廓信息.序列中的每个元素是曲线中一个点的位置.关于序列表示的轮廓细节将在后面讨论,如今只要简单把轮廓想象为使用CvSeq表示的一系列的点就能够了.算法
函数cvFindContours()从二值图像中寻找轮廓.cvFindContours()处理的图像能够是从cvCanny()函数获得的有边缘像素的图像,或者是从cvThreshold()及cvAdaptiveThreshold()获得的图像,这时的边缘是正和负区域之间的边界.数组
图8-2描述了cvFindContours的函数功能,图像的上半部分是神色背景和白色区域(被从A到E标记)的测试图像.下半部分是使用cvFindCountours()函数后会获得的轮廓的说明.这些轮廓被标记为cX或hx,"c"表示"轮廓(contour)","h"表示"孔(hole)","X表述数字".其中一些轮廓用虚划线表示;代表他们是白色区域的外部边界(例如,非0区域).孔(hole)的外部边界(例如,非0区域)即白色区域的内部边界.在图中是用电线表示外部边界的.OpenCV的cvFindContours()函数可区份内部和外部边界.app
包含的概念在不少应用中都很是重要.所以.OpenCV容许获得的轮廓被聚合成一个轮廓树,从而把包含关系编码到树结构中.这个测试图的轮廓树在根节点的轮廓叫c0,孔h00和h01是它的字子节点.这些轮廓中直接包含轮廓称为他们的子节点,以此类推.ide
如今来看cvFindContours()函数函数
storage 是内存存储器,cvFindContours()找到的轮廓记录在此内存里.正如以前所说,这个存储器的空间应该由cvCreateMemStorage()分配.测试
first_contour 是指向CvSeq*的一个指针firstContour.无需动手,cvFindContours()会自动分配该指针.实际上,只要在这里传一个指针就能够了函数会自动设置.不须要分配和释放(new/delete或者malloc/free).就是这个指针(例如,*firstContour)指向轮廓树的首地址(head).cvFindContours()返回值是,找到的全部轮廓的个数ui
cvSeq* firstContout = NULL;编码
cvFindContours(..., &firstContour, ...);spa
headerSize告诉cvFindContours()更多有关对象分配的信息,它能够被设定为sizeof(CvContour)或者sizeof(CvChain)(当近似方法参数method被设定为CV_ChAIN_CODE时使用后者).最后是mode和method参数,他们分别指定计算方法和如何计算..net
mode变量能够被设置为如下四个选项之一: CV_RETR_ExTERNAL, CV_RETR_LIST, CV_RETR_CCOMP或CV_RETR_TREE.mode的值向cvFindeContours()说明须要的轮廓类型,和但愿的放回值形式.具体说来,mode的值决定把找到的轮廓如何挂到轮廓树节点变量(h_prev,h_next,v_prev和v_next)上,图8-3展现了四种可能的mode值所获得的结果的拓扑结构.
每中状况下,结构均可以当作是被"横向"链接(h_next和h_prev)联系和被"纵向"链接(v_next和v_prev)不一样的"层次".
CV_RETR_EXTERNAL 只检测出最外的轮廓.图8-2中,只有一个最外轮廓,所以图8-3中第一个轮廓指向最外的序列,除此以外没有别的链接
CV_RETR_LIST 检测全部的轮廓并将他们保存到表(list)中.图8-3描绘了从图8-2样图中获得的表.在这个例子中,有8条轮廓被找到,他们相互之间有h_prev和h_next链接(这里并无使用v_prev和v_next)
CV_RETR_CCOMP 检出全部的轮廓并将他们组织成双层结构(two-level hierarchy),顶层边界是全部成份的外界边界,第二层边界是空的边界.图8-3中,咱们能看到5个外部边界,其中3个包含孔.孔被v_next和v_prev能够只包括一个值,此节点能够只有一个子节点.c0中有两个孔,由于v_next能够值包括一个值,次节点能够只有一个子节点.c0以内的全部孔相互间有h_prev和h_next指针链接.
CV_RETR_TREE 检出全部轮廓而且从新创建网状的轮廓结构.在咱们给出的例子中(图8-2和8-3中),这意味着根节点是最外的轮廓c0.c0之下是空h00,在同一层次中与另外一个孔h01相链接.同理,每一个孔都有子节点(相对应的是c000和c010),这些子节点与父节点被垂直链接起来.这个步骤一直持续到图像最内层的轮廓,这些轮廓会成为树叶节点.
如下的五个值与方法相关(例如轮廓会如何被近似).
CV_CHAIN_CODE 用freeman链码输出轮廓,其余方法输出多边形(顶点的序列)
CV_CHAIN_APPROX_NONE 将链码编码中的全部点转换为点
CV_CHAIN_APPROX_SIMPLE 压缩水平,垂直或斜的部分,只保存最后一个点
CV_CHAIN_APPROX_TC89_L1或CV_CHAIN_APPROX_TC89_KCOS使用Ten-Chin链逼近算法中的一个
CV_LINK_RUNS 与上述算法彻底不一样的算法,链接全部水平层次的轮廓.此方法只可与Cv_RETR_LIST搭配使用.
使用序列表示轮廓
当调用cvFindContours函数的时候,返回多个序列.序列的类型依赖与调用cvFindContours时 所传递的参数.默认状况下使用CV_RETR_LIST和CV_CHAIN_APPROX_SIMPLE参数.
序列中保存一系列的点,这些点构成轮廓,轮廓是本章的重点.轮廓只是序列所能表示物体的一种.轮廓的点的序列,能够用来表示图像空间中的曲线.这种点的序列很经常使用,全部须要有专门的函数来帮助咱们对他进行处理.下面是一组这样的处理函数.
cvSubstituteContour()函数用于替换scanner指向的轮廓.该函数的一个特性是,若是参数 new_contour为NULL,那么当前的轮廓将被从Scanner指定的树或链表中删除(受影响的序列会做适当更新,来保证不会有指针指向不存在的物体).
函数cvEndFindContour()结束轮廓查找,而且将scanner设置为结束状态.注意,scanner并无被删除,实际上该函数返回的是指针所指序列的第一个元素.
最后一个函数cvApproxChains()函数.该函数将Freeman链转换为多边形表示(精确转换或者近似拟合).
通常状况下,经过cvFindCountours获取的轮廓是一系列顶点的序列.另外一种不一样的表达是设置method参数为CV_CHAIN_CODE,而后生成轮廓.当选者CV_CHAIN_CODE标志的时候,检测的轮廓经过Freemain链码[Freeman67](图8-4)的方式返回.在Freeman链码中,多边形被表示为一系列的位移,每个位移有8个方向,这8个方向使用整数0到7表示.Freeman链码对于识别一些形状的物体颇有帮助.若是获得的是Freeman链码,能够经过如下两个函数读出每一个点
第一个函数用来初始化Freeman链CvChainPtReader结构,第二个函数经过CvChainptReader来读每一个点,CvChainPtReader对应当前状态.结构CvChain从CvSeq扩展得来.和CvContourScanner从多个轮廓间迭代同样,CvChainPtReader用于迭代一个使用Freemain链码表示轮廓中的每一个点.CvChainPtReader和CvSeqReader的用法相似.如您所指望,当全部点都读完后,返回CvChainPtReader值为NULL.
一个常用的功能是在屏幕上绘制检测到的轮廓.绘制能够用cvDrawContours函数完成
经过max_level变量能够告诉cvDrawConturs() 如何处理经过节点树变量连结到一个轮廓上的其余任何轮廓.此变量能够被设置为遍历轮廓的最大深度.所以max_level = 0表示与输入轮廓属于赞成等级的全部轮廓(更具体的说,输入轮廓和与其相邻的轮廓被画出),max_level = 1表示与输入轮廓属于同一登记的全部轮廓与其子节点被画出,以此类推.若是项要画的轮廓是由cvFindContous()的CV_RETR_CCOMP或CV_RETR_TREE模式获得的话,max_level的负值也是被支持的.在这种状况下,max_level=-1表示只有输入轮廓被画出,以此类推,max_level = -2 表示输入轮廓与其直系(仅直接相连的)子节点会被画出,以此类推.
参数thickness和line_type就如其字面含义所示.最后,咱们能够给绘图程序一个偏移量,这样轮廓能够被画在指定的精确坐标上.当轮廓坐标被转换成质心坐标或其余局部坐标系的时候,这个特性很是有用.
若是在图像上的不一样感兴趣的区域屡次执行cvFindContour(),而后又想将全部结果在原来大图像上显示出来,便宜量offset也颇有用.相反,能够先从大图提取出一个轮廓,而后在用offset和填充,在小图像上造成和轮廓对应的蒙板(mask);
首先建立一个窗口用于显示图像,滑动条(trackbar)用于设置阈值,而后对采二值化后的图像提取轮廓并绘制轮廓.当控制参数的滑动条变换时,图像被更新.
而后g_image被转换为灰度图像,接着用g_thresh为参数进行二值化处理,获得的二值图像保存在g_gray中.cvFindContours从二值图像g_gray查找轮廓,而后将获得的轮廓用cvDrawContours()函数绘制为白色到灰度图像.最终图像在窗口中显示出来,并将在回调函数开始处申请的结构释放.
在上例中,咱们检测出输入图像的轮廓,而后逐个绘制没格轮廓.从这个例子中,咱们能够了解到轮廓检测方法(如代码中是CV_RETR_LIST)以及max_depth(代码中是0)等参数的细节.若是设置的max_depth是一个比较大的值,你能够发现cvFindCountours()返回的轮廓是经过h_next链接被遍历.对于其余一些拓扑结构(CV_RETR_TREE,CV_REER_CCOMP等),你会发现有些轮廓被画过不仅一次
例8-3 在输入图像上寻找并绘制轮廓
当咱们绘制一个多边形或者进行形状分析的时候,一般须要使用多边形毕竟一个轮廓,使顶点数目变少.有多种方法能够实现这个功能,OpenCV实现了其中的一种逼近算法.函数cvApproxPoly是该算法的一种实现,能够处理轮廓的序列.
由于cvApproxPoly在返回结果的时候须要建立新的对象,所以 须要指定一个内存存储器以及头结构大小.(通常为sizeof(CvContour)).
逼急算法目前只可以使用CV_POLY_APPROx_DP.另外两个参数为逼近算法参数(目前只用到第一个).eps参数指定逼近的精度.若是想了解这个参数如何起做用的的必须仔细了解具体的算法.最后一个参数指定是否针对所有的轮廓(经过h_next和v_next可达的)进行逼近
若是为0,则表示只处理src_seq指向轮廓.
下面简要介绍一下算法的工做原理.参考图8-5,算法先从轮廓(图b)选择2个最远的点,而后将2个连成一个线段(图c),而后再查找轮廓上到线段距离最远的点,添加到逼近后的心轮廓(图d).算法反复迭代,不断将最远点的添加到结果中.直到全部点的点到多边形的最短距离小于eps参数指定的精度(图f).从这里能够看出,精度和轮廓的周长,或者外包矩形周长的几分之一比较合适.
曲线逼近的过程和寻找关掉点的过程密切相关。跟曲线上的其余点相比,关键点是那些包含曲线信息比较多的点。关键点在逼近算法以及其余应用中都会涉及。函数cvFindDominantPoints()实现了被称为IPAN*[Chetvreikov99]的算法.
CvSeq cvFindDominantPoints(CvSeq* contour,CvMemStorage* storage,int metod = CV_DOMINANT_IPAN,double parameter1 = 0,double parameter2 = 0,double parameter3 = 0,double parameter4 = 0);
本质上,IPAN算法经过扫描轮廓上并在曲线内部使用可能顶点构造三角形来实现.对于三角形的大小和张角有特殊要求.在此某一特定的全局阈值和它的相邻的张角小的状况下,具备大张角的点被保留.
函数cvFindDominantPoints()按照惯例使用参数CvSeq* 和CvMemStorage* .而且要求指定一个方法,和cvApproxPoly()相同,目前可供选择的方法只有一个,就是CV_DOMINANT_IPAN.
接下来四个参数是:最短距离dmin,最长距离dmax,相邻距离dn和最大角度θmax.如图8-6所示,算法首先把全部两边距离rpa和rpb在dmin和dmax之间,θab < θmax的三角形找出来.而后保留对于距离dn(dn的大小不得超过dmax)有最小夹角θab的全部点p.dmin,dmax,dn和θmax典型值能够是7,9,9,150(最后一个参数是以度数为单位的角大小).
轮廓处理中常常遇到的另外一个任务是计算一些轮廓变化的归纳特性.这可能包括长度或者其余一些反映轮廓总体大小的度量.另外一个有用的特性是轮廓的轮廓矩(contourmoment),能够用来归纳轮廓的总形状特性
函数cvContourPerimeter()做用于一个轮廓并返回其长度.事实上,此函数是一个调用函数cvArcLength()的宏.
一个和cvArcLength()有紧密关系的函数是cvContourArea(),如其名称所示,这个函数同于计算轮廓的面积.函数的参数contour和slice和cvArcLength()同样.
固然长度和面积只是轮廓的简单特性,更复杂一些的特性描述应该是矩形边界框,圆形边界框或椭圆形边界框.有两种方法能够获得矩形边界框,圆形与椭圆形编辑框各只有一种方法.
cvBoundingRect()获得的长方形的一个问题是,cvRect只能表现一个四边水平和竖直的长方形.然而函数cvMinAreaRect2()能够返回一个包围轮廓最小的长方形,这个长方形多是倾斜的;请看图8-7,该函数的参数和cvBoundingRect()的类似.opencv的数据类型CvBox2D就是用来表述这样的长方形状的.
接着咱们来看函数cvMinEnclosingCircle().该函数和矩形边界框的做用基本相同,输入一样很灵活,能够是点的序列,也能够是二维点的数组.
与最小包围圆同样,OpenCV提供一函数来拟合一组点,以获取最佳拟合椭圆
椭圆的拟合结果由CvBox2D结构体返回,给出的矩形正好彻底包围椭圆,如图8-8所示.
在处理CvBox2D或多边形边界的时候,常常须要进行多边形以及边界框的重叠判断.OpenCV提供了一组方便的小函数用于此类测试.
下一个使用函数cvBoxPoints()用于计算CvBox2D结构表示矩形的4个顶点.固然你也能够本身经过三角函数计算,不过这使人头大,而简单调用一下这个函数则可求出.
第三实用函数cvPointSeqFromMat()从mat中初始化序列.这在你须要使用轮廓相关的函数,可是函数又不支持矩阵参数的时候使用.第一个参数用于指定点序列类型,seq_kind能够为如下类型:点集为0;曲线为CV_SEQ_KIND_CURVE;封闭曲线为CV_SEQ_KIND_CURVE|Cv_SEQ_FLAG_CLOSED.第二个参数是输入的矩阵,该参数是连续的1维向量.矩阵类型必须为cv_32C2或CV_32FC2.
下面的两个参数是指针,指针指向的内容经过该函数填充.contour_header参数对应轮廓结构,通常要事先建立,不过由该函数负责初始化.block参数一样如此,也是由该函数复杂初始化.最后,该函数放回一个类型为CvSeq*的序列指针,指向你输入的序列头*contour_header.返回值跟输入参数相同只是为了使用该函数时更方便,由于这样你就能够将该函数看成某个轮廓函数的参数使用,代码写入同一行.
最后一个平面几个相关的函数是cvPointPolygonTest(),用于测试一个点是否在多边形的内部.若是参数measure_dist非零,函数返回值是点到多边形最近距离.若是measure_dist为0,函数返回+1,-1,0,分别表示在内部,外部,在多边形边上.参数contour能够是序列,也能够是2通道矩阵向量.