现在终于可以讨论轮廓的问题了。首先我们需要了解轮廓到底是什么?一个轮廓一般对应一系列的点,也就是图像中的一条曲线。表示方法可能根据不同情况而有所不同。有多种方法可以表示曲线。在OpenCV中一般用序列来存储轮廓信息。序列中的每一个元素是曲线中一个点的位置。关于序列表示的轮廓细节将在后面讨论,现在只要简单把轮廓想像为使用CvSeq表示的一系列的点就可以了。
1、cvFindCantours()寻找轮廓
函数cvFindCantours()从二值图像中寻找轮廓。cvFindContours()处理的图像可以是从cvCanny()函数得到的有边缘像素的图像,或者是从cvThreshold()及cvAdaptiveThreshold()得到的图像,这时的边缘是正和负区域之间的边界。
cvFindContours()书上的定义1
int cvFindContours(
IplImage* image,
CvMemStorage* storage,
CvSeq** first_contour,
int header_size=sizeof(CvContour),
CvContourRetrievalMode mode=CV_RETR_LIST,
CvChainApproxMethod method=CV_CHAIN_APPROX_SIMPLE
);
参数:
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:检索所有的轮廓,并重构嵌套轮廓的整个层次。
method
边缘近似方法(除了CV_RETR_RUNS使用内置的近似,其他模式均使用此设定的近似算法)。可取值如下:
CV_CHAIN_CODE:以Freeman链码的方式输出轮廓,所有其他方法输出多边形(顶点的序列)。
CV_CHAIN_APPROX_NONE:将所有的连码点,转换成点。存储所有的轮廓点,相邻的两个点的像素位置差不超过1。
CV_CHAIN_APPROX_SIMPLE:压缩水平的、垂直的和斜的部分,也就是,函数只保留他们的终点部分,例如一个矩形轮廓只需4个点来保存轮廓信息。
CV_CHAIN_APPROX_TC89_L1,CV_CHAIN_APPROX_TC89_KCOS:使用the flavors of Teh-Chin chain近似算法的一种。
CV_LINK_RUNS:通过连接水平段的1,使用完全不同的边缘提取算法。使用CV_RETR_LIST检索模式能使用此方法。
mode到底是什么意思
http://blog.csdn.net/lu597203933/article/details/14489225
在这片博文里面,说明了使用各个参数时,各个轮廓之间的关系。
轮廓分为外轮廓和内轮廓 如下图:外轮廓以c开头 内轮廓以h开头。
CV_RETR_EXTERNAL:first = c0
其他两种太复杂,没明白。
2、重要:怎样找到自己想要的轮廓
上面还没说,找到了一个网站,说的很明白:
http://www.tuicool.com/articles/NFVJRr
轮廓(contour)可以简单理解为一段连续的像素点。比如一个长方形的边,比如一条线,比如一个点,都属于轮廓。而轮廓之间有一定的层级关系,以下图为例:
主要说明以下概念:
external & internal:对于最大的包围盒而言,2 是外部轮廓(external),2a 是内部轮廓(internal)。
parent & child:2 是 2a 的父轮廓(parent),2a 是 2 的子轮廓(child),3 是 2a 的子轮廓,同理,3a 是 3 的子轮廓,4 和 5 都是 3a 的子轮廓。
external | outermost:0、1、2 都属于最外围轮廓(outermost)。
hierarchy level:0、1、2 是同一层级(same hierarchy),都属于 hierarchy-0 ,它们的第一层子轮廓属于 hierarchy-1 。
first child:4 是 3a 的第一个子轮廓(first child)。实际上 5 也可以,这个看个人喜好了。
在 OpenCV 中,通过一个数组表达轮廓的层级关系:
[Next, Previous, First_Child, Parent]
Next:同一层级的下一个轮廓。在上图中, 0 的 Next 就是 1 ,1 的 Next 就是 2 ,2 的 Next 是 -1 ,表示没有下一个同级轮廓。
Previous:同一层级的上一个轮廓。比如 5 的 Previous 是 4, 1 的 Previous 就是 0 ,0 的 Previous 是 -1 。
First_Child:第一个子轮廓,比如 2 的 First_Child 就是 2a ,像 3a 这种有两个 Child ,只取第一个,比如选择 4 作为 First_Child 。
Parent:父轮廓,比如 4 和 5 的 Parent 都是 3a ,3a 的 Parent 是 3 。
findContours 是寻找轮廓的函数,其中的参数:
mode:边缘检测的模式,包括:
CV_RETR_EXTERNAL:只检索最大的外部轮廓(extreme outer),没有层级关系,只取根节点的轮廓。
关于CV_RETR_EXTERNAL
从上面可以看出来,为什么用这个参数不管什么图片的检测结果都是1了。
CV_RETR_LIST:检索所有轮廓,但是没有 Parent 和 Child 的层级关系,所有轮廓都是同级的。
CV_RETR_CCOMP:检索所有轮廓,并且按照二级结构组织:外轮廓和内轮廓。以前面的大图为例,0、1、2、3、4、5 都属于第0层,2a 和 3a 都属于第1层。
CV_RETR_TREE:检索所有轮廓,并且按照嵌套关系组织层级。以前面的大图为例,0、1、2 属于第0层,2a 属于第1层,3 属于第2层,3a 属于第3层,4、5 属于第4层。
3、关于CV_RETR_CCOMP的辨析实例:
上面说了,CV_RETR_CCOMP:检索所有轮廓,并且按照二级结构组织:外轮廓和内轮廓。
http://bbs.csdn.net/topics/391935739
所以,如果图像是没有hole的,如:
这时,如果在cvFindcontours中的参数为CV_RETR_CCOMP,提取的结果是空的,因为对于CV_RETR_CCOMP,它是提取hole和最外框之间的外轮廓和内轮廓,此图没有内轮廓,所以没结果。
此时需要使用参数CV_RETR_LIST,就可以将轮廓提取出来:
如果图像为:
用CV_RETR_LIST提取出的轮廓为:
用CV_RETR_CCOMP提取出的轮廓为:
4、重点:自己关于CV_RETR_CCOMP的辨析
要想使用cvFindContours()得到自己想要的轮廓计数结果,所有的“孔洞”必须是白色的,背景必须是黑色的。
可以拿下面两幅图自己试一下。
5、自己写的查找轮廓实例:
#include "highgui.h"
#include "cv.h"
void count(IplImage* image)
{
int Number_Object =0; //定义目标对象数量
CvMemStorage *stor = 0;
CvSeq * cont = 0;
CvContourScanner contour_scanner;
CvSeq * a_contour= 0;
stor = cvCreateMemStorage(0);
cont = cvCreateSeq(CV_SEQ_ELTYPE_POINT, sizeof(CvSeq), sizeof(CvPoint), stor);
Number_Object = cvFindContours(image, stor, &cont, sizeof(CvContour), CV_RETR_CCOMP, CV_CHAIN_APPROX_NONE, cvPoint(0,0) ); //找到所有轮廓
printf("Number_Object: %d\n", Number_Object);
}
/*主函数*/
int main(int argc, char ** argv)
{
IplImage* img=cvLoadImage("10.jpg");
cvNamedWindow("Example1",CV_WINDOW_AUTOSIZE);
cvShowImage("Example1",img);
IplImage* dst = cvCreateImage(cvGetSize(img),IPL_DEPTH_8U,1);
cvCvtColor(img,dst,CV_BGR2GRAY);
cvShowImage("GRAY",dst);
count(dst);
cvWaitKey(0);
cvDestroyAllWindows();
return 0;
}