opencv学习笔记 第三篇 轮廓检测的学习
虽然Canny之类的边缘检测可以根据像素之间的差异测出轮廓边界像素,但没有作为一个整体,cvFindContours()可以将边缘像素组装成轮廓,我们循序渐进,先学习内存存储器memory strorage 和序列sequence,继而深入讨论轮廓检测
内存
Opencv使内存存储器来统一管理各种动态对象的内存,在底层被实现为一个有许多相同大小的内存块组成的双向链表,基于内存存储器实现的函数,需要先先向内存存储器申请内存空间
函数说明:CvMemStorage *storage=cvCreateMemStorage(block_size);
用来创建一个内存存储器,来统一管理各种动态对象的内存。
函数返回一个新创建的内存存储器指针。
参数block_size对应内存器中每个内存块的大小,为0时内存块默认大小为64k。
序列
序列是内存储器中可以存储的一种对象,是魔种结构的链表,在内存中被实现为一个双端队列
函数说明:CvSeq* cvCreateSeq(int seq_flags,int header_size,int elem_size,CvMemStorage* storage)
功能:创建一序列
说明:CvSeq本身就是一个可增长的序列,不是固定的序列
参数:
(1)seq_flags为序列的符号标志。如果序列不会被传递给任何使用特定序列的函数,那么将它设为0,否则从预定义的序列类型中选择一合适的类型。
(2)Header_size为序列头部的大小;必须大于或等于sizeof(CvSeq)。如果制定了类型或它的扩展名,则此类型必须适合基类的头部大小。
(3)Elem_size为元素的大小,以字节计。这个大小必须与序列类型(由seq_flags指定)相一致。例如,对于一个点的序列,元素类型 CV_SEQ_ELTYPE_POINT应当被指定,参数elem_size必须等同于sizeof(CvPoint)。
(4)Storage为指向前面定义的内存存储器.
查找轮廓
函数介绍cvFindContours
函数功能:对图像进行轮廓检测,这个函数将生成一条链表以保存检测出的各个轮廓信息,并传出指向这条链表表头的指针。返回值是找到的所有轮廓的个数,
调用cvFindContours时,会返回多个序列,序列的类型取决于传递的参数,默认是CV_RETR_LIST和CV_CHAIN_APPROX_SIMPLE参数,
函数原型:
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)
);
函数说明:
第一个参数表示输入图像,必须为一个8位的二值图像。应该被转化为二值图,如果是将来还有用的的图像,应该复制之后再传给这个函数
第二参数表示存储轮廓的容器。为CvMemStorage类型
第三个参数为输出参数,这个参数将指向用来存储轮廓信息的链表表头。
第四个参数表示存储轮廓链表的表头大小,当第六个参数传入CV_CHAIN_CODE时,要设置成sizeof(CvChain),其它情况统一设置成sizeof(CvContour)。
第五个参数为轮廓检测的模式:
CV_RETR_EXTERNAL:只检索最外面的轮廓;
CV_RETR_LIST:检索所有的轮廓,并将其保存到一条链表当中;
CV_RETR_CCOMP:检索所有的轮廓,并将他们组织为两层:顶层是各部分的外边界,第二层是孔的内边界;CV_RETR_TREE:检索所有的轮廓并且重新建立网状的轮廓结构,子节点和父节点被垂直连接起来步骤一直持续到图像最内层
第六个参数用来表示轮廓边缘的方法:
CV_CHAIN_CODE:以Freeman链码的方式输出轮廓,所有其他方法输出多边形(顶点的序列)。
CV_CHAIN_APPROX_SIMPLE:压缩水平的、垂直的和斜的部分,只保留最后的一个点。
第七个参数表示偏移量,比如你要从图像的(100, 0)开始进行轮廓检测,那么就传入(100, 0)。
使用序列表示轮廓
序列中保存一系列的点,点构成轮廓,需要注意的轮廓只是序列能表示物体的一种,轮廓是点的序列,可以用来表示图像空间中的曲线,另外,CvContour结构不同于CvSeq的地方是,它还包含颜色、外包矩阵区域等
Freeman链码
一般情况下,cvFindContours获取的轮廓是一系列顶点的序列,两一种不同的表达是设置method参数为CV_CHAIN_CODE,然后生成轮廓,在Freeman链码中,多边形被表示为一系列的位移,每一个位移都有8个方向,这8个方向使用整数0到7表示,Freeman链码对识别一些形状的物体很有帮助,可以通过一下两个函数读出每个点:
void cvStartReadChainPoints( CvChain* chain, CvChainPtReader* reader );
用来初始化Freeman链码CvChainPtReader结构,CvChain是从CvSeq扩展得来的
CvPoint cvReadChainPoint( CvChainPtReader* reader );
这个函数通过CvChainPtReader来读每个点
绘制轮廓
函数介绍cvDrawContours
函数功能:在图像上绘制检测到的轮廓
函数原型:
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)
);
第一个参数表示输入图像,函数将在这张图像上绘制轮廓。
第二个参数表示指向轮廓链表的指针,表示要绘制的轮廓
第三个参数表示绘制外轮廓的颜色,external
第三个参数表示绘制内轮廓的颜色,hole
第五个参数表示绘制轮廓的最大层数,如果是0,表示与输入轮廓属于同一等级的所有轮廓,可以理解为输入轮廓呵与其相邻的轮廓;
如果是1,绘制和contour同层的所有轮廓与其子节点;
如果是2,追加绘制比contour低一层的轮廓,以此类推;如果值是负值,则函数并不绘制contour后的轮廓,但是将画出其子轮廓,一直到abs(max_level) - 1层。
第六个参数表示轮廓线的宽度,如果为CV_FILLED则会填充轮廓内部。
第七个参数表示轮廓线的类型。
第八个参数表示偏移量,如果传入(10,20),那绘制将从图像的(10,20)处开始。
程序例子
include <cv.h>
include <highgui.h>
IplImage* image = NULL;
IplImage* gray = NULL;
int thresh = 100;
CvMemStorage* storage = NULL;
void on_trackbar(int)
{
if (storage == NULL)
{
gray = cvCreateImage(cvGetSize(image),8,1);
storage = cvCreateMemStorage(0);
}
else
{
cvClearMemStorage(storage);
}
CvSeq* contours = NULL;
cvCvtColor(image,gray,CV_BGR2GRAY);
cvThreshold(gray,gray,thresh,255,CV_THRESH_BINARY);
cvFindContours(gray,storage,&contours);
cvZero(gray); //是让矩阵的值都为0,有初始化的作用,或者说清零~
if (contours)
{
cvDrawContours(gray,contours,cvScalarAll(255),cvScalarAll(255),100);
}
cvShowImage("Contours",gray);
}
int main(int argc, char* argv[]) //argc: 整数,用来统计你运行程序时送给main函数的命令行参数的个数 * argv[ ]: 字符串数组,用来存放指向你的字符串参数的指针数组,每一个元素指向一个参数
{
image = cvLoadImage("dog.jpg");
cvNamedWindow("Contours",0);
cvCreateTrackbar("Threshold","Contours",&thresh,300,on_trackbar);
on_trackbar(10);
cvWaitKey(0);
return 0;
}
程序结果
注释:(链表:是一种物理存储单元上非连续,非顺序的存储结构,数据元素的逻辑是通过链表中指针的链接次序实现链表有一系列结点(链表中的每个元素)组成,结点在运行时动态产生,每个节点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个借点地址的指针域