OpenCV的轮廓查找和填充

时间:2022-01-02 22:07:21

OpenCV的轮廓查找有C版本和C++版本,当轮廓比较复杂的时候,例如嵌入多层轮廓,如果方法不当那么很容易会漏处理一些轮廓。本文介绍了复杂轮廓场景下的几种主要的查找轮廓和颜色填充方法。

1:cvFindContours函数介绍

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:检索所有的轮廓,并重构嵌套轮廓的整个层次。

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

2:找轮廓及颜色填充

方法1:树形遍历

        由contours的h_next和 v_next指针组成一个庞大的树形结构,如下面的图示,其中蓝色表示v_next,绿色表示h_next。如果不了解contours结构的话,很容易仅仅遍历v_next或者h_next,这样实际上会漏掉一些需要处理的轮廓。要全部遍历所有轮廓需要编写不少代码,这个还没搜索到直接用这种方式遍历轮廓的代码。需要注意的是,很多博文介绍的利用h_next或者v_next的方法都是会缺失不少轮廓的,轮廓的复杂会导致整个树也变的很复杂,所以建议还是不要用这种方式来遍历轮廓。我一开始用的就是这种方法,应该说是琢磨了好长时间,才知道为什么这种方法的轮廓数总是偏少。

 OpenCV的轮廓查找和填充

 

方法2:cvFindNextContour方法

本方法是利用了轮廓扫描器CvContourScanner,使用cvStartFindContours、cvFindNextContour、cvEndFindContours函数获取从外到内的轮廓,获取一层轮廓进行一次颜色的填充。具体后面使用的原始轮廓图如下所示:

OpenCV的轮廓查找和填充

#include “opencv/cv.h”
#include “opencv/highgui.h”
int main(int argc, char **argv)
{

IplImage * pSrcImg= cvLoadImage(“E:\\11.jpg”);
IplImage * pGrayImg =cvCreateImage(cvSize(pSrcImg->width,pSrcImg->height),IPL_DEPTH_8U,1);
IplImage * pThresholdImg =cvCreateImage(cvSize(pSrcImg->width,pSrcImg->height),IPL_DEPTH_8U,1);
IplImage * pDstImg =cvCreateImage(cvSize(pSrcImg->width,pSrcImg->height),IPL_DEPTH_8U,3);

cvSetZero(pDstImg);
srand((int)time(0));   
CvSeq * contours = 0;
CvMemStorage * storage=cvCreateMemStorage(0);
CvScalar color=cvScalar( rand()&255, rand()&255, rand()&255 );

cvCvtColor(pSrcImg,pGrayImg,CV_BGR2GRAY);
cvNot(pGrayImg,pGrayImg);
cvThreshold(pGrayImg,pThresholdImg,100,255,CV_THRESH_BINARY);
cvSet(pDstImg,color);

CvContourScanner scanner = cvStartFindContours(pThresholdImg, storage,
                                    sizeof(CvContour),CV_RETR_TREE ,CV_CHAIN_APPROX_NONE);
while (contours=cvFindNextContour(scanner))
{

color=cvScalar( rand()&255, rand()&255, rand()&255 );
cvDrawContours(pDstImg, contours, color,color, 0,CV_FILLED);

};
contours= cvEndFindContours(&scanner);

cvSaveImage(“dst.jpg”,pDstImg);
cvReleaseImage(&pDstImg);
cvReleaseImage(&pGrayImg);
cvReleaseImage(&pThresholdImg);
cvReleaseMemStorage(&storage);
return 0;

}

 方法3:cvNextTreeNode方法

本方法是利用cvFindContours,先找到所有轮廓,再使用树节点的迭代器CvTreeNodeIterator的函数cvNextTreeNode,获取从外到内的轮廓,并进行颜色的填充。

#include “opencv/cv.h”
#include “opencv/highgui.h”

int main(int argc, char **argv)
{

IplImage * pSrcImg= cvLoadImage(“E:\\11.jpg”);
IplImage * pGrayImg =cvCreateImage(cvSize(pSrcImg->width,pSrcImg->height),IPL_DEPTH_8U,1);
IplImage * pThresholdImg =cvCreateImage(cvSize(pSrcImg->width,pSrcImg->height),IPL_DEPTH_8U,1);
IplImage * pDstImg =cvCreateImage(cvSize(pSrcImg->width,pSrcImg->height),IPL_DEPTH_8U,3);

cvSetZero(pDstImg);
srand((int)time(0));
CvSeq * contours = 0;
CvMemStorage * storage=cvCreateMemStorage(0);
CvScalar color=cvScalar( rand()&255, rand()&255, rand()&255 );

cvCvtColor(pSrcImg,pGrayImg,CV_BGR2GRAY);
cvNot(pGrayImg,pGrayImg);
cvThreshold(pGrayImg,pThresholdImg,100,255,CV_THRESH_BINARY);
                                               cvFindContours(pThresholdImg,storage,&contours,sizeof(CvContour),
CV_RETR_TREE ,CV_CHAIN_APPROX_NONE);
cvSet(pDstImg,color);

CvTreeNodeIterator iterator; 
cvInitTreeNodeIterator(&iterator,contours,3); 
while( 0 != (contours = (CvSeq*)cvNextTreeNode(&iterator)) ) 
{

color=cvScalar( rand()&255, rand()&255, rand()&255 );
cvDrawContours(pDstImg, contours, color,color, 0,CV_FILLED);

}
cvSaveImage(“dst.jpg”,pDstImg);
cvReleaseImage(&pDstImg);
cvReleaseImage(&pGrayImg);
cvReleaseImage(&pThresholdImg);
cvReleaseMemStorage(&storage); 
return 0;

}

方法4: C++的iterator方法

本方法是利用OpenCV的C++函数findContours,获取从外到内的轮廓,并利用const_iterator 来逐个找到所有的轮廓。从编码的效果来看,最开始的轮廓是最外延的轮廓,然后逐渐找内部的轮廓,所以进行颜色填充的时候,不会出现外面的大轮廓覆盖掉内部的小轮廓的问题。

#include “opencv/cv.h”
#include “opencv/highgui.h”

using namespace cv;

int main( int argc, char** argv )
{

vector<Vec4i> hierarchy;
vector<vector<Point> > contours;
vector<vector<Point>>::const_iterator itContours;

srand((int)time(0));
Mat src = imread(“E:\\11.jpg”,0);
Mat dst = Mat::zeros(src.rows, src.cols, CV_8UC3);
CvScalar color=cvScalar( rand()&255, rand()&255, rand()&255 );

src = src > 100;
findContours( src, contours, hierarchy, CV_RETR_TREE,CV_CHAIN_APPROX_NONE );

itContours=contours.begin();
int i=0;
for(;itContours!=contours.end();++itContours)
{

color=cvScalar( rand()&255, rand()&255, rand()&255 );
drawContours( dst,contours ,i, color, CV_FILLED );
i++;

}
imwrite(“dst.jpg”,dst);

}

必须采用for(;itContours!=contours.end();++itContours)的循环方式。

如果采用for( ; idx >= 0; idx = hierarchy[idx][0] )的循环方式,会导致大的轮廓颜色覆盖掉其内部小的轮廓颜色。

 结果

如下为两次运行的效果图,其中C语言版本不会将整个图片的外延作为一个轮廓,所以这部分颜色要额外先填充。

从编码效率来看,C++版本的编码效率要高于C版本,因为不需要考虑很多的内存的释放等问题。

 OpenCV的轮廓查找和填充OpenCV的轮廓查找和填充

 

 

 

 

 

 

 

参考资料:

http://baike.baidu.com/view/4111188.htm

http://blog.csdn.net/augusdi/article/details/9000276

http://blog.csdn.net/augusdi/article/details/9000893

http://blog.csdn.net/timidsmile/article/details/8519751

声明:

如果转载了本文,也请注明转载出处:http://www.cvrobot.net/opencv-find-and-draw-contours/