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;
}
结果:
输入:
二值化:
轮廓:
二值化图像的效果不是很好,这个rand()&255, rand()&255, rand()&255???还不知道是什么意思,详细有待学习后再更新。