基于轮廓填充连通区内部空洞

使用cvFindContours函数与cvFillPoly填充连通区内部空洞

</pre></h1><h1 style="margin: 0px; padding: 0px; font-family: Tahoma; line-height: 24px; text-align: center;"></h1><h1 align="center" style="margin: 0px; padding: 0px; font-family: Tahoma; line-height: 24px; text-align: center;"><span style="margin: 0px; padding: 0px; list-style: none outside none; word-break: normal; word-wrap: break-word; font-family: 宋体;">基于轮廓填充连通区内部空洞</span><span lang="EN-US" style="margin: 0px; padding: 0px; list-style: none outside none; word-break: normal; word-wrap: break-word; font-size: 18px; font-family: Arial, sans-serif;"></span></h1><div style="margin: 0px; padding: 0px; font-family: Tahoma; font-size: 14px; line-height: 24px;"><h2 style="margin: 0px; padding: 0px;"><span style="margin: 0px; padding: 0px; list-style: none outside none; word-break: normal; word-wrap: break-word; font-size: 18px;">前言:</span></h2><p style="margin-top: 0px; margin-bottom: 0px; padding-top: 0px; padding-bottom: 0px;"><span style="margin: 0px; padding: 0px; list-style: none outside none; word-break: normal; word-wrap: break-word; font-size: 18px;"><span style="margin: 0px; padding: 0px;"> 最近在做火焰识别项目时使用了一种火焰颜色模型分割疑似火焰区域,由于火焰内部温度高、焰色偏白而被该模型舍弃,造成火焰连通区域内部有空洞,影响进一步的火焰判断。通过查找资料学习,我使用cvFindContours()函数与cvFillPoly()函数填充连通区内部空洞,取得了良好的效果,特写此片博文总结、分享我的经验。</span></span></p><h2 style="margin: 0px; padding: 0px;"><span style="margin: 0px; padding: 0px; list-style: none outside none; word-break: normal; word-wrap: break-word; font-size: 18px;">1. 主要函数介绍</span></h2><h3 style="margin: 0px; padding: 0px;"><span style="margin: 0px; padding: 0px; list-style: none outside none; word-break: normal; word-wrap: break-word; font-size: 18px;">1.1 cvFindContours</span></h3><p style="margin-top: 0px; margin-bottom: 0px; padding-top: 0px; padding-bottom: 0px;"><span style="margin: 0px; padding: 0px;"><span style="margin: 0px; padding: 0px; list-style: none outside none; word-break: normal; word-wrap: break-word; font-size: 18px;">在二值图像中寻找轮廓</span></span></p><p style="margin-top: 0px; margin-bottom: 0px; padding-top: 0px; padding-bottom: 0px;"><span style="margin: 0px; padding: 0px;"><span style="margin: 0px; padding: 0px; list-style: none outside none; word-break: normal; word-wrap: break-word; font-size: 18px;">int cvFindContours( CvArr* image, CvMemStorage* storage,CvSeq** first_contour,<br style="margin: 0px; padding: 0px;" />                                   intheader_size=sizeof(CvContour),int mode=CV_RETR_LIST,<br style="margin: 0px; padding: 0px;" />                                   intmethod=CV_CHAIN_APPROX_SIMPLE,CvPointoffset=cvPoint(0,0) );</span></span></p><p style="margin-top: 0px; margin-bottom: 0px; padding-top: 0px; padding-bottom: 0px;"><span style="margin: 0px; padding: 0px; list-style: none outside none; word-break: normal; word-wrap: break-word; font-size: 18px;"><span style="margin: 0px; padding: 0px;">【参数介绍】<br style="margin: 0px; padding: 0px;" />image </span><br style="margin: 0px; padding: 0px;" />  <span style="margin: 0px; padding: 0px; list-style: none outside none; word-break: normal; word-wrap: break-word;">输入的 8-比特、单通道图像. 非零元素被当成 1, 0 象素值保留为 0 - 从而图像被看成二值的。为了从灰度图像中得到这样的二值图像,可以使用 cvThreshold,cvAdaptiveThreshold 或 cvCanny。本函数改变输入图像内容。</span><br style="margin: 0px; padding: 0px;" /><span style="margin: 0px; padding: 0px;">storage </span><br style="margin: 0px; padding: 0px;" /><span style="margin: 0px; padding: 0px; list-style: none outside none; word-break: normal; word-wrap: break-word;">  得到的轮廓的存储容器 </span><br style="margin: 0px; padding: 0px;" /><span style="margin: 0px; padding: 0px;">first_contour </span><br style="margin: 0px; padding: 0px;" />  <span style="margin: 0px; padding: 0px; list-style: none outside none; word-break: normal; word-wrap: break-word;">输出参数:包含第一个输出轮廓的指针 </span><br style="margin: 0px; padding: 0px;" /><span style="margin: 0px; padding: 0px;">header_size </span><br style="margin: 0px; padding: 0px;" />  <span style="margin: 0px; padding: 0px; list-style: none outside none; word-break: normal; word-wrap: break-word;">如果 method=CV_CHAIN_CODE,则序列头的大小 >=sizeof(CvChain),否则>=sizeof(CvContour) 。</span><br style="margin: 0px; padding: 0px;" /><span style="margin: 0px; padding: 0px;">mode </span><br style="margin: 0px; padding: 0px;" />  提取模式. <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; list-style: none outside none; word-break: normal; word-wrap: break-word;"> CV_RETR_EXTERNAL - 只提取最外层的轮廓 <br style="margin: 0px; padding: 0px;" />  CV_RETR_LIST - 提取所有轮廓,并且放置在 list 中 <br style="margin: 0px; padding: 0px;" />  CV_RETR_CCOMP - 提取所有轮廓,并且将其组织为两层的 hierarchy: 顶层为连通域的外围边界,次层为洞的内层边界。 <br style="margin: 0px; padding: 0px;" />  CV_RETR_TREE - 提取所有轮廓,并且重构嵌套轮廓的全部 hierarchy </span><br style="margin: 0px; padding: 0px;" /><span style="margin: 0px; padding: 0px;">method </span><br style="margin: 0px; padding: 0px;" />  逼近方法 (对所有节点, 不包括使用内部逼近的 CV_RETR_RUNS). <br style="margin: 0px; padding: 0px;" /><span style="margin: 0px; padding: 0px; list-style: none outside none; word-break: normal; word-wrap: break-word;">  CV_CHAIN_CODE - Freeman 链码的输出轮廓. 其它方法输出多边形(定点序列). <br style="margin: 0px; padding: 0px;" />  CV_CHAIN_APPROX_NONE - 将所有点由链码形式翻译(转化)为点序列形式 <br style="margin: 0px; padding: 0px;" />  CV_CHAIN_APPROX_SIMPLE - 压缩水平、垂直和对角分割,即函数只保留末端的象素点; <br style="margin: 0px; padding: 0px;" />  CV_CHAIN_APPROX_TC89_L1, <br style="margin: 0px; padding: 0px;" />  CV_CHAIN_APPROX_TC89_KCOS - 应用 Teh-Chin 链逼近算法. CV_LINK_RUNS - 通过连接为 1 的水平碎片使用完全不同的轮廓提取算法。仅有 CV_RETR_LIST 提取模式可以在本方法中应用。</span><br style="margin: 0px; padding: 0px;" /><span style="margin: 0px; padding: 0px;">offset </span><br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; list-style: none outside none; word-break: normal; word-wrap: break-word;"> 每一个轮廓点的偏移量. 当轮廓是从图像 ROI 中提取出来的时候,使用偏移量有用,因为可以从整个图像上下文来对轮廓做分析。 <br style="margin: 0px; padding: 0px;" />  函数cvFindContours从二值图像中提取轮廓,并且返回提取轮廓的数目。指针 first_contour 的内容由函数填写。它包含第一个最外层轮廓的指针,如果指针为NULL,则没有检测到轮廓(比如图像是全黑的)。其它轮廓可以从 first_contour 利用 h_next 和 v_next 链接访问到。使用cvDrawContours 可以画出轮廓,轮廓也可以用来做形状分析和对象识别。</span></span></p><h3 style="margin: 0px; padding: 0px;"><span style="margin: 0px; padding: 0px; list-style: none outside none; word-break: normal; word-wrap: break-word; font-size: 18px;">1.2 cvFillPoly</span></h3><p style="margin-top: 0px; margin-bottom: 0px; padding-top: 0px; padding-bottom: 0px;"><span style="margin: 0px; padding: 0px; list-style: none outside none; word-break: normal; word-wrap: break-word; font-size: 18px;"><span style="margin: 0px; padding: 0px;">填充多边形内部 <br style="margin: 0px; padding: 0px;" /><br style="margin: 0px; padding: 0px;" />void cvFillPoly( CvArr* img, CvPoint** pts, int* npts, int contours,<br style="margin: 0px; padding: 0px;" />                           CvScalar color, int line_type=8, intshift=0 );</span></span></p><p style="margin-top: 0px; margin-bottom: 0px; padding-top: 0px; padding-bottom: 0px;"><span style="margin: 0px; padding: 0px; list-style: none outside none; word-break: normal; word-wrap: break-word; font-size: 18px;"><span style="margin: 0px; padding: 0px;">【参数介绍】<br style="margin: 0px; padding: 0px;" />img </span><br style="margin: 0px; padding: 0px;" /><span style="margin: 0px; padding: 0px; list-style: none outside none; word-break: normal; word-wrap: break-word;">  图像。 </span><br style="margin: 0px; padding: 0px;" /><span style="margin: 0px; padding: 0px;">pts </span><br style="margin: 0px; padding: 0px;" /><span style="margin: 0px; padding: 0px; list-style: none outside none; word-break: normal; word-wrap: break-word;">  指向多边形的数组指针。 </span><br style="margin: 0px; padding: 0px;" /><span style="margin: 0px; padding: 0px;">npts </span><br style="margin: 0px; padding: 0px;" /><span style="margin: 0px; padding: 0px; list-style: none outside none; word-break: normal; word-wrap: break-word;">  多边形的顶点个数的数组。 </span><br style="margin: 0px; padding: 0px;" /><span style="margin: 0px; padding: 0px;">contours </span><br style="margin: 0px; padding: 0px;" /><span style="margin: 0px; padding: 0px; list-style: none outside none; word-break: normal; word-wrap: break-word;">  组成填充区域的线段的数量。 </span><br style="margin: 0px; padding: 0px;" /><span style="margin: 0px; padding: 0px;">color </span><br style="margin: 0px; padding: 0px;" /><span style="margin: 0px; padding: 0px; list-style: none outside none; word-break: normal; word-wrap: break-word;">  多边形的颜色。 </span><br style="margin: 0px; padding: 0px;" /><span style="margin: 0px; padding: 0px;">line_type </span><br style="margin: 0px; padding: 0px;" /><span style="margin: 0px; padding: 0px; list-style: none outside none; word-break: normal; word-wrap: break-word;">  组成多边形的线条的类型。 </span><br style="margin: 0px; padding: 0px;" /><span style="margin: 0px; padding: 0px;">shift </span><br style="margin: 0px; padding: 0px;" /><span style="margin: 0px; padding: 0px; list-style: none outside none; word-break: normal; word-wrap: break-word;">  顶点坐标的小数点位数。 <br style="margin: 0px; padding: 0px;" />  函数cvFillPoly用于一个单独被多边形轮廓所限定的区域内进行填充。函数可以填充复杂的区域,例如,有漏洞的区域和有交叉点的区域等等。</span></span></p><h2 style="margin: 0px; padding: 0px;"><span style="margin: 0px; padding: 0px; list-style: none outside none; word-break: normal; word-wrap: break-word; font-size: 18px;">2. 编程实现</span></h2></div><div style="margin: 0px; padding: 0px; font-family: Tahoma; font-size: 14px; line-height: 24px;"><span style="margin: 0px; padding: 0px; list-style: none outside none; word-break: normal; word-wrap: break-word; font-size: 18px;"></span><pre name="code" class="cpp" style="margin-top: 0px; margin-bottom: 0px; padding: 0px;">//根据分割结果确定轮廓并填充
void fillSeg(IplImage *src,IplImage *tempdst)
{
	CvSeq * contour = NULL;
	CvMemStorage * storage = cvCreateMemStorage();
	//在二值图像中寻找轮廓,CV_CHAIN_APPROX_SIMPLE - 压缩水平、垂直和对角分割,即函数只保留末端的象素点
	cvFindContours(src,storage,&contour,sizeof(CvContour),CV_RETR_CCOMP ,CV_CHAIN_APPROX_SIMPLE);
	cvZero(tempdst);
	for( contour; contour != 0; contour = contour->h_next)
	{   
		//轮廓的方向影响面积的符号。因此函数也许会返回负的结果。应用函数 fabs() 得到面积的绝对值。 
		double area = cvContourArea( contour,CV_WHOLE_SEQ );
		//计算整个轮廓或部分轮廓的面积
		if(fabs(area) < 10)
		{
			continue;
		}
		//	CvScalar color = CV_RGB( 255, 255, 255 );
		CvPoint *point = new CvPoint[contour->total];
		CvPoint *Point;

		//printf("图像分割contour->total\t%d\n",contour->total);
		for (int i = 0;i<contour->total;i++)
		{
			Point = (CvPoint*)cvGetSeqElem(contour,i);
			point[i].x =Point->x;
			point[i].y = Point->y;
		}
		int pts[1] = {contour->total};
		cvFillPoly(tempdst,&point,pts,1,CV_RGB(255,255,255));//填充多边形内部 
	}
		cvReleaseMemStorage(&storage);
}

下图a原始图片,图b为本函数的作用效果图。作图为经过颜色模型分割的火焰区域,绿色框标注出了火焰内部的空洞;右图是经过填充的图案。内部空洞完全填充,并且火焰轮廓没有受到侵蚀。

(a)
(b)

3. 完整工程

#include<opencv/cv.h>
#include<opencv/highgui.h>
#include <math.h>
#define min(x,y) (x<y?x:y)

#define R_THRESHHOLD 125
#define S_THRESHHOLD 60

//RGB+HSI颜色模型
void colorModel(IplImage *src,IplImage * dst){
	int step = NULL;
	int rows = src->height;
	int cols = src->width;
	for(int i = 0;i < rows;i++){
		//uchar* dataS = src.ptr<uchar>(i);
		//uchar* dataD = dst.ptr<uchar>(i);
		uchar *dataS = (uchar*)src->imageData;
		uchar *dataD= (uchar*)dst->imageData;
		for(int j = 0;j < cols; j++){
			step = i*src->widthStep+j*src->nChannels;;
			float S;
			float b = dataS[step]/255.0;
			float g = dataS[step+1]/255.0;
			float r = dataS[step+2]/255.0;
			float minRGB = min(min(r,g),b);
			float den = r+g+b;
			if(den == 0)	//分母不能为0
				S = 0;
			else
				S = (1 - 3*minRGB/den)*100;
			if(dataS[step+2] <= R_THRESHHOLD || dataS[step+2] < 165){
				dataD[step] = 0;
				dataD[step+1] = 0;
				dataD[step+2] = 0;
			}
			else if(dataS[step+2] <= dataS[step+1] || dataS[step+1] <= dataS[step] ){
				dataD[step] = 0;
				dataD[step+1] = 0;
				dataD[step+2] = 0;
			}
			else if(S <= (float)(S_THRESHHOLD*(255 - dataS [step+2]))/R_THRESHHOLD){
				dataD[step] = 0;
				dataD[step+1] = 0;
				dataD[step+2] = 0;
			}
			else{
				dataD[step] = dataS[step];
				dataD[step+1] = dataS[step+1];
				dataD[step+2] = dataS[step+2];
			}
		}
	}
}

//根据分割结果确定轮廓并填充
void fillSeg(IplImage *src,IplImage *tempdst)
{
	CvSeq * contour = NULL;
	CvMemStorage * storage = cvCreateMemStorage();
	//在二值图像中寻找轮廓,CV_CHAIN_APPROX_SIMPLE - 压缩水平、垂直和对角分割,即函数只保留末端的象素点
	cvFindContours(src,storage,&contour,sizeof(CvContour),CV_RETR_CCOMP ,CV_CHAIN_APPROX_SIMPLE);
	cvZero(tempdst);
	for( contour; contour != 0; contour = contour->h_next)
	{   
		//轮廓的方向影响面积的符号。因此函数也许会返回负的结果。应用函数 fabs() 得到面积的绝对值。 
		double area = cvContourArea( contour,CV_WHOLE_SEQ );
		//计算整个轮廓或部分轮廓的面积
		if(fabs(area) < 10)
		{
			continue;
		}
		//	CvScalar color = CV_RGB( 255, 255, 255 );
		CvPoint *point = new CvPoint[contour->total];
		CvPoint *Point;

		//printf("图像分割contour->total\t%d\n",contour->total);
		for (int i = 0;i<contour->total;i++)
		{
			Point = (CvPoint*)cvGetSeqElem(contour,i);
			point[i].x =Point->x;
			point[i].y = Point->y;
		}
		int pts[1] = {contour->total};
		cvFillPoly(tempdst,&point,pts,1,CV_RGB(255,255,255));//填充多边形内部 
	}
		cvReleaseMemStorage(&storage);
}
int main(){
	IplImage *img = NULL;		//输入图像,8bit 3通道
	IplImage *colTemp = NULL;	//颜色分割后(有内部空洞)的火焰图片
	IplImage *gray = NULL;		//灰度图
	IplImage *mask = NULL;		//二值图,用于复制图像的掩膜
	IplImage *dst = NULL;		//输出火焰疑似图像,8bit、3通道

	img = cvLoadImage("E:\\Test\\SegTest\\fire40.JPG");					//载入原始图片
	colTemp =  cvCreateImage(cvGetSize(img),img->depth,img->nChannels);//经过颜色分割后(有内部空洞)的火焰图片
	gray = cvCreateImage(cvGetSize(img),img->depth,1);
	mask = cvCreateImage(cvGetSize(img),img->depth,1);
	dst = cvCreateImage(cvGetSize(img),img->depth,img->nChannels);		//经过填补后的火焰图片
	cvZero(dst);
	colorModel(img,colTemp);
	cvCvtColor(colTemp,gray,CV_BGR2GRAY);
	//使用cvFindContours函数与cvFillPoly填充连通区内部空洞
	fillSeg(gray,mask);
	cvCopy(img,dst,mask);
	cvShowImage("原始图片",img);
	cvShowImage("颜色分割处理",colTemp);
	cvShowImage("填充处理图片",dst);
	cvShowImage("掩膜",mask);
	cvWaitKey();
	//销毁窗口
	cvDestroyWindow( "原始图片" );
	cvDestroyWindow( "颜色分割处理" );
	cvDestroyWindow( "填充处理图片" );
	cvDestroyWindow( "掩膜" );
	//释放图像
	cvReleaseImage(&img);
	cvReleaseImage(&colTemp);
	cvReleaseImage(&gray);
	cvReleaseImage( &mask ); 


}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值