使用cvFindContours函数与cvFillPoly填充连通区内部空洞
OpenCV基于图像轮廓填充连通区内部空洞
前言:
最近在做火焰识别项目时使用了一种火焰颜色模型分割疑似火焰区域,由于火焰内部温度高、焰色偏白而被该模型舍弃,造成火焰连通区域内部有空洞,影响进一步的火焰判断。通过查找资料学习,我使用cvFindContours()函数与cvFillPoly()函数填充连通区内部空洞,取得了良好的效果,特写此片博文总结、分享我的经验。
1. 主要函数介绍
1.1 FindContours
在二值图像中寻找轮廓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 象素值保留为 0 - 从而图像被看成二值的。为了从灰度图像中得到这样的二值图像,可以使用 cvThreshold, cvAdaptiveThreshold 或 cvCanny. 本函数改变输入图像内容。
storage
得到的轮廓的存储容器
first_contour
输出参数:包含第一个输出轮廓的指针
header_size
如果 method=CV_CHAIN_CODE,则序列头的大小 >=sizeof(CvChain),否则 >=sizeof(CvContour) .
mode
提取模式.
CV_RETR_EXTERNAL - 只提取最外层的轮廓
CV_RETR_LIST - 提取所有轮廓,并且放置在 list 中
CV_RETR_CCOMP - 提取所有轮廓,并且将其组织为两层的 hierarchy: 顶层为连通域的外围边界,次层为洞的内层边界。
CV_RETR_TREE - 提取所有轮廓,并且重构嵌套轮廓的全部 hierarchy
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 - 应用 Teh-Chin 链逼近算法. CV_LINK_RUNS - 通过连接为 1 的水平碎片 使用完全不同的轮廓提取算法。仅有 CV_RETR_LIST 提取模式可以在本方法中应用.
offset
每一个轮廓点的偏移量. 当轮廓是从图像 ROI 中提取出来的时候,使用偏移量有用,因为可以从整个图像上下文来对轮廓做分析.
函数 cvFindContours从二值图像中提取轮廓,并且返回提取轮廓的数目。指针 first_contour 的内容由函数填写。它包含第一个最外层轮廓的指针,如果指针为 NULL,则没有检测到轮廓(比如图像是全黑的)。其它轮廓可以从 first_contour 利用 h_next 和 v_next 链接访问到。 在 cvDrawContours 的样例显示如何使用轮廓来进行连通域的检测。轮廓也可以用来做形状分析和对象识别。
image
输入的 8-比特、单通道图像. 非零元素被当成 1, 0 象素值保留为 0 - 从而图像被看成二值的。为了从灰度图像中得到这样的二值图像,可以使用 cvThreshold, cvAdaptiveThreshold 或 cvCanny. 本函数改变输入图像内容。
storage
得到的轮廓的存储容器
first_contour
输出参数:包含第一个输出轮廓的指针
header_size
如果 method=CV_CHAIN_CODE,则序列头的大小 >=sizeof(CvChain),否则 >=sizeof(CvContour) .
mode
提取模式.
CV_RETR_EXTERNAL - 只提取最外层的轮廓
CV_RETR_LIST - 提取所有轮廓,并且放置在 list 中
CV_RETR_CCOMP - 提取所有轮廓,并且将其组织为两层的 hierarchy: 顶层为连通域的外围边界,次层为洞的内层边界。
CV_RETR_TREE - 提取所有轮廓,并且重构嵌套轮廓的全部 hierarchy
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 - 应用 Teh-Chin 链逼近算法. CV_LINK_RUNS - 通过连接为 1 的水平碎片 使用完全不同的轮廓提取算法。仅有 CV_RETR_LIST 提取模式可以在本方法中应用.
offset
每一个轮廓点的偏移量. 当轮廓是从图像 ROI 中提取出来的时候,使用偏移量有用,因为可以从整个图像上下文来对轮廓做分析.
函数 cvFindContours从二值图像中提取轮廓,并且返回提取轮廓的数目。指针 first_contour 的内容由函数填写。它包含第一个最外层轮廓的指针,如果指针为 NULL,则没有检测到轮廓(比如图像是全黑的)。其它轮廓可以从 first_contour 利用 h_next 和 v_next 链接访问到。 在 cvDrawContours 的样例显示如何使用轮廓来进行连通域的检测。轮廓也可以用来做形状分析和对象识别。
1.2 FillPoly
填充多边形内部void cvFillPoly( CvArr* img, CvPoint** pts, int* npts, int contours,
CvScalar color, int line_type=8, int shift=0 );
【参数介绍】
img
图像。
pts
指向多边形的数组指针。
npts
多边形的顶点个数的数组。
contours
组成填充区域的线段的数量。
color
多边形的颜色。
line_type
组成多边形的线条的类型。
shift
顶点坐标的小数点位数。
函数cvFillPoly用于一个单独被多边形轮廓所限定的区域内进行填充。函数可以填充复杂的区域,例如,有漏洞的区域和有交叉点的区域等等。
img
图像。
pts
指向多边形的数组指针。
npts
多边形的顶点个数的数组。
contours
组成填充区域的线段的数量。
color
多边形的颜色。
line_type
组成多边形的线条的类型。
shift
顶点坐标的小数点位数。
函数cvFillPoly用于一个单独被多边形轮廓所限定的区域内进行填充。函数可以填充复杂的区域,例如,有漏洞的区域和有交叉点的区域等等。
2. 编程实现
函数fiilSeg()输入为经过颜色分割的火焰图片(需转为单通道灰度图),输出为填补了空洞的二值图像。

//根据分割结果确定轮廓并填充 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));//填充多边形内部 } }
下图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));//填充多边形内部 } } 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",mask); cvWaitKey(); }
文章来自:http://blog.csdn.net/solomon1558/article/details/44187273