轮廓查找

转载于:http://blog.csdn.net/hitwengqi/article/details/6922051

1、查找轮廓

轮廓(contour)到底是什么?

      一个轮廓对应一系列的店,也就是图像中的一条曲线,OpenCV中一般用序列来存储轮廓信息,序列中的每一个元素是曲线中一个点的位置。

轮廓树?

      OpenCV将得到的轮廓聚合成一个轮廓树,把轮廓的包含关系编码到树结构中,保护的轮廓在树中体现为节点。轮廓树有4种拓扑结构,CV_RETR_EXTERNAL、CV_RETR_CCOMP、CV_RETR_TREE、CV_RETR_LIST。

2、表示轮廓

轮廓只是序列所能表示的一种,轮廓类型CvContour是从CvSeq扩展而来,它还含有几个其他的成员,例如颜色、外包矩形区域等。

轮廓时点的序列,可以用来表示图像空间中的曲线,涉及到的几个处理函数如下:

    cvFindContours();  //查找到所有轮廓后统一返回轮廓个数   

    cvStartFindContours();  //每次查找并返回一个轮廓

    cvFindNextContour();  //查找剩余的轮廓

    cvSubstituteContour();  //替换scannner指向的轮廓

    cvEndFindContour();  //结束轮廓查找

    cvApproxChains();  //将Freeman链转换为多边形表示,近似拟合。

Freeman链码

      在链码中,多边形被表示为一系列的位移,每一个位移有8个方向。

通过以下函数读出每个点:

    cvStartReadChainPoints();  //初始化Freeman链CvChainPtReader结构

    cvReadChainPoint();  //读取每个点

3、绘制轮廓

    在屏幕上绘制检测到的轮廓,用cvDrawContours函数。

例程:根据滑动条参数设置阈值检测轮廓。

--------------------------------------------------------------------------------------------------

cvCreateTrackbar 
创建trackbar并将它添加到指定的窗口。 

CV_EXTERN_C_FUNCPTR( void (*CvTrackbarCallback)(int pos) );

int cvCreateTrackbar( const char* trackbar_name, const char* window_name,
int* value, int count, CvTrackbarCallback on_change );
trackbar_name
被创建的trackbar名字。 
window_name
窗口名字,这个窗口将为被创建trackbar的父对象。 
value 
整数指针,它的值将反映滑块的位置。这个变量指定创建时的滑块位置。 
count 
滑块位置的最大值。最小值一直是0。 
on_change 
每次滑块位置被改变的时候,被调用函数的指针。这个函数应该被声明为void Foo(int); 如果没有回调函数,这个值可以设为NULL。 
函数cvCreateTrackbar用指定的名字和范围来创建trackbar(滑块或者范围控制),指定与trackbar位置同步的变量,并且指定当trackbar位置被改变的时候调用的回调函数。被创建的trackbar显示在指定窗口的顶端。 


FindContours
在二值图像中寻找轮廓 

int cvFindContours( CvArr* image, CvMemStorage* storage, CvSeq** first_contour,
int header_size=sizeof(CvContour), int mode=CV_RETR_LIST,
int method=CV_CHAIN_APPROX_SIMPLE, CvPoint offset=cvPoint(0,0) );
image 
输入的 8-比特、单通道图像. 非零元素被当成 1, 0 象素值保留为 0 - 从而图像被看成二值的。为了从灰度图像中得到这样的二值图像,可以使用 cvThreshold, cvAdaptiveThreshold 或 cvCanny. 本函数改变输入图像内容。
storage 
得到的轮廓的存储容器 
first_contour 
输出参数:包含第一个输出轮廓的指针 
header_size 
如果 method=CV_CHAIN_CODE,则序列头的大小 >=sizeof(CvChain),否则 >=sizeof(CvContour) . 
mode 
提取模式. 
CV_RETR_EXTERNAL - 只提取最外层的轮廓 
CV_RETR_LIST - 提取所有轮廓,并且放置在 list 中 
CV_RETR_CCOMP - 提取所有轮廓,并且将其组织为两层的 hierarchy: 顶层为连通域的外围边界,次层为洞的内层边界。 
CV_RETR_TREE - 提取所有轮廓,并且重构嵌套轮廓的全部 hierarchy 
method 
逼近方法 (对所有节点, 不包括使用内部逼近的 CV_RETR_RUNS). 
CV_CHAIN_CODE - Freeman 链码的输出轮廓. 其它方法输出多边形(定点序列). 
CV_CHAIN_APPROX_NONE - 将所有点由链码形式翻译(转化)为点序列形式 
CV_CHAIN_APPROX_SIMPLE - 压缩水平、垂直和对角分割,即函数只保留末端的象素点; 
CV_CHAIN_APPROX_TC89_L1, 
CV_CHAIN_APPROX_TC89_KCOS - 应用 Teh-Chin 链逼近算法. CV_LINK_RUNS - 通过连接为 1 的水平碎片使用完全不同的轮廓提取算法。仅有 CV_RETR_LIST 提取模式可以在本方法中应用.
offset 
每一个轮廓点的偏移量. 当轮廓是从图像 ROI 中提取出来的时候,使用偏移量有用,因为可以从整个图像上下文来对轮廓做分析. 
函数 cvFindContours 从二值图像中提取轮廓,并且返回提取轮廓的数目。指针 first_contour 的内容由函数填写。它包含第一个最外层轮廓的指针,如果指针为 NULL,则没有检测到轮廓(比如图像是全黑的)。其它轮廓可以从 first_contour 利用 h_next 和 v_next 链接访问到。 在 cvDrawContours 的样例显示如何使用轮廓来进行连通域的检测。轮廓也可以用来做形状分析和对象识别 - 见CVPR2001 教程中的 squares 样例。该教程可以在 SourceForge 网站上找到。 
--------------------------------------------------------------------------------------------------
/*code*/

[cpp]  view plain copy
  1. #include <cv.h>  
  2. #include <highgui.h>  
  3.   
  4. IplImage* g_image = NULL;  
  5. IplImage* g_gray = NULL;  
  6. int g_thresh = 100;  //阈值  
  7. CvMemStorage* g_storage = NULL;  
  8.   
  9. void on_trackbar( int a );  
  10.   
  11. int main( int argc, char** argv )  
  12. {  
  13.     if( ( argc != 2 ) || !( g_image = cvLoadImage( argv[1] ) ) )  
  14.         return -1;  
  15.     cvNamedWindow( "Contours", 1 );  
  16.     cvCreateTrackbar( "Threshold""Contours", &g_thresh, 255, on_trackbar );  //滚动条设置阈值,得到的值存储到g_thresh中  
  17.     on_trackbar( 0 );  
  18.     cvWaitKey(0);  
  19.   
  20.     return 0;  
  21. }  
  22.   
  23. void on_trackbar( int a )  
  24. {  
  25.     if( g_storage == NULL )  
  26.     {  
  27.         g_gray = cvCreateImage( cvGetSize( g_image ), 8, 1 );  
  28.         g_storage = cvCreateMemStorage(0);  
  29.     }  
  30.     else  
  31.     {  
  32.         cvClearMemStorage( g_storage );  
  33.     }  
  34.     CvSeq* contours = 0;  
  35.     cvCvtColor( g_image, g_gray, CV_BGR2GRAY );  //转换色彩空间,转为灰度图  
  36.     cvThreshold( g_gray, g_gray, g_thresh, 255, CV_THRESH_BINARY );  //阈值变换  
  37.     cvFindContours( g_gray, g_storage, &contours );  
  38.     cvZero( g_gray );  
  39.     if( contours )  
  40.     {  
  41.         cvDrawContours( g_gray, contours, cvScalarAll( 255 ), cvScalarAll( 255 ), 1 );  //绘制轮廓  
  42.     }  
  43.     cvShowImage( "Contours", g_gray );  
  44. }  
--------------------------------------------------------------------------------------------------

/*result*/

依旧是水果图

Threshold == 120

Threshold == 150



------------------------------------------------华丽的分割线-------------------------------------------------

/*在输入图像上寻找并绘制轮廓*/

/*code*/

[cpp]  view plain copy
  1. #include <highgui.h>  
  2. #include <cv.h>  
  3. #include <stdio.h>  
  4.   
  5. #define CVX_RED CV_RGB( 0xff, 0x00, 0x00 )  //宏定义  
  6. #define CVX_GREEN CV_RGB( 0x00, 0xff, 0x00 )  
  7. #define CVX_BLUE CV_RGB( 0x00, 0x00, 0xff )  
  8.   
  9. int main(int argc, char** argv)  
  10. {  
  11.     cvNamedWindow( argv[0], 1 );  
  12.     IplImage* img_8uc1 = cvLoadImage( argv[1], CV_LOAD_IMAGE_GRAYSCALE );  
  13.     IplImage* img_edge = cvCreateImage( cvGetSize( img_8uc1 ), 8, 1 );  //单通道图  
  14.     IplImage* img_8uc3 = cvCreateImage( cvGetSize( img_8uc1 ), 8, 3 );  
  15.     cvThreshold( img_8uc1, img_edge, 128, 255, CV_THRESH_BINARY );  
  16.     CvMemStorage* storage = cvCreateMemStorage();  
  17.     CvSeq* first_contour = NULL;  
  18.   
  19.     int Nc = cvFindContours( img_edge, storage, &first_contour, sizeof( CvContour ), CV_RETR_LIST );  //返回找到的轮廓数,并将第一个轮廓的序列返回到first_contour变量中  
  20.     int n = 0;  
  21.     printf( "Total Contours Detected: %d\n", Nc );  
  22.     for( CvSeq* c = first_contour; c != NULL; c = c -> h_next )  
  23.     {  
  24.         cvCvtColor( img_8uc1, img_8uc3, CV_GRAY2BGR );  
  25.         cvDrawContours( img_8uc3, c, CVX_RED, CVX_BLUE, 0, 2, 8 ); //绘制,CVX_RED是宏定义函数  
  26.         printf( "Contour #%d\n", n );  
  27.         cvShowImage( argv[0], img_8uc3 );  
  28.         printf( " %d elements:\n", c -> total );  
  29.         forint i = 0; i < c -> total; ++i )  
  30.         {  
  31.             CvPoint* p = CV_GET_SEQ_ELEM( CvPoint, c, i );  
  32.             printf( "     (%d,%d)\n", p -> x, p -> y );  //打印出序列的所有元素  
  33.         }  
  34.         cvWaitKey(0);  
  35.         n++;  
  36.     }  
  37.       
  38.     printf( "Finished all contours.\n" );  
  39.     cvCvtColor( img_8uc1, img_8uc3, CV_GRAY2BGR );  
  40.     cvShowImage( argv[0], img_8uc3 );  
  41.     cvWaitKey(0);  
  42.     cvDestroyWindow( argv[0] );  
  43.     cvReleaseImage( &img_8uc1 );  
  44.     cvReleaseImage( &img_8uc3 );  
  45.     cvReleaseImage( &img_edge );  
  46.   
  47.     return 0;  
  48. }  

/*result*/

source image


contour 1


contour 2


8 contours



本小节学习轮廓的常用操作,识别和处理,深入理解轮廓的相关算法及原理。

1. 多边形逼近

当需要对图像进行形状分析时,需要使用多边形逼近一个轮廓,使得顶点数目变少,算法原理比较简单,核心就是不断找多边形最远的点加入形成新的多边形,直到最短距离小于指定的精度。OpenCV里面用函数cvApproxPoly()实现。

ApproxPoly
用指定精度逼近多边形曲线 

CvSeq* cvApproxPoly( const void* src_seq, int header_size, CvMemStorage* storage,
int method, double parameter, int parameter2=0 );
src_seq 
点集数组序列 
header_size 
逼近曲线的头尺寸 
storage 
逼近轮廓的容器。如果为 NULL, 则使用输入的序列 
method 
逼近方法。目前仅支持 CV_POLY_APPROX_DP , 对应 Douglas-Peucker 算法. 
parameter 
方法相关参数。对 CV_POLY_APPROX_DP 它是指定的逼近精度 
parameter2 
如果 src_seq 是序列,它表示要么逼近单个序列,要么在 src_seq 的同一个或低级层次上逼近所有序列 (参考 cvFindContours 中对轮廓继承结构的描述). 如果 src_seq 是点集的数组 (CvMat*) , 参数指定曲线是闭合 (parameter2!=0) 还是非闭合 (parameter2=0).
函数 cvApproxPoly 逼近一个或多个曲线,并返回逼近结果。对多个曲线的逼近,生成的树将与输入的具有同样的结构。(1:1 的对应关系). 


2. 轮廓的特性概括

轮廓包括一些特性,长度、矩形边界框、面积、圆形和椭圆形边界,轮廓钜等。


3. 轮廓的匹配

实际应用中会有两个轮廓的匹配比较相似度,或者比较一个轮廓和一个抽象模板的的相似度。

简单的方式是比较两个轮廓的轮廓矩。

矩的定义:通过对轮廓上所有点进行积分运算。

计算轮廓矩:cvContoursMoments

归一化的矩(重要):不同大小但是形状相同的物体轮廓应有相同的矩。因此在比对轮廓相似度时通常使用归一化的矩,Hu矩是其中一种较好的方法。但是有一个问题,为什么每个图像要对应7个hu矩呢?

使用Hu矩进行匹配:

double cvMatchShapes( const void* object1, const void* object2, int method, double parameter=0 )

等级匹配:

用相似度量度来计算轮廓所有匹配的部分,这就要用到轮廓树,这里的轮廓树不是cvFindContours()函数返回的多个轮廓的继承描述,而是描述一个特定形状(不是多个特定形状)内各部分的等级关系,建立轮廓树的算法很巧妙,要掌握这种思想(见书page285),最后产生的轮廓树是一个二分树。

如图所示的轮廓树,每个节点表示一个三角形,父节点是子节点合并形成的新的三角形:


轮廓的凸包和凸缺陷:

概念很简单,见图就可理解,箭头指的区域就是缺陷,整个手的外部多边形就是凸包。


有三个对应的操作函数:

CvSeq* cvConvexHull2( const CvArr* input, void* hull_storage=NULL,int orientation=CV_CLOCKWISE, int return_points=0 );  //简单计算已知轮廓的凸包

int cvCheckContourConvexity( const CvArr* contour );  //测试轮廓的凸性

CvSeq* cvConvexityDefects( const CvArr* contour, const CvArr* convexhull,CvMemStorage* storage=NULL );  //发现轮廓凸形缺陷

成对几何直方图(PGH):实际上是链码编码直方图(CCH)的一个扩展,CCH是一种直方图,用来统计一个轮廓的Freeman链码编码每一种走法的数字。(Freeman的设计很巧妙,他是对一个多边形的描述,规定8给方向的移动,每个这样的移动有固定的长度和特定的方向,参见page 266)

如图Freeman编码,旋转45度,只需将链码循环平移一个单位即可:


PGH的设计也很好,它规定多边形上的每两个边都有一个夹角、一个最小距离和一个最大距离,并且这些数字被编码到一个二维直方图,无论如何旋转图像,改变图像的大小,直方图都不变,都能得到匹配,即旋转不变性。

如图所示:



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值