摘要
本文通过opencv来实现一种前景检测算法——GMM,算法采用的思想来自论文[1][2][4]。在进行前景检测前,先对背景进行训练,对图像中每个背景采用一个混合高斯模型进行模拟,每个背景的混合高斯的个数可以自适应。然后在测试阶段,对新来的像素进行GMM匹配,如果该像素值能够匹配其中一个高斯,则认为是背景,否则认为是前景。由于整个过程GMM模型在不断更新学习中,所以对动态背景有一定的鲁棒性。最后通过对一个有树枝摇摆的动态背景进行前景检测,取得了较好的效果。
关键字:GMM,opencv,前景检测
前言
前景检测主要分为帧差法,平均背景法,光流法,前景建模法,背景非参数估计,背景建模法等。而本文要实现的方法属于背景建模法中的一种——GMM,也称混合高斯模型。
混合高斯模型最早在计算机视觉中的应用是Stauffer et al.[1],作者将其用来做前景检测,主要是用于视频监控领域,这个系统和稳定且有自学能力,能在户外环境跑16个多月。KaewTraKulPong et al.[2]将GMM的训练过程做了改进,将训练过程分为2步进行,前L帧采用EM算法进行权值,均值,方差更新,后面的过程就采用[1]中的方法进行更新,取得了更好的检测效果。Zivkovic et al.在[3]中对GMM理论做了全面的论述,使得GMM理论的使用不仅金限于计算机视觉领域。并且该作者在[4]将该理论进一步具体到背景减图的前景检测中来,即加入了参数估计的先验知识,取得了很好的效果和稳定性。
最近几年陆续有学者对GMM的背景见图中的应用做了更深一步的研究,其代表性贡献见论文[5][6]。
实现过程
本文中主要是根据[2][4]中提出的算法,采用其中的更新方差来根性模型中的3个参数,最后结合用opencv基本底层函数实现该算法。其主要过程如下:
4. 当到达训练的帧数T后,进行不同像素点GMM个数自适应的选择。首先用权值除以方差对各个高斯进行从大到小排序,然后选取最前面B个高斯,使
这样就可以很好的消除训练过程中的噪声点。
5. 在测试阶段,对新来像素点的值与B个高斯中的每一个均值进行比较,如果其差值在2倍的方差之间的话,则认为是背景,否则认为是前景。并且只要其中有一个高斯分量满足该条件就认为是前景。前景赋值为255,背景赋值为0。这样就形成了一副前景二值图。
6. 由于前景二值图中含有很多噪声,所以采用了形态学的开操作将噪声缩减到0,紧接着用闭操作重建由于开操作丢失的边缘部分的信息。消除了不连通的小噪声点。
上面是该算法实现的大概流程,但是当我们在具体编程时,还是有很多细节的地方需要注意,比如有些参数值的选择。在这里,经过试验将一些常用的参数值声明如下:
程序流程框图
该工程的流程框图如下图所示:
试验结果
本次试验的数据为摇摆的树枝作为背景,Waving Trees,其来源网址为:http://research.microsoft.com/en-us/um/people/jckrumm/WallFlower/TestImages.htm 由于该数据是286张bmp格式的图片,所以用的前200张图片来作为GMM参数训练,后186张作为测试。训练的过程中树枝被很大幅度的摆动,测试过程中有行人走动,该行人是需要迁就检测的部分。
下图为训练过程中动态背景截图
由上图可以看出,树枝在不断摇摆,可见其背景是动态的。
没有形态学处理的结果如下:
下图是有简单形态学的试验结果:
(上面2幅图的左边为测试原始图片,右图为检测效果图)
总结
通过本次试验,不仅学习到了GMM的相关理论知识,以及背景减图法在前景检测中的应用。更重要的时,对opencv在计算机视觉中的使用更进一步的熟悉了。另外对于目标检测的难点有了更深一层的理解。
参考文献
后续改进
需要改进的地方:
1. 程序运行的速度太慢,很多参数都是浮点数,每个像素都要建立一个gmm,gmm个数本身比较多,所以训练过程中速度比较慢,代码需要优化。
2. 最后生成的前景图需要用连通域处理算法进行修整,即需要形态学操作,然后找出连通域大小满足要求的轮廓,用多边形拟合来进行处理。这种算法在《learning opencv》一书中有提到。后续有时间加入该算法,效果会好很多的。
附录:工程代码
1 //gmm.cpp : 定义控制台应用程序的入口点。 2 #include "stdafx.h" 3 #include "cv.h" 4 #include "highgui.h" 5 #include <opencv2/imgproc/imgproc.hpp> 6 #include <opencv2/core/core.hpp> 7 #include <opencv2/highgui/highgui.hpp> 8 #include <stdio.h> 9 #include <iostream> 10 11 using namespace cv; 12 using namespace std; 13 14 //定义gmm模型用到的变量 15 #define GMM_MAX_COMPONT 6 16 #define GMM_LEARN_ALPHA 0.005 //该学习率越大的话,学习速度太快,效果不好 17 #define GMM_THRESHOD_SUMW 0.7 //如果取值太大了的话,则更多树的部分都被检测出来了 18 #define END_FRAME 200 19 20 bool pause=false; 21 22 Mat w[GMM_MAX_COMPONT]; 23 Mat u[GMM_MAX_COMPONT]; 24 Mat sigma[GMM_MAX_COMPONT]; 25 Mat fit_num,gmask,foreground; 26 vector<Mat> output_m; 27 Mat output_img; 28 29 float temp_w,temp_sigma; 30 unsigned char temp_u; 31 int i=-1; 32 33 34 //For connected components: 35 int CVCONTOUR_APPROX_LEVEL = 2; // Approx.threshold - the bigger it is, the simpler is the boundary 36 int CVCLOSE_ITR = 1; 37 38 //Just some convienience macros 39 #define CV_CVX_WHITE CV_RGB(0xff,0xff,0xff) 40 #define CV_CVX_BLACK CV_RGB(0x00,0x00,0x00) 41 42 //gmm整体初始化函数声明 43 void gmm_init(Mat img); 44 45 //gmm第一帧初始化函数声明 46 void gmm_first_frame(Mat img); 47 48 //gmm训练过程函数声明 49 void gmm_train(Mat img); 50 51 //对输入图像每个像素gmm选择合适的个数函数声明 52 void gmm_fit_num(Mat img); 53 54 //gmm测试函数的实现 55 void gmm_test(Mat img); 56 57 //连通域去噪函数声明 58 void find_connected_components(Mat img); 59 //void cvconnectedComponents(IplImage *mask, int poly1_hull0, float perimScale, int *num, CvRect *bbs, CvPoint *centers); 60 61 int main(int argc, const char* argv[]) 62 { 63 Mat img,img_gray; 64 char str_num[5]; 65 66 67 // char *str_num;//why does this definition not work? 68 String str="WavingTrees/b00";//string,the 's' can be a captial or lower-caseletters 69 70 /****read the first image,and reset the array w,u,sigma****/ 71 img=imread("WavingTrees/b00000.bmp"); 72 if(img.empty()) 73 return -1; 74 75 output_img=Mat::zeros(img.size(),img.type()); 76 cvtColor(img,img_gray,CV_BGR2GRAY);//covert the colorful image to the corresponding gray-level image 77 78 /****initialization the three parameters ****/ 79 gmm_init(img_gray); 80 fit_num=Mat(img.size(),CV_8UC1,-1);//初始化为1 81 gmask=Mat(img.size(),CV_8UC1,-1); 82 foreground=img.clone(); 83 split(img,output_m); 84 85 output_m[0]=Mat::zeros(img.size(),output_m[0].type()); 86 output_m[1]=Mat::zeros(img.size(),output_m[0].type()); 87 output_m[2]=Mat::zeros(img.size(),output_m[0].type()); 88 89 namedWindow("src",WINDOW_AUTOSIZE); 90 namedWindow("gmask",WINDOW_AUTOSIZE); 91 92 //在定义视频输出对象时,文件名一定后面要加后缀,比如这里的.avi,否则是输出不了视频的!并且这里只能是avi格式的,当参数为('P','I','M','1')时 93 VideoWriter output_src("src.avi",CV_FOURCC('P','I','M','1'),20,Size(160,120),1);//c++版本的opencv用Size函数即可,c版本的用cvSize()函数 94 //VideoWriter output_src("src.avi",CV_FOURCC('M','J','P','G'),5,Size(160,120),1);//c++版本的opencv用Size函数即可,c版本的用cvSize()函数 95 VideoWriter output_dst("dst.avi",CV_FOURCC('P','I','M','1'),20,Size(160,120),1);//这样输出的是3个通道的数据 96 while(1) 97 98 { 99 if(!pause)100 {101 /****read image from WavingTrees****/102 i++;103 _itoa_s(i,str_num,10);//the latest name is _itoa_s or _itoa,not the itoa,although iota can be used,deprecated104 if(i<10)105 str+="00";106 else if(i<100)107 str+="0";108 else if(i>285)//we used the first 285 frames to learn the gmm model109 i=-1;110 str+=str_num;111 str+=".bmp";112 113 img=imread(str);114 if(img.empty())115 break;116 str="WavingTrees/b00";//after read,str must be reseted ;117 118 cvtColor(img,img_gray,CV_BGR2GRAY);//covert the colorful image to the corresponding gray-level image119 120 /****when it is the first frame,set the default parameter****/121 if(1==i)122 {123 gmm_first_frame(img_gray);124 }125 126 //the train of gmm phase127 //if(1<i&&i<5&&i!=3)//由此可知当i大于等于3以后,就会一直出现错误,且错误在内部排序的部分128 if(1<i<END_FRAME)129 {130 gmm_train(img_gray);131 }//end the train phase132 133 cout<<i<<endl;134 /****chose the fitting number of component in gmm****/135 if(END_FRAME==i)136 {137 gmm_fit_num(img_gray);138 // cout<<fit_num<<endl;//其输出值有4个高斯的,但也有0个高斯的,why?照理说不可能的啊!139 }140 141 /****start the test phase****/142 if(i>=END_FRAME)143 {144 output_src<<img;145 gmm_test(img_gray);146 find_connected_components(img_gray);147 148 output_m[0]=gmask.clone();149 output_m[1]=gmask.clone();150 output_m[2]=gmask.clone();151 152 merge(output_m,output_img);153 output_dst<<output_img;154 }155 if(285==i)156 {157 return 0;158 }159 imshow("src",img);160 imshow("gmask",gmask);161 }162 163 char c=(char)waitKey(1);164 if(c==27)//if press the ESC key,the exit the proggram165 break;166 if(c==' ')167 // pause=~pause;//if use '~',then the pause key cannot work,why?168 pause=!pause; 169 }170 return 0;171 }172 173 174 //gmm初始化函数实现175 void gmm_init(Mat img)176 {177 /****initialization the three parameters ****/178 for(int j=0;j<GMM_MAX_COMPONT;j++)179 {180 w[j]=Mat(img.size(),CV_32FC1,0.0);//CV_32FC1本身体现了正负符号181 u[j]=Mat(img.size(),CV_8UC1,-1);//为什么这里赋值为0时,后面的就一直出错?暂时还不知道原因,先赋值-1,其实内部存储的也是0182 sigma[j]=Mat(img.size(),CV_32FC1,0.0);//float类型183 }184 185 //为什么一下语句不能放在这个函数里面呢186 // output_m[0]=Mat(img.size(),CV_8UC1,0);187 // output_m[1]=Mat(img.size(),CV_8UC1,0);188 // output_m[2]=Mat(img.size(),CV_8UC1,0);189 }190 191 192 //gmm第一帧初始化函数实现193 void gmm_first_frame(Mat img)194 {195 for(int m=0;m<img.rows;m++)196 for(int n=0;n<img.cols;n++) 197 {198 w[0].at<float>(m,n)=1.0;199 200 //if the pixvel is gray-clever,then we should use unsigned char,not the unsigned int201 u[0].at<unsigned char>(m,n)=img.at<unsigned char>(m,n);// 一定要注意其类型转换,否则会得不得预期的结果202 sigma[0].at<float>(m,n)=15.0;//opencv 自带的gmm代码中用的是15.0203 204 for(int k=1;k<GMM_MAX_COMPONT;k++) 205 {206 /****when assigment this,we must be very carefully****/207 w[k].at<float>(m,n)=0.0;208 u[k].at<unsigned char>(m,n)=-1;209 sigma[k].at<float>(m,n)=15.0;//防止后面排序时有分母为0的情况210 }211 } 212 }213 214 215 //gmm训练过程函数实现216 void gmm_train(Mat img)217 {218 for(int m=0;m<img.rows;m++)219 for(int n=0;n<img.cols;n++)220 {221 int k=0;222 int nfit=0;223 for(;k<GMM_MAX_COMPONT;k++)224 {225 // if(w[k].at<float>(m,n)!=0)//只有在权值不为0的情况下才进行比较226 // {227 int delam=abs(img.at<unsigned char>(m,n)-u[k].at<unsigned char>(m,n));//防止溢出228 long dis=delam*delam;229 if(dis<3.0*sigma[k].at<float>(m,n))//the present pixpel is fit the component230 {231 /****update the weight****/232 w[k].at<float>(m,n)=w[k].at<float>(m,n)+GMM_LEARN_ALPHA*(1-w[k].at<float>(m,n));233 234 /****update the average****/235 u[k].at<unsigned char>(m,n)=u[k].at<unsigned char>(m,n)+(GMM_LEARN_ALPHA/w[k].at<float>(m,n))*delam;236 237 /****update the variance****/238 sigma[k].at<float>(m,n)=sigma[k].at<float>(m,n)+(GMM_LEARN_ALPHA/w[k].at<float>(m,n))*(dis-sigma[k].at<float>(m,n));239 240 // break;241 }242 else{243 w[k].at<float>(m,n)=w[k].at<float>(m,n)+GMM_LEARN_ALPHA*(0-w[k].at<float>(m,n));244 nfit++;245 } 246 // }247 }248 249 ////训练过程加速算法250 //for(int bb=k+1;bb<GMM_MAX_COMPONT;bb++)251 //{252 // w[bb].at<float>(m,n)=w[bb].at<float>(m,n)+GMM_LEARN_ALPHA*(0-w[bb].at<float>(m,n));253 // nfit++;254 //}255 256 //对gmm各个高斯进行排序,从大到小排序,排序依据为w/sigma257 for(int kk=0;kk<GMM_MAX_COMPONT;kk++)258 {259 for(int rr=kk;rr<GMM_MAX_COMPONT;rr++)260 {261 //怎样才能做到gmm结构体整体排序呢?262 if(w[rr].at<float>(m,n)/sigma[rr].at<float>(m,n)>w[kk].at<float>(m,n)/sigma[kk].at<float>(m,n))263 {264 //权值交换265 temp_w=w[rr].at<float>(m,n);266 w[rr].at<float>(m,n)=w[kk].at<float>(m,n);267 w[kk].at<float>(m,n)=temp_w;268 269 //均值交换270 temp_u=u[rr].at<unsigned char>(m,n);271 u[rr].at<unsigned char>(m,n)=u[kk].at<unsigned char>(m,n);272 u[kk].at<unsigned char>(m,n)=temp_u;273 274 //方差交换275 temp_sigma=sigma[rr].at<float>(m,n);276 sigma[rr].at<float>(m,n)=sigma[kk].at<float>(m,n);277 sigma[kk].at<float>(m,n)=temp_sigma;278 }279 }280 }281 282 //****如果没有满足条件的高斯,则重新开始算一个高斯分布****/283 if(nfit==GMM_MAX_COMPONT&&0==w[GMM_MAX_COMPONT-1].at<float>(m,n))//if there is no exit component fit,then start a new componen284 {285 //不能写为for(int h=0;h<MAX_GMM_COMPONT&&((0==w[h].at<float>(m,n)));h++),因为这样明显h不会每次都加1286 for(int h=0;h<GMM_MAX_COMPONT;h++)287 {288 if((0==w[h].at<float>(m,n)))289 {290 w[h].at<float>(m,n)=GMM_LEARN_ALPHA;//按照论文的参数来的291 u[h].at<unsigned char>(m,n)=(unsigned char)img.at<unsigned char>(m,n);292 sigma[h].at<float>(m,n)=15.0;//the opencv library code is 15.0293 for(int q=0;q<GMM_MAX_COMPONT&&q!=h;q++)294 {295 /****update the other unfit's weight,u and sigma remain unchanged****/296 w[q].at<float>(m,n)*=1-GMM_LEARN_ALPHA;//normalization the weight,let they sum to 1297 }298 break;//找到第一个权值不为0的即可299 } 300 }301 }302 //如果GMM_MAX_COMPONT都曾经赋值过,则用新来的高斯代替权值最弱的高斯,权值不变,只更新均值和方差303 else if(nfit==GMM_MAX_COMPONT&&w[GMM_MAX_COMPONT-1].at<float>(m,n)!=0)304 {305 u[GMM_MAX_COMPONT-1].at<unsigned char>(m,n)=(unsigned char)img.at<unsigned char>(m,n);306 sigma[GMM_MAX_COMPONT-1].at<float>(m,n)=15.0;//the opencv library code is 15.0307 }308 309 310 }311 }//end the train phase312 313 314 //对输入图像每个像素gmm选择合适的个数315 void gmm_fit_num(Mat img)316 {317 //float sum_w=0.0;//重新赋值为0,给下一个像素做累积318 for(int m=0;m<img.rows;m++)319 for(int n=0;n<img.cols;n++)320 {321 float sum_w=0.0;//重新赋值为0,给下一个像素做累积322 //chose the fittest number fit_num323 for(unsigned char a=0;a<GMM_MAX_COMPONT;a++)324 {325 //cout<<w[a].at<float>(m,n)<<endl;326 sum_w+=w[a].at<float>(m,n);327 if(sum_w>=GMM_THRESHOD_SUMW)//如果这里THRESHOD_SUMW=0.6的话,那么得到的高斯数目都为1,因为每个像素都有一个权值接近1328 {329 fit_num.at<unsigned char>(m,n)=a+1;330 break;331 }332 }333 }334 }335 336 337 //gmm测试函数的实现338 void gmm_test(Mat img)339 {340 for(int m=0;m<img.rows;m++)341 for(int n=0;n<img.cols;n++)342 {343 unsigned char a=0;344 for(;a<fit_num.at<unsigned char>(m,n);a++)345 {346 //如果对sigma取根号的话,树枝当做前景的概率会更大,不过人被检测出来的效果也更好些;用2相乘,不用开根号效果还不错347 // if(abs(img.at<unsigned char>(m,n)-u[a].at<unsigned char>(m,n))<(unsigned char)(2*(sigma[a].at<float>(m,n))))348 if(abs(img.at<unsigned char>(m,n)-u[a].at<unsigned char>(m,n))<(unsigned char)(2.5*(sigma[a].at<float>(m,n))))349 {350 gmask.at<unsigned char>(m,n)=1;//背景351 break;352 }353 }354 if(a==fit_num.at<unsigned char>(m,n))355 gmask.at<unsigned char>(m,n)=255;//前景356 }357 }358 359 //连通域去噪函数实现360 void find_connected_components(Mat img)361 {362 morphologyEx(gmask,gmask,MORPH_OPEN,Mat());363 // morphologyEx(gmask,gmask,MORPH_CLOSE,Mat());364 }365 366 ////连通域去噪函数实现367 //void find_connected_components(Mat img)368 //{369 // morphologyEx(gmask,gmask,MORPH_OPEN,Mat());370 // morphologyEx(gmask,gmask,MORPH_CLOSE,Mat());371 //// erode(gmask,gmask,Mat());//只腐蚀是不行的,人来了也被腐蚀掉了372 //373 // vector<vector<Point>> contours;//由点向量组成的向量,所以有2个层次结构374 // vector<Vec4i> hierarchy;//typedef Vec<int,4>Vec4i;即由4个整数组成的向量375 // 376 // //找到gmask的轮廓,存储在contours中,其拓扑结构存储在hierarchy中,且仅仅找出最外面的轮廓,用压缩算法只存储水平,垂直,斜对角线的端点377 // //其中暗含了hierarchy[i][2]=hierarchy[3]=-1,即其父轮廓和嵌套轮廓不用考虑378 // findContours(gmask,contours,hierarchy,CV_RETR_EXTERNAL,CV_CHAIN_APPROX_SIMPLE);379 // if(contours.size()==0)380 // return;381 //382 // int idex=0;383 // for(idex=0;idex<contours.size();idex++)384 // {385 // const vector<Point>& c=contours[idex];386 //// const vector<Point>& cnull::zeros();387 // double len=arcLength(c,false);//求出轮廓的周长,并不一定要求其是封闭的388 // double q=(img.rows+img.cols)/4;389 // if(q>=len)390 // {391 // const vector<Point> &cnew=contours[idex];392 // // Mat mcnew=Mat(cnew);393 // // Mat mcnew;394 // // approxPolyDP(Mat(c),mcnew,CVCONTOUR_APPROX_LEVEL,false);//多边形曲线拟合,并不一定要求其轮廓闭合395 // // approxPolyDP(Mat(c),Mat(cnew),CVCONTOUR_APPROX_LEVEL,false);//多边形曲线拟合,并不一定要求其轮廓闭合396 // approxPolyDP(Mat(c),cnew,CVCONTOUR_APPROX_LEVEL,false);//多边形曲线拟合,并不一定要求其轮廓闭合397 // // cnew=vector<Point>(mcnew);398 // // contours[idex]=cnew;399 // }400 //// else contours[idex]=vector<Point(0,0,0)>;401 // } 402 //403 //}404 405 ///////////////////////////////////////////////////////////////////////////////////////////406 //void cvconnectedComponents(IplImage *mask, int poly1_hull0, float perimScale, int *num, CvRect *bbs, CvPoint *centers)407 // This cleans up the forground segmentation mask derived from calls to cvbackgroundDiff408 //409 // mask Is a grayscale (8 bit depth) "raw" mask image which will be cleaned up410 //411 // OPTIONAL PARAMETERS:412 // poly1_hull0 If set, approximate connected component by (DEFAULT) polygon, or else convex hull (0)413 // perimScale Len = image (width+height)/perimScale. If contour len < this, delete that contour (DEFAULT: 4)414 // num Maximum number of rectangles and/or centers to return, on return, will contain number filled (DEFAULT: NULL)415 // bbs Pointer to bounding box rectangle vector of length num. (DEFAULT SETTING: NULL)416 // centers Pointer to contour centers vectore of length num (DEFULT: NULL)417 //418 //void cvconnectedComponents(IplImage *mask, int poly1_hull0, float perimScale, int *num, CvRect *bbs, CvPoint *centers)419 //{420 // static CvMemStorage* mem_storage = NULL;421 // static CvSeq* contours = NULL;422 //// static CvSeq** firstContour;423 //424 // //CLEAN UP RAW MASK425 // //开运算作用:平滑轮廓,去掉细节,断开缺口426 // cvMorphologyEx( mask, mask, NULL, NULL, CV_MOP_OPEN, CVCLOSE_ITR );//对输入mask进行开操作,CVCLOSE_ITR为开操作的次数,输出为mask图像427 // //闭运算作用:平滑轮廓,连接缺口428 // cvMorphologyEx( mask, mask, NULL, NULL, CV_MOP_CLOSE, CVCLOSE_ITR );//对输入mask进行闭操作,CVCLOSE_ITR为闭操作的次数,输出为mask图像429 //430 // //FIND CONTOURS AROUND ONLY BIGGER REGIONS431 // if( mem_storage==NULL ) mem_storage = cvCreateMemStorage(0);432 // else cvClearMemStorage(mem_storage);433 //434 // //CV_RETR_EXTERNAL=0是在types_c.h中定义的,CV_CHAIN_APPROX_SIMPLE=2也是在该文件中定义的435 // CvContourScanner scanner = cvStartFindContours(mask,mem_storage,sizeof(CvContour),CV_RETR_EXTERNAL,CV_CHAIN_APPROX_SIMPLE);436 //// CvContourScanner scanner = cvFindContours(mask,mem_storage,firstContour,sizeof(CvContour),CV_RETR_EXTERNAL,CV_CHAIN_APPROX_SIMPLE);437 // CvSeq* c;438 // int numCont = 0;439 // while( (c = cvFindNextContour( scanner )) != NULL )440 // {441 // double len = cvContourPerimeter( c );442 // double q = (mask->height + mask->width) /perimScale; //calculate perimeter len threshold443 // if( len < q ) //Get rid of blob if it's perimeter is too small444 // {445 // cvSubstituteContour( scanner, NULL );446 // }447 // else //Smooth it's edges if it's large enough448 // {449 // CvSeq* c_new;450 // if(poly1_hull0) //Polygonal approximation of the segmentation451 // c_new = cvApproxPoly(c,sizeof(CvContour),mem_storage,CV_POLY_APPROX_DP, CVCONTOUR_APPROX_LEVEL,0);452 // else //Convex Hull of the segmentation453 // c_new = cvConvexHull2(c,mem_storage,CV_CLOCKWISE,1);454 // cvSubstituteContour( scanner, c_new );455 // numCont++;456 // }457 // }458 // contours = cvEndFindContours( &scanner );459 //460 // // PAINT THE FOUND REGIONS BACK INTO THE IMAGE461 // cvZero( mask );462 // IplImage *maskTemp;463 // //CALC CENTER OF MASS AND OR BOUNDING RECTANGLES464 // if(num != NULL)465 // {466 // int N = *num, numFilled = 0, i=0;467 // CvMoments moments;468 // double M00, M01, M10;469 // maskTemp = cvCloneImage(mask);470 // for(i=0, c=contours; c != NULL; c = c->h_next,i++ )471 // {472 // if(i < N) //Only process up to *num of them473 // {474 // cvDrawContours(maskTemp,c,CV_CVX_WHITE, CV_CVX_WHITE,-1,CV_FILLED,8);475 // //Find the center of each contour476 // if(centers != NULL)477 // {478 // cvMoments(maskTemp,&moments,1);479 // M00 = cvGetSpatialMoment(&moments,0,0);480 // M10 = cvGetSpatialMoment(&moments,1,0);481 // M01 = cvGetSpatialMoment(&moments,0,1);482 // centers[i].x = (int)(M10/M00);483 // centers[i].y = (int)(M01/M00);484 // }485 // //Bounding rectangles around blobs486 // if(bbs != NULL)487 // {488 // bbs[i] = cvBoundingRect(c);489 // }490 // cvZero(maskTemp);491 // numFilled++;492 // }493 // //Draw filled contours into mask494 // cvDrawContours(mask,c,CV_CVX_WHITE,CV_CVX_WHITE,-1,CV_FILLED,8); //draw to central mask495 // } //end looping over contours496 // *num = numFilled;497 // cvReleaseImage( &maskTemp);498 // }499 // //ELSE JUST DRAW PROCESSED CONTOURS INTO THE MASK500 // else501 // {502 // for( c=contours; c != NULL; c = c->h_next )503 // {504 // cvDrawContours(mask,c,CV_CVX_WHITE, CV_CVX_BLACK,-1,CV_FILLED,8);505 // }506 // }507 //}
联系客服