OpenCV_轮廓的查找、表达、绘制、特性及匹配

我的新浪微博:http://weibo.com/u/1645794700/home?wvr=5&c=spr_web_360_hao360_weibo_t001

CV机器视觉2013CV机器视觉2013CV机器视觉2013

开始

虽然Canny之类的边缘检测算法可以根据像素间的差异检测出轮廓边界的像素,但是它并没有将轮廓作为一个整体。下一步是要将这些边缘像素组装成轮廓。

轮廓是构成任何一个形状的边界或外形线。直方图对比和模板匹配根据色彩及色彩的分布来进行匹配,以下包括:轮廓的查找、表达方式、组织方式、绘制、特性、匹配。

首先回忆下几个结构体:

首先是图像本身的结构体:

typedef struct CvMat

{

int type; /* CvMat 标识 (CV_MAT_MAGIC_VAL), 元素类型和标记 */

int step; /* 以字节为单位的行数据长度*/

int* refcount; /* 数据引用计数 */

union

{

uchar* ptr;

short* s;

int* i;

float* fl;

double* db;

} data;

union

{

int rows;

int height;

};

union

{

int cols;

int width;

};

这个结构体是最基础的矩阵,而图像本身就是一个复杂的矩阵,所以图像是对这个结构体的继承:

typedef struct _IplImage

{

int nSize; /* IplImage大小 */

int ID; /* 版本 (=0)*/

int nChannels; /* 大多数OPENCV函数支持1,2,3 或 4 个通道 */

int alphaChannel; /* 被OpenCV忽略 */

int depth; /* 像素的位深度: IPL_DEPTH_8U, IPL_DEPTH_8S, IPL_DEPTH_16U,

IPL_DEPTH_16S, IPL_DEPTH_32S, IPL_DEPTH_32F and IPL_DEPTH_64F 可支持 */

char colorModel[4]; /* 被OpenCV忽略 */

char channelSeq[4]; /* 同上 */

int dataOrder; /* 0 - 交叉存取颜色通道, 1 - 分开的颜色通道.

cvCreateImage只能创建交叉存取图像 */

int origin; /* 0 - 顶—左结构,1 - 底—左结构 (Windows bitmaps 风格) */

int align; /* 图像行排列 (4 or 8). OpenCV 忽略它,使用 widthStep 代替 */

int width; /* 图像宽像素数 */

int height; /* 图像高像素数*/

struct _IplROI *roi;/* 图像感兴趣区域. 当该值非空只对该区域进行处理 */

struct _IplImage *maskROI; /* 在 OpenCV中必须置NULL */

void *imageId; /* 同上*/

struct _IplTileInfo *tileInfo; /*同上*/

int imageSize; /* 图像数据大小(在交叉存取格式下imageSize=image->height*image->widthStep),单位字节*/

char *imageData; /* 指向排列的图像数据 */

int widthStep; /* 排列的图像行大小,以字节为单位 */

int BorderMode[4]; /* 边际结束模式, 被OpenCV忽略 */

int BorderConst[4]; /* 同上 */

char *imageDataOrigin; /* 指针指向一个不同的图像数据结构(不是必须排列的),是为了纠正图像内存分配准备的 */

}IplImage;

值得注意的地方:首先是origin这个,当有些图像复制或者视频播放时候,由于原点坐标位置未定,很容造成图片倒置。这时就得用void cvFlip( const CvArr* src, CvArr* dst=NULL, int flip_mode=0)函数或者直接设定origin来改变坐标原点;widthstep就是CvMat的step;

构造方法:IplImage* cvCreateImage( CvSize size, int depth, int channels );

直方图结构:

typedef struct CvHistogram

{

int type;

CvArr* bins;

float thresh[CV_MAX_DIM][2]; /* 对于标准直方图,bins的值有左边界+右边界=2 */

float** thresh2; /* 对于非标准直方图 */

CvMatND mat; /* embedded matrix header for array histograms */

}CvHistogram;

因此,由于直方图的复杂性,得到一个图片的直方图的步骤就不是一个函数完成的:

1,分割图片通道

2,求出bins数量及范围

3,CvHistogram* cvCreateHist( int dims, int* sizes, int type,float** ranges=NULL, int uniform=1 );

创建直方图

4,void cvCalcHist( IplImage** image, CvHistogram* hist,int accumulate=0, const CvArr* mask=NULL );

计算直方图

下面开始轮廓的学习。

查找轮廓

    首先是如何在图像中找到轮廓,可以利用OpenCV提供的方法cvFindContours()可以很方便的查找轮廓。

cvFindContours()方法从二值图像中寻找轮廓。因此此方法处理的图像可以是从cvCanny()函数得到的有边缘像素的图像,或者从cvThreshold()及cvAdaptiveThreshold()得到的图像,这时的边缘是正和负区域之间的边界。

既然在查找之前,我们需要将彩色图像转换成灰度图像,然后再将灰度图像转换成二值图像。代码如下所示:

1  CvSeq *contours = 0;
2  cvCvtColor(src,dst,CV_BGR2GRAY);//将源图像进行灰度化
3  cvThreshold(dst,dst,f_thresh,255,CV_THRESH_BINARY);//二值化阈值 虽然第一个参数是const,但仍可以更改dst
4  cvFindContours(dst,f_storage,&contours); //查找轮廓
5  cvZero(dst);

轮廓的表达方式

使用上面的代码可以得到图像的默认轮廓,但是轮廓在电脑中是如何表达的呢?在OpenCv中提供了两类表达轮廓的方式:顶点的序列、Freeman链码。

首先介绍下内存存储器的概念,这是OpenCV在创建动态对象时存取内存的技术。

CvMemStorage* cvCreateMemStorage( int block_size=0 );//创建默认值大小的内存空间

void cvReleaseMemStorage( CvMemStorage** storage );//释放内存空间

void cvClearMemStorage( CvMemStorage* storage );//清空内存块,可以用于重复使用,将内存返还给存储器,而不是返回给系统

void *cvMemStorageAlloc(CvMemStorage *storage,size_t size);//开辟内存空间

序列

序列是内存存储器中可以存储的一种对象。序列是某种结构的链表。序列在内存中被实现为一个双端队列,因此序列可以实习快速的随机访问,以及快速删除顶端的元素,但是从中间删除元素则稍慢些。

序列结构:

CvSeq

可动态增长元素序列(OpenCV_1.0已发生改变,详见cxtypes.h) Growable sequence of elements

#define CV_SEQUENCE_FIELDS() /

int flags; /* micsellaneous flags */ /

int header_size; /* size of sequence header */ /

struct CvSeq* h_prev; /* previous sequence */ /

struct CvSeq* h_next; /* next sequence */ /

struct CvSeq* v_prev; /* 2nd previous sequence */ /

struct CvSeq* v_next; /* 2nd next sequence */ /

int total; /* total number of elements */ /

int elem_size;/* size of sequence element in bytes */ /

char* block_max;/* maximal bound of the last block */ /

char* ptr; /* current write pointer */ /

int delta_elems; /* how many elements allocated when the sequence grows (sequence granularity) */ /

CvMemStorage* storage; /* where the seq is stored */ /

CvSeqBlock* free_blocks; /* free blocks list */ /

CvSeqBlock* first; /* pointer to the first sequence block */

typedef struct CvSeq

{

CV_SEQUENCE_FIELDS()

} CvSeq;

相关操作就不重复列出(排序,查找,逆序,拆分,复制,读取,写入切片的复制,移除,插入,),可以查找相关文档。

1.顶点的序列

    用多个顶点(或各点间的线段)来表达轮廓。假设要表达一个从(0,0)到(2,2)的矩形,

(1)如果用点来表示,那么依次存储的可能是:(0,0),(1,0),(2,0),(2,1),(2,2),(1,2),(0,2),(0,1);

(2)如果用点间的线段来表达轮廓,那么依次存储的可能是:(0,0),(2,0),(2,2),(0,2)。

2.Freeman链码

    Freeman链码需要一个起点,以及从起点出发的一系列位移。每个位移有8个方向,从0~7分别指向从正北开始的8个方向。假设要用Freeman链码表达从(0,0)到(2,2)的矩形,可能的表示方法是:起点(0,0),方向链2,2,4,4,6,6,0,0。

轮廓之间的组织方式

    在查找到轮廓之后,不同轮廓是怎么组织的呢?根据不同的选择,它们可能是:(1)列表;(2)双层结构;(3)树型结构。

从纵向上来看,列表只有一层,双层结构有一或者两层,树型结构可能有一层或者多层。

如果要遍历所有的轮廓,可以使用递归的方式。

轮廓的绘制

    轮廓的绘制比较简单,用上面提到的方法取得轮廓的所有点,然后把这些点连接成一个多边形即可。

轮廓的一个例子为:OpenCV_轮廓例子

上例中检测出输入图像的轮廓,然后逐个绘制每个轮廓。下个例子为:

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

具体代码为:

 1 #include "stdafx.h"
 2 #include <iostream>
 3 using namespace std;
 4
 5
 6 #ifdef _CH_
 7 #pragma package <opencv>
 8 #endif
 9
10 #include "cv.h"
11 #include "highgui.h"
12
13 using namespace std;
14
15 int _tmain(int argc, _TCHAR* argv[])
16 {
17     cvNamedWindow("flower",1);
18
19     IplImage* img_8uc1 = cvLoadImage("flower.jpg",CV_LOAD_IMAGE_GRAYSCALE);
20     IplImage* img_edge = cvCreateImage(cvGetSize(img_8uc1),8,1);
21     IplImage* img_8uc3 = cvCreateImage(cvGetSize(img_8uc1),8,3);
22
23     cvThreshold(img_8uc1,img_edge,128,255,CV_THRESH_BINARY);
24
25     CvMemStorage* storage = cvCreateMemStorage();
26     CvSeq* first_contour = NULL;
27
28     int Nc = cvFindContours(
29         img_edge,
30         storage,
31         &first_contour,
32         sizeof(CvContour),
33         CV_RETR_LIST
34         );
35
36     int n=0;
37     printf("Total contours detected %d \n",Nc);
38
39     for (CvSeq* c=first_contour;c!=NULL;c=c->h_next)
40     {
41         cvCvtColor(img_8uc1,img_8uc3,CV_GRAY2BGR);
42
43         cvDrawContours(
44             img_8uc3,
45             c,
46             CV_RGB(0,0,255),
47             CV_RGB(0,255,0),
48             0,
49             2,
50             8
51             );
52
53         printf("Contour # %d\n",n);
54         cvShowImage("flower",img_8uc3);
55         printf("%d elements:\n",c->total);
56
57         for (int i=0;i<c->total;++i)
58         {
59             CvPoint* p = CV_GET_SEQ_ELEM(CvPoint,c,i);
60             printf("(%d,%d)\n",p->x,p->y);
61         }
62         cvWaitKey(0);
63         n++;
64     }
65
66     printf("Finished all contours.\n");
67
68     cvCvtColor(img_8uc1,img_8uc3,CV_GRAY2BGR);
69     cvShowImage("flower",img_8uc3);
70
71     cvWaitKey(0);
72
73     cvDestroyWindow("flower");
74     cvReleaseImage(&img_8uc3);
75     cvReleaseImage(&img_8uc1);
76     cvReleaseImage(&img_edge);
77     return 0;
78 }

轮廓的特性

    轮廓的特性有很多,下面一一介绍。

1.轮廓的多边形逼近

    轮廓的多边形逼近指的是:使用多边形来近似表示一个轮廓。

多边形逼近的目的是为了减少轮廓的顶点数目。

多边形逼近的结果依然是一个轮廓,只是这个轮廓相对要粗旷一些。

可以使用方法cvApproxPoly()

2.轮廓的关键点

    轮廓的关键点是:轮廓上包含曲线信息比较多的点。关键点是轮廓顶点的子集。

可以使用cvFindDominantPoints函数来获取轮廓上的关键点,该函数返回的结果一个包含 关键点在轮廓顶点中索引 的序列。再次强调:是索引,不是具体的点。如果要得到关键点的具体坐标,可以用索引到轮廓上去找。

3.轮廓的周长和面积

    轮廓的周长可以用cvContourPerimeter或者cvArcLength函数来获取。

轮廓的面积可以用cvContourArea函数来获取。

4.轮廓的边界框

    有三种常见的边界框:矩形、圆形、椭圆。

(1)矩形:在图像处理系统中提供了一种叫Rectangle的矩形,不过它只能表达边垂直或水平的特例;OpenCv中还有一种叫Box的矩形,它跟数学上的矩形一致,只要4个角是直角即可。

如果要获取轮廓的Rectangle,可以使用cvBoundingRect函数。

如果要获取轮廓的Box,可以使用cvMinAreaRect2函数。

(2)圆形

如果要获取轮廓的圆形边界框,可以使用cvMinEnclosingCircle函数。

(3)椭圆

如果要获取轮廓的椭圆边界框,可以使用cvFitEllipse2函数。

5.轮廓的矩

    矩是通过对轮廓上所有点进行积分运算(或者认为是求和运算)而得到的一个粗略特征。

在连续情况下,图像函数为 f(x,y),那么图像的p+q阶几何矩(标准矩)定义为:

p ,q = 0,1,2……

p+q阶中心距定义为:

 p,q = 0,1,2……

其中代表图像的重心,

,

对于离散的数字图像,采用求和号代替积分:

,,p,q = 0,1,2 ……

N和M分别是图像的高度和宽度;

归一化的中心距定义为:;其中

在公式中,p对应x维度上的矩,q对应y维度上的矩,阶数表示对应的部分的指数。该计算是对轮廓界上所有像素(数目为n)进行求和。如果p和q全部为0,那么m00实际上对应轮廓边界上点的数目。

虽然可以直接计算出轮廓的矩,但是经常会用到归一化的矩(因此不同大小但是形状相同的物体会有相同的值)。同样,简单的矩依赖于所选坐标系,这意味着物体旋转后就无法正确匹配。

于是就产生了Hu矩以及其他归一化矩的函数。

Hu矩是归一化中心矩的线性组合。之所以这样做是为了能够获取代表图像某个特征的矩函数。这些矩函数对缩放,旋转和镜像映射出了(h1)具有不变性。

Hu矩是从中心矩中计算得到。即七个由归一化中心矩组合成的矩:

其中中心矩和归一化中心矩的定义为:

我们可以使用cvContoursMoments函数、cvMoments函数方便的得到轮廓的矩集,然后再相应的方法或函数获取各种矩。

特定的矩:cvGetSpatialMoment函数

中心矩:cvGetCentralMoment函数

归一化中心矩:cvGetNormalizedCentralMoment函数

Hu矩:cvGetHuMoments函数

6.轮廓的轮廓树

    轮廓树用来描述某个特定轮廓的内部特征。注意:轮廓树跟轮廓是一一对应的关系;轮廓树不用于描述多个轮廓之间的层次关系。

轮廓树的创建过程:

从一个轮廓创建一个轮廓树是从底端(叶子节点)到顶端(根节点)的。首先搜索三角形突出或者凹陷的形状的周边(轮廓上的每一个点都不是完全和它的相邻点共线的)每个这样的三角形被一条线段代替,这条线段通过连接非相邻点的两点得到;因此实际上三角形或者被削平或者被填满。每个这样的替换都把轮廓的顶点减少,并且给轮廓树创建一个新节点。如果这样的一个三角形的两侧有原始边,那么她就是得到的轮廓树的叶子;如果一侧已是一个三角形,那么它就是那个三角形的父节点。这个过程的迭代最终把物体的外形简称一个四边形,这个四边形也被剖开;得到的两个三角形是根节点的两个子节点。

结果的二分树最终将原始轮廓的形状性比编码。每个节点被它所对应的三角形的信息所注释。

这样建立的轮廓树并不太鲁棒,因为轮廓上小的改变也可能会彻底改变结果的树,同时最初的三角形是任意选取的。为了得到较好的描述需要首先使用函数cvApproxPoly()之后将轮廓排列(运用循环移动)成最初的三角形不怎么收到旋转影响的状态。

可以用函数cvCreateContourTree来构造轮廓树。

7.轮廓的凸包和凸缺陷

    轮廓的凸包和凸缺陷用于描述物体的外形。凸包和凸缺陷很容易获得,不过我目前不知道它们到底怎么使用。

如果要判断轮廓是否是凸的,可以用cvCheckContourConvexity函数。

如果要获取轮廓的凸包,可以用cvConvexHull2函数,返回的是包含顶点的序列。

如果要获取轮廓的凸缺陷,可以用cvConvexityDefects函数。

8.轮廓的成对几何直方图

    成对几何直方图(pairwise geometrical histogram PGH)是链码编码直方图(chain code histogram CCH)的一个扩展或者延伸。CCH是一种直方图,用来统计一个轮廓的Freeman链码编码每一种走法的数字。这种直方图的一个优良性质为当物体旋转45度,那么新直方图是老直方图的循环平移。这样就可以不受旋转影响。

(1)轮廓保存的是一系列的顶点,轮廓是由一系列线段组成的多边形。对于看起来光滑的轮廓(例如圆),只是线段条数比较多,线段长度比较短而已。实际上,电脑中显示的任何曲线都由线段组成。

(2)每两条线段之间都有一定的关系,包括它们(或者它们的延长线)之间的夹角,两条线段的夹角范围是:(0,180)。

(3)每两条线段上的点之间还有距离关系,包括最短(小)距离、最远(大)距离,以及平均距离。最大距离我用了一个偷懒的计算方法,我把轮廓外界矩形的对角线长度看作了最大距离。

(4)成对几何直方图所用的统计数据包括了夹角和距离。

轮廓的匹配

如果要比较两个物体,可供选择的特征很多。如果要判断某个人的性别,可以根据他(她)头发的长短来判断,这很直观,在长发男稀有的年代准确率也很高。也可以根据这个人尿尿的射程来判断,如果射程大于0.50米,则是男性。总之,方法很多,不一而足。

我们在上文中得到了轮廓的这么多特征,它们也可以用于进行匹配。典型的轮廓匹配方法有:Hu矩匹配、轮廓树匹配、成对几何直方图匹配。

1.Hu矩匹配

轮廓的Hu矩对包括缩放、旋转和镜像映射在内的变化具有不变性。cvMatchShapes函数可以很方便的实现对2个轮廓间的匹配。

2.轮廓树匹配

用树的形式比较两个轮廓。cvMatchContourTrees函数实现了轮廓树的对比。

3.成对几何直方图匹配

    在得到轮廓的成对几何直方图之后,可以使用直方图对比的方法来进行匹配。

******Hu矩匹配********************************************/
//    IplImage* img_8uc1 = cvLoadImage("flower.jpg",CV_LOAD_IMAGE_GRAYSCALE);
//    IplImage* img_edge1 = cvCreateImage(cvGetSize(img_8uc1),8,1);
////    IplImage* img_8uc3 = cvCreateImage(cvGetSize(img_8uc1),8,3);
//
//    cvThreshold(img_8uc1,img_edge1,128,255,CV_THRESH_BINARY);
//
//
//    CvMemStorage* storage1 = cvCreateMemStorage();
//    CvSeq* first_contour1 = NULL;
//
//    int Nc = cvFindContours(
//        img_edge1,
//        storage1,
//        &first_contour1,
//        sizeof(CvContour),
//        CV_RETR_LIST
//        );
//
//    IplImage* img_8uc12 = cvLoadImage("flower1.jpg",CV_LOAD_IMAGE_GRAYSCALE);
//    IplImage* img_edge12 = cvCreateImage(cvGetSize(img_8uc12),8,1);
////    IplImage* img_8uc3 = cvCreateImage(cvGetSize(img_8uc1),8,3);
//
//    cvThreshold(img_8uc12,img_edge12,128,255,CV_THRESH_BINARY);
//
//
//    CvMemStorage* storage2 = cvCreateMemStorage();
//    CvSeq* first_contour2 = NULL;
//
//    int Nc2 = cvFindContours(
//        img_edge12,
//        storage2,
//        &first_contour2,
//        sizeof(CvContour),
//        CV_RETR_LIST
//        );
//
//    double n = cvMatchShapes(first_contour1,first_contour2,CV_CONTOURS_MATCH_I1,0);
//
//    printf("%d",n);
//
//    cvWaitKey();

/***************************轮廓树匹配***********************************************/
    //    IplImage* img_8uc1 = cvLoadImage("flower.jpg",CV_LOAD_IMAGE_GRAYSCALE);
    //    IplImage* img_edge1 = cvCreateImage(cvGetSize(img_8uc1),8,1);
    ////    IplImage* img_8uc3 = cvCreateImage(cvGetSize(img_8uc1),8,3);
    //
    //    cvThreshold(img_8uc1,img_edge1,128,255,CV_THRESH_BINARY);
    //
    //
    //    CvMemStorage* storage1 = cvCreateMemStorage();
    //    CvSeq* first_contour1 = NULL;
    //
    //    int Nc = cvFindContours(
    //        img_edge1,
    //        storage1,
    //        &first_contour1,
    //        sizeof(CvContour),
    //        CV_RETR_LIST
    //        );

    //    CvContourTree* tree1 = cvCreateContourTree(
    //        first_contour1,
    //        storage1,
    //        200
    //        );
    //
    //    IplImage* img_8uc12 = cvLoadImage("flower1.jpg",CV_LOAD_IMAGE_GRAYSCALE);
    //    IplImage* img_edge12 = cvCreateImage(cvGetSize(img_8uc12),8,1);
    ////    IplImage* img_8uc3 = cvCreateImage(cvGetSize(img_8uc1),8,3);
    //
    //    cvThreshold(img_8uc12,img_edge12,128,255,CV_THRESH_BINARY);
    //
    //
    //    CvMemStorage* storage2 = cvCreateMemStorage();
    //    CvSeq* first_contour2 = NULL;
    //
    //    int Nc2 = cvFindContours(
    //        img_edge12,
    //        storage2,
    //        &first_contour2,
    //        sizeof(CvContour),
    //        CV_RETR_LIST
    //        );
    //    CvContourTree* tree2 = cvCreateContourTree(
    //        first_contour2,
    //        storage2,
    //        200
    //        );
    //    double n = cvMatchContourTrees(tree1,tree1,CV_CONTOURS_MATCH_I1,200);
    //
    //    printf("%d",n);
    //
    //    cvWaitKey();

下面为成对几何直方图匹配方法

#include "gesrec.h"
#include <stdio.h>//////////////////////////////////////////

#define PI 3.14159f

//轮廓面积比较函数
static int gesContourCompFunc(const void* _a, const void* _b, void* userdata)
{
    int retval;
    double s1, s2;
    CvContour* a = (CvContour*)_a;
    CvContour* b = (CvContour*)_b;

    s1 = fabs(cvContourArea(a));
    s2 = fabs(cvContourArea(b));
    //s1 = a->rect.height * a->rect.width;
    //s2 = b->rect.height * b->rect.width;

    if(s1 < s2)
    {
        retval = 1;
    }
    else if(s1 == s2)
    {
        retval = 0;
    }
    else
    {
        retval = -1;
    }

    return retval;
}

//src:BGR dst:
void gesFindContours(IplImage* src, IplImage* dst, CvSeq** templateContour, CvMemStorage* templateStorage, int flag)
{
    int count;//轮廓数
    IplImage* gray;
    CvMemStorage* first_sto;
    CvMemStorage* all_sto;
    CvSeq* first_cont;
    CvSeq* all_cont;
    CvSeq* cur_cont;

    //初始化动态内存
    first_sto = cvCreateMemStorage(0);
    first_cont = cvCreateSeq(CV_SEQ_ELTYPE_POINT, sizeof(CvSeq), sizeof(CvPoint), first_sto);
    all_sto = cvCreateMemStorage(0);
    all_cont = cvCreateSeq(0, sizeof(CvSeq), sizeof(CvSeq), all_sto);

    //创建源图像对应的灰度图像
    gray = cvCreateImage(cvGetSize(src), IPL_DEPTH_8U, 1);
    cvCvtColor(src, gray, CV_BGR2GRAY);

    //得到图像的外层轮廓
    count = cvFindContours(gray, first_sto, &first_cont, sizeof(CvContour), CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);

    //如果没有检测到轮廓则返回
    if(first_sto == NULL)
    {
        return;
    }

    //将所有的轮廓都放到first_cont中
    for(;first_cont != 0;first_cont = first_cont->h_next)
    {
        if(((CvContour* )first_cont)->rect.height * ((CvContour* )first_cont)->rect.width >= 625)
            cvSeqPush(all_cont, first_cont);
    }

    //对轮廓按照面积进行排序
    cvSeqSort(all_cont, gesContourCompFunc, 0);

    //在dst中画出轮廓
    cvZero(dst);
    for(int i = 0;i < min(all_cont->total, 3);i++)///////////////////////次数待改
    {
        cur_cont = (CvSeq* )cvGetSeqElem(all_cont, i);
        if(flag != 0 && i == 0)
        {
            *templateContour = cvCloneSeq(cur_cont, templateStorage);
        }

        CvScalar color = CV_RGB(rand()&255, rand()&255, rand()&255);
        cvDrawContours(dst, (CvSeq* )cur_cont, color, color, -1, 1, 8);
    }

    //判断原点位置以确定是否需要反转图像
    if(src->origin == 1)
    {
        cvFlip(dst);
    }

    //释放内存
    cvReleaseMemStorage(&first_sto);
    cvReleaseMemStorage(&all_sto);
    cvReleaseImage(&gray);
}

void gesMatchContoursTemplate(IplImage* src, IplImage* dst, CvSeq** templateContour)
{
    CvSeq* contour;
    CvMemStorage* storage;

    //初始化动态内存
    storage = cvCreateMemStorage(0);
    contour = cvCreateSeq(CV_SEQ_ELTYPE_POINT, sizeof(CvSeq), sizeof(CvPoint), storage);

    //得到轮廓并进行匹配
    gesFindContours(src, dst, &contour, storage, 1);
    if(contour->total != 0)//如果得到的轮廓不为空
    {
        double result = cvMatchShapes((CvContour* )contour, (CvContour* )(*templateContour), CV_CONTOURS_MATCH_I3);
        printf("%.2f\n", result);/////////////////////////////////////////////
    }

    //释放内存
    cvReleaseMemStorage(&storage);
}

//模版匹配法的完整实现
int gesMatchContoursTemplate2(IplImage* src, IplImage* dst, CvSeq* templateContour)
{
    CvSeq* contour;
    CvSeq* cur_cont;
    CvMemStorage* storage;
    double minValue, tempValue;
    int i, minIndex;

    //初始化动态内存
    storage = cvCreateMemStorage(0);
    contour = cvCreateSeq(CV_SEQ_ELTYPE_POINT, sizeof(CvSeq), sizeof(CvPoint), storage);

    //得到轮廓并进行匹配
    minIndex = -1;
    gesFindContours(src, dst, &contour, storage, 1);
    if(contour->total != 0)//如果得到的轮廓不为空
    {
        if(templateContour->total != 0)
        {
            cur_cont = (CvSeq* )cvGetSeqElem(templateContour, 0);
            minValue = cvMatchShapes((CvContour* )contour, (CvContour* )cur_cont, CV_CONTOURS_MATCH_I3);
            minIndex = 0;
            printf("0:%.2f\n", minValue);
        }

        for(i = 1;i < templateContour->total;i++)
        {
            cur_cont = (CvSeq* )cvGetSeqElem(templateContour, i);
            tempValue = cvMatchShapes((CvContour* )contour, (CvContour* )cur_cont, CV_CONTOURS_MATCH_I3);
            if(tempValue < minValue)
            {
                minValue = tempValue;
                minIndex = i;
            }
            printf("%d:%.2f\n", i, tempValue);
        }

        if(minValue >= 0.3)
        {
            minIndex = -1;
        }
    }

    //打印匹配结果
    printf("the result is %d\n", minIndex);

    //释放内存
    cvReleaseMemStorage(&storage);

    return minIndex;
}

//找出轮廓最大的5个极大值点
void gesFindContourMaxs(CvSeq* contour)
{
    int i;
    CvScalar center;//重心位置
    CvPoint* p;
    CvMat max;//存储5个极大值的数组
    double initMax[] = {-1, -1, -1, -1, -1};//初始极大值设置为-1
    double minValue, maxValue;//5个极大值中的最大值与最小值
    CvPoint minLoc;//最小值的位置
    double preDistance = 0;
    bool isCandidate = false;//是否是候选的极大值点

    //初始化重心位置
    center = cvScalarAll(0);

    //初始化极大值矩阵
    max = cvMat(1, 5, CV_64FC1, initMax);

    //首先求出轮廓的重心
    for(i = 0;i < contour->total;i++)
    {
        p = (CvPoint* )cvGetSeqElem(contour, i);
        center.val[0] += p->x;
        center.val[1] += p->y;
    }
    center.val[0] /= contour->total;
    center.val[1] /= contour->total;

    //遍历轮廓,找出所有的极大值点
    for(i = 0;i < contour->total;i++)
    {
        p = (CvPoint* )cvGetSeqElem(contour, i);
        double distance = sqrt(pow(center.val[0] - p->x, 2) + pow(center.val[1] - p->y, 2));

        if(distance > preDistance)
        {
            isCandidate = true;
        }
        else if(distance < preDistance && isCandidate == true)
        {
            cvMinMaxLoc(&max, &minValue, &maxValue, &minLoc);

            if(distance > minValue)
            {
                cvmSet(&max, minLoc.y, minLoc.x, distance);
            }
            isCandidate = false;
        }
        else
        {
            isCandidate = false;
        }

        preDistance = distance;
    }

    //打印5个极大值
    printf("%.2f %.2f %.2f %.2f %.2f\n", cvmGet(&max, 0, 0), cvmGet(&max, 0, 1), cvmGet(&max, 0, 2), cvmGet(&max, 0, 3), cvmGet(&max, 0, 4));
}

//计算轮廓的pair-wise几何直方图
CvHistogram* gesCalcContoursPGH(CvSeq* contour)
{
    CvHistogram* hist;//成对几何直方图
    CvContour* tempCont;

    //得到成对几何直方图第二个维度上的范围
    tempCont = (CvContour* )contour;
    cvBoundingRect(tempCont, 1);

    int sizes[2] = {60, 200};
    float ranges[2][2] = {{0,PI}, {0,200}};
    float** rangesPtr = new float* [2];
    rangesPtr[0] = ranges[0];
    rangesPtr[1] = ranges[1];

    //初始化几何直方图
    hist = cvCreateHist(2, sizes, CV_HIST_ARRAY, rangesPtr, 1);

    //计算轮廓的成对几何直方图
    cvCalcPGH(contour, hist);

    return hist;
}

//对轮廓的pair-wise几何直方图进行匹配
void gesMatchContoursPGH(CvSeq* contour, CvHistogram* templateHist)
{
    CvHistogram* hist;

    //得到轮廓的成对几何直方图
    hist = gesCalcContoursPGH(contour);

    //归一化直方图
    cvNormalizeHist(templateHist, 1);
    cvNormalizeHist(hist, 1);

    //直方图匹配
    double result = cvCompareHist(hist, templateHist, CV_COMP_INTERSECT);
    printf("result:%.2f\n", result);

    //释放内存
    cvReleaseHist(&hist);
}
时间: 2024-10-02 10:46:33

OpenCV_轮廓的查找、表达、绘制、特性及匹配的相关文章

轮廓的查找、表达、绘制、特性及匹配(How to Use Contour? Find, Component, Construct, Features &amp; Match)

http://www.cnblogs.com/xrwang/archive/2010/02/09/HowToUseContour.html 作者:王先荣 前言    轮廓是构成任何一个形状的边界或外形线.前面讲了如何根据色彩及色彩的分布(直方图对比和模板匹配)来进行匹配,现在我们来看看如何利用物体的轮廓.包括以下内容:轮廓的查找.表达方式.组织方式.绘制.特性.匹配. 查找轮廓    首先我们面对的问题是如何在图像中找到轮廓,OpenCv(EmguCv)为我们做了很多工作,我们的任务只是调用现成

25、【opencv入门】轮廓查找与绘制(3)——凸包

一简介 1.凸包 凸包(Convex Hull)是一个计算机几何图形学中的概念, 简单来说, 给定二维平面点集, 凸包就是能够将最外层的点连接起来构成的凸多边形, 它能够包含点集中所有的点.物体的凸包检测场应用在物体识别.手势识别及边界检测等领域. A-H是被标出的凸包缺陷 2.寻找凸包---convexHull() 1 CV_EXPORTS_W void convexHull(InputArray points, OutputArray hull, bool clockwise=false,

查找并绘制轮廓

1 寻找轮廓:findContours()函数 void findContours(InputOutputArray image,OutputArrayOfArrays contours,OutputArray hierarchy,int mode,int method,Point offset=Point()) 2 绘制轮廓:drawContours()函数 void drawContours(InputOutputArray image,InoutArrayOfArrays contours

3.05 在一个表中查找与其他表不匹配的记录

问题:对于具有相同关键字的两个表,要在一个表中查找与另外一个不匹配的行.例如,要查找没有职员的部门. 要查找部门中每个员工的工作岗位需要在表需要在表deptno及emp中有一个等值联接.deptno列就是这两个表之间的公共值.但是,等值联接却不能直接显示出那个部门没有员工.这是因为在表emp和dept正在等值联接时,将会返回满足联接条件的所有行.可是我们只需要那些在表dept中不满足联接条件的行. 尽管乍看起来这个问题同前一个问题类似,但是要更复杂一些.其不同之处就是在前一个问题中,需要列出在表

opencv学习之路(25)、轮廓查找与绘制(四)——正外接矩形

一.简介 二.外接矩形的查找绘制 1 #include "opencv2/opencv.hpp" 2 using namespace cv; 3 void main() 4 { 5 //外接矩形的查找绘制 6 Mat srcImg =imread("E://12.jpg"); 7 imshow("src",srcImg); 8 Mat dstImg = srcImg.clone(); //原图备份 9 cvtColor(srcImg, srcIm

opencv学习之路(24)、轮廓查找与绘制(三)——凸包

一.简介 二.绘制点集的凸包 1 #include<opencv2/opencv.hpp> 2 using namespace cv; 3 4 void main() 5 { 6 //---绘制点集的凸包 7 Mat img(400, 400, CV_8UC3, Scalar::all(0)); //定义绘制图像 8 RNG rng; //定义随机数对象 9 while(1) 10 { 11 char key; 12 int count = (unsigned int)rng % 100; /

opencv学习之路(27)、轮廓查找与绘制(六)——外接圆

一.最小外接圆 1 #include "opencv2/opencv.hpp" 2 #include<iostream> 3 using namespace std; 4 using namespace cv; 5 6 void main() { 7 Mat srcImg = imread("E://10.png"); 8 imshow("src", srcImg); 9 Mat dstImg = srcImg.clone(); 10

26、【opencv入门】轮廓查找与绘制(4)——正外接矩形

一.简介 1.使用特定形状的轮廓包围 在实际应用中, 经常会有将检测到的轮廓用多边形表示出来的需求, 提取包围轮廓的多边形也方便我们做进一步分析, 轮廓包围主要有一下几种: 轮廓外接矩形.轮廓最小外接矩形(旋转).轮廓最小包围圆形.轮廓拟合椭圆.轮廓逼近多边形曲线 2.轮廓外接矩形 --- boundingRect() 1 CV_EXPORTS_W Rect boundingRect(InputArray points); points: 输入的二维点集, 可以填Mat类型或std::vecto

opencv学习之路(26)、轮廓查找与绘制(五)——最小外接矩形

一.简介 二.轮廓最小外接矩形的绘制 1 #include "opencv2/opencv.hpp" 2 using namespace cv; 3 4 void main() 5 { 6 //轮廓最小外接矩形的绘制 7 Mat srcImg = imread("E://00.png"); 8 Mat dstImg = srcImg.clone(); 9 cvtColor(srcImg, srcImg, CV_BGR2GRAY); 10 threshold(srcI