opencv序列结构CvSeq和轮廓提取cvFindContours的简单运用

时间:2021-01-09 22:07:02

1)OpenCV CvSeq的内部结构探讨

  对于CvSeq这一结构体,又称为可动态增长元素序列(OpenCV_1.0已发生改变,详见cxtypes.h) Growable sequence of elements。 
  CvSeq定义复杂,首先,定义CV_SEQUENCE_FIELDS()。 
  #define CV_SEQUENCE_FIELDS() \ 

typedef struct CvSeq

{

  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 */ 
}

而CvSeq可以表达成: 
  typedef struct CvSeq 
  { 
  CV_SEQUENCE_FIELDS() 
  } CvSeq


2)cvCreateSeq:创建一序列 

CvSeq* cvCreateSeq(int seq_flags,int header_size,int elem_size,CvMemStorage* storage) 
  说明:CvSeq本身就是一个可增长的序列,不是固定的序列 
  参数:seq_flags为序列的符号标志。如果序列不会被传递给任何使用特定序列的函数,那么将它设为0,否则从预定义的序列类型中选择一合适的类型。 Header_size为序列头部的大小;必须大于或等于sizeof(CvSeq)。如果制定了类型或它的扩展名,则此类型必须适合基类的头部大小。 Elem_size为元素的大小,以 字节计。这个大小必须与序列类型(由seq_flags指定)相一致。例如,对于一个点的序列,元素类型 CV_SEQ_ELTYPE_POINT应当被指定,参数elem_size必须等同于sizeof(CvPoint)。Storage为指向前面定义的内存存储器。


3)一些序列函数集合

CvSeq* cvCloneSeq(const CvSeq* seq,CvMemStorage* storage=NULL) 创建序列的一份拷贝


Void cvSeqInvert(CvSeq* seq) 将序列中的元素进行逆序操作


Void cvSeqSort(CvSeq* seq,CvCmpFunc func,

void *userdata=NULL) 使用特定的比较函数对序列中的元素进行排序
 
Char* cvSeqSearch(CvSeq* seq,const void* elem,CvCmpFunc func,int is_sorted,

int *elem_idx,void *userdata=NULL) 查询序列中的元素


Void cvClearSeq(CvSeq* seq); 清空序列


Char* cvSeqPush(CvSeq* seq,void* element=NULL) 添加元素到序列的尾部


void cvSeqPop(CvSeq* seq,void* element=NULL)删除序列尾部元素
 
Char* cvSeqPushFront(CvSeq* seq,void* element=NULL) 在序列头部添加元素
 
Void cvSeqPopFront(CvSeq* seq,void* element=NULL)  删除在序列的头部的元素
 
Void cvSeqPushMulti(CvSeq* seq,void* elements,

int count,int in_front=0);添加多个元素到序列尾部或头部
 
Void cvSeqPopMulti(CvSeq* seq,void* elements,

int count,int in_front=0) 删除多个序列头部或尾部元素
 
Char* cvSeqInsert(CvSeq* seq,int before_index,

void* element=NULL)在序列中的指定位置添加元素
 
Void cvSeqRemove(CvSeq* seq,int index)  删除序列中的指定位置的元素
 
Char* cvGetSeqElem(const CvSeq* seq,int index) 返回索引所指定的元素指针
 
Int cvSeqElemIdx(const CvSeq* seq,const void* element,

CvSeqBlock** block=NULL)返回序列中元素的索引
 
Void cvStartAppendToSeq(CvSeq* seq,CvSeqWriter* writer) 将数据写入序列中,并初始化该过程
 
Void cvStartWriteSeq(int seq_flags,int header_size,int elem_size,

CvMemStorage* storage,CvSeqWriter* writer)创建新序列,并初始化写入部分
 
CvSeq* cvEndWriteSeq(CvSeqWriter* writer) 完成写入操作
 
Void cvStartReadSeq(const CvSeq* seq,CvSeqReader* reader,

 int reverse=0)初始化序列中的读取过程
 

4)cvFindContours:返回检测到的轮廓的个数

函数cvFindContours从二值图像中检索轮廓,并返回检测到的轮廓的个数。first_contour的值由函数填充返回,它的值将为第一个外轮廓的指针,当没有轮廓被检测到时为NULL。其它轮廓可以使用h_next和v_next连接,从first_contour到达。

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像素保存不变。从一个灰度图像得到二值图像的函数有:cvThreshold,cvAdaptiveThreshold和cvCanny。

storage
返回轮廓的容器。

first_contour
输出参数,用于存储指向第一个外接轮廓。

header_size
header序列的尺寸。就两种情况:

如果选择method = CV_CHAIN_CODE, 则header_size >= sizeof(CvChain);
其他,则header_size >=sizeof(CvContour)。

mode
检索模式,可取值如下:
CV_RETR_EXTERNAL:只检索最外面的轮廓;

CV_RETR_LIST:检索所有的轮廓,并将其放入list中;

CV_RETR_CCOMP:检索所有的轮廓,并将他们组织为两层:顶层是各部分的外部边界,第二层是空洞的边界;

CV_RETR_TREE:检索所有的轮廓,并重构嵌套轮廓的整个层次。

蓝色表示v_next,绿色表示h_next
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:使用the flavors of Teh-Chin chain近似算法的一种。

CV_LINK_RUNS:通过连接水平段的1,使用完全不同的边缘提取算法。使用CV_RETR_LIST检索模式能使用此方法。

offset
偏移量,用于移动所有轮廓点。当轮廓是从图像的ROI提取的,并且需要在整个图像中分析时,这个参数将很有用。

讨论部分cvDrawContours中的案例显示了任何使用轮廓检测连通区域。轮廓可以用于形状分析和目标识别——可以参考文件夹OpenCV sample中的squares.c

5)cvDrawContours:在图像上绘制外部和内部轮廓
函数cvDrawContours用于在图像上绘制外部和内部轮廓。当thickness >= 0 时,绘制轮廓线;否则填充由轮廓包围的部分。

void cvDrawContours( CvArr *img, CvSeq* contour,CvScalar external_color, CvScalar hole_color,
int max_level, int thickness=1,int line_type=8, CvPoint offset=cvPoint(0,0) );

img
要在其上绘制轮廓的图像。和在其他绘图函数里一样,轮廓是ROI的修剪结果。

contour
指向第一个轮廓的指针。

external_color
外轮廓的颜色。

hole_color
内轮廓的颜色。

max_level
画轮廓的最大层数。如果是0,只绘制contour;如果是1,将绘制contour后和contour同层的所有轮廓;如果是2,绘制contour后所有同层和低一层的轮廓,以此类推;如果值是负值,则函数并不绘制contour后的轮廓,但是将画出其子轮廓,一直到abs(max_level) - 1层。

thickness
绘制轮廓线的宽度。如果为负值(例如,等于CV_FILLED),则contour内部将被绘制。

line_type
轮廓线段的类型,具体查看cvLine的描述。

offset
按给定值移动所有点的坐标。

6)cvBoundingRect:返回二维点集的最外面 (up-right)矩形边界

CvRect cvBoundingRect( CvArr* points, int update=0 );

points:
二维点集,点的序列或向量 (CvMat)

update:
更新标识。

下面是轮廓类型和标识的一些可能组合:

update=0, contour ~ CvContour*: 不计算矩形边界,但直接由轮廓头的 rect 域得到。

update=1, contour ~ CvContour*: 计算矩形边界,而且将结果写入到轮廓头的 rect 域中 header。

update=0, contour ~ CvSeq* or CvMat*: 计算并返回边界矩形。

update=1, contour ~ CvSeq* or CvMat*: 产生运行错误 (runtime error is raised)。

函数 cvBoundingRect 返回二维点集的最外面 (up-right)矩形边界。

7) cvRectangle:通过对角线上的两个顶点绘制简单、指定粗细或者带填充的矩形,是个绘图函数

函数原型:void cvRectangle( CvArr* img, CvPoint pt1, CvPoint pt2, CvScalar color,int thickness=1, int line_type=8, int shift=0 );

参数介绍:
img -- 图像.
pt1 -- 矩形的一个顶点。
pt2 -- 矩形对角线上的另一个顶点
color -- 线条颜色 (RGB) 或亮度(灰度图像 )(grayscale image)。
thickness -- 组成矩形的线条的粗细程度。取负值时(如 CV_FILLED)函数绘制填充了色彩的矩形。
line_type -- 线条的类型。见cvLine的描述
shift -- 坐标点的小数点位数。


代码:

#include <stdio.h>  
#include <cv.h>
#include <highgui.h>  
#include <math.h>  
int main()  
{  
IplImage *src = cvLoadImage(".\\a4.jpg", 0);  
IplImage *dsw = cvCreateImage(cvGetSize(src), 8, 1);  
IplImage *dst = cvCreateImage(cvGetSize(src), 8, 3);  
CvMemStorage *storage = cvCreateMemStorage(0);  
CvSeq *first_contour = NULL;  

//turn the src image to a binary image  
//cvThreshold(src, dsw, 125, 255, CV_THRESH_BINARY_INV); 
//二值化源图像
cvThreshold(src, dsw, 100, 255, CV_THRESH_BINARY);  

cvFindContours(dsw, storage, &first_contour, sizeof(CvContour), CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE);  
cvZero(dst);  
int cnt = 0;  
for(; first_contour != 0; first_contour = first_contour->h_next)  
{  
cnt++;  
//外内轮廓的颜色
CvScalar color = CV_RGB(rand()&255, rand()&255, rand()&255);  //rand()&255, rand()&255, rand()&255???
//#define CV_FILLED -1  如果为负值(例如,等于CV_FILLED),则contour内部将被绘制。
cvDrawContours(dst, first_contour, color, color, 0, 2, CV_FILLED, cvPoint(0, 0));  
//返回二维点集的最外面 (up-right)矩形边界
CvRect rect = cvBoundingRect(first_contour,0);
//cvRectangle是绘图函数,绘制矩形
cvRectangle(dst, cvPoint(rect.x, rect.y), cvPoint(rect.x + rect.width, rect.y + rect.height),CV_RGB(255, 0, 0), 1, 8, 0);
}  
printf("the num of contours : %d\n", cnt);  
cvNamedWindow( "Source", 1 );  
cvShowImage( "Source", src ); 
cvSaveImage("source.jpg",src);
cvNamedWindow( "dsw", 1 );  
cvShowImage( "dsw", dsw );  
cvSaveImage("dsw.jpg",dsw);
cvNamedWindow( "Components", 1 );  
cvShowImage( "Components", dst ); 
cvSaveImage("components.jpg",dst);
cvReleaseMemStorage(&storage);  
cvWaitKey(-1);  
return 0;  
}  

结果:

输入:opencv序列结构CvSeq和轮廓提取cvFindContours的简单运用

二值化:opencv序列结构CvSeq和轮廓提取cvFindContours的简单运用

轮廓:opencv序列结构CvSeq和轮廓提取cvFindContours的简单运用

二值化图像的效果不是很好,这个rand()&255, rand()&255, rand()&255???还不知道是什么意思,详细有待学习后再更新。