tornadomeet

前景检测算法_4(opencv自带GMM)

http://www.cnblogs.com/tornadomeet/archive/2012/06/02/2531705.html  

前面已经有3篇博文介绍了背景减图方面相关知识(见下面的链接),在第3篇博文中自己也实现了gmm简单算法,但效果不是很好,下面来体验下opencv自带2个gmm算法。

  opencv实现背景减图法1(codebook和平均背景法)

  http://www.cnblogs.com/tornadomeet/archive/2012/04/08/2438158.html

  opencv实现背景减图法2(帧差法)

  http://www.cnblogs.com/tornadomeet/archive/2012/05/01/2477629.html

  opencv实现背景减图法3(GMM)

  http://www.cnblogs.com/tornadomeet/archive/2012/06/02/2531565.html

  工程环境opencv2.3.1+vs2010

  实现功能:与上面第三篇博文一样,完成动态背景的训练,来检测前景。

  数据来源和前面的一样: http://research.microsoft.com/en-us/um/people/jckrumm/WallFlower/TestImages.htm 由于该数据是286张bmp格式的图片,所以用的前200张图片来作为GMM参数训练,后186张作为测试。训练的过程中树枝被很大幅度的摆动,测试过程中有行人走动,该行人是需要迁就检测的部分。

  Opencv自带的gmm算法1的实验结果如下:

  

  

  

  其工程代码如下:

  1 // gmm_wavetrees.cpp : 定义控制台应用程序的入口点。
2 //
3
4 #include "stdafx.h"
5
6 #include "opencv2/core/core.hpp"
7 #include "opencv2/video/background_segm.hpp"
8 #include "opencv2/highgui/highgui.hpp"
9 #include "opencv2/imgproc/imgproc.hpp"
10 #include <stdio.h>
11
12 using namespace std;
13 using namespace cv;
14
15 //this is a sample for foreground detection functions
16 string src_img_name="WavingTrees/b00";
17 const char *src_img_name1;
18 Mat img, fgmask, fgimg;
19 int i=-1;
20 char chari[500];
21 bool update_bg_model = true;
22 bool pause=false;
23
24 //第一种gmm,用的是KaewTraKulPong, P. and R. Bowden (2001).
25 //An improved adaptive background mixture model for real-time tracking with shadow detection.
26 BackgroundSubtractorMOG bg_model;
27
28 void refineSegments(const Mat& img, Mat& mask, Mat& dst)
29 {
30 int niters = 3;
31
32 vector<vector<Point> > contours;
33 vector<Vec4i> hierarchy;
34
35 Mat temp;
36
37 dilate(mask, temp, Mat(), Point(-1,-1), niters);//膨胀,3*3的element,迭代次数为niters
38 erode(temp, temp, Mat(), Point(-1,-1), niters*2);//腐蚀
39 dilate(temp, temp, Mat(), Point(-1,-1), niters);
40
41 findContours( temp, contours, hierarchy, CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE );//找轮廓
42
43 dst = Mat::zeros(img.size(), CV_8UC3);
44
45 if( contours.size() == 0 )
46 return;
47
48 // iterate through all the top-level contours,
49 // draw each connected component with its own random color
50 int idx = 0, largestComp = 0;
51 double maxArea = 0;
52
53 for( ; idx >= 0; idx = hierarchy[idx][0] )//这句没怎么看懂
54 {
55 const vector<Point>& c = contours[idx];
56 double area = fabs(contourArea(Mat(c)));
57 if( area > maxArea )
58 {
59 maxArea = area;
60 largestComp = idx;//找出包含面积最大的轮廓
61 }
62 }
63 Scalar color( 0, 255, 0 );
64 drawContours( dst, contours, largestComp, color, CV_FILLED, 8, hierarchy );
65 }
66
67 int main(int argc, const char** argv)
68 {
69 bg_model.noiseSigma = 10;
70 img=imread("WavingTrees/b00000.bmp");
71 if(img.empty())
72 {
73 namedWindow("image",1);//不能更改窗口
74 namedWindow("foreground image",1);
75 namedWindow("mean background image", 1);
76 }
77 for(;;)
78 {
79 if(!pause)
80 {
81 i++;
82 itoa(i,chari,10);
83 if(i<10)
84 {
85 src_img_name+="00";
86 }
87 else if(i<100)
88 {
89 src_img_name+="0";
90 }
91 else if(i>285)
92 {
93 i=-1;
94 }
95 if(i>=230)
96 update_bg_model=false;
97 else update_bg_model=true;
98
99 src_img_name+=chari;
100 src_img_name+=".bmp";
101
102 img=imread(src_img_name);
103 if( img.empty() )
104 break;
105
106 //update the model
107 bg_model(img, fgmask, update_bg_model ? 0.005 : 0);//计算前景mask图像,其中输出fgmask为8-bit二进制图像,第3个参数为学习速率
108 refineSegments(img, fgmask, fgimg);
109
110 imshow("image", img);
111 imshow("foreground image", fgimg);
112
113 src_img_name="WavingTrees/b00";
114
115 }
116 char k = (char)waitKey(80);
117 if( k == 27 ) break;
118
119 if( k == ' ' )
120 {
121 pause=!pause;
122 }
123 }
124
125 return 0;
126 }

  Opencv自带的gmm算法2的实验结果如下:

  

  

  

  其工程代码如下:

  1 // gmm2_wavetrees.cpp : 定义控制台应用程序的入口点。
2 //
3
4 #include "stdafx.h"
5
6 #include "opencv2/core/core.hpp"
7 #include "opencv2/video/background_segm.hpp"
8 #include "opencv2/highgui/highgui.hpp"
9 #include "opencv2/imgproc/imgproc.hpp"
10 #include <stdio.h>
11
12 using namespace std;
13 using namespace cv;
14
15 //this is a sample for foreground detection functions
16 string src_img_name="WavingTrees/b00";
17 const char *src_img_name1;
18 Mat img, fgmask, fgimg;
19 int i=-1;
20 char chari[500];
21 bool update_bg_model = true;
22 bool pause=false;
23
24 //第一种gmm,用的是KaewTraKulPong, P. and R. Bowden (2001).
25 //An improved adaptive background mixture model for real-time tracking with shadow detection.
26 BackgroundSubtractorMOG2 bg_model;
27
28 void refineSegments(const Mat& img, Mat& mask, Mat& dst)
29 {
30 int niters = 3;
31
32 vector<vector<Point> > contours;
33 vector<Vec4i> hierarchy;
34
35 Mat temp;
36
37 dilate(mask, temp, Mat(), Point(-1,-1), niters);
38 erode(temp, temp, Mat(), Point(-1,-1), niters*2);
39 dilate(temp, temp, Mat(), Point(-1,-1), niters);
40
41 findContours( temp, contours, hierarchy, CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE );
42
43 dst = Mat::zeros(img.size(), CV_8UC3);
44
45 if( contours.size() == 0 )
46 return;
47
48 // iterate through all the top-level contours,
49 // draw each connected component with its own random color
50 int idx = 0, largestComp = 0;
51 double maxArea = 0;
52
53 for( ; idx >= 0; idx = hierarchy[idx][0] )
54 {
55 const vector<Point>& c = contours[idx];
56 double area = fabs(contourArea(Mat(c)));
57 if( area > maxArea )
58 {
59 maxArea = area;
60 largestComp = idx;
61 }
62 }
63 Scalar color( 255, 0, 0 );
64 drawContours( dst, contours, largestComp, color, CV_FILLED, 8, hierarchy );
65 }
66
67 int main(int argc, const char** argv)
68 {
69 img=imread("WvingTrees/b00000.bmp");
70 if(img.empty())
71 {
72 namedWindow("image",1);//不能更改窗口
73 //cvNamedWindow("image",0);
74 namedWindow("foreground image",1);
75 // namedWindow("mean background image", 1);
76 }
77 for(;;)
78 {
79 if(!pause)
80 {
81 i++;
82 itoa(i,chari,10);
83 if(i<10)
84 {
85 src_img_name+="00";
86 }
87 else if(i<100)
88 {
89 src_img_name+="0";
90 }
91 else if(i>285)
92 {
93 i=-1;
94 }
95 // if(i>=230)
96 // update_bg_model=false;
97 // else update_bg_model=true;
98
99 src_img_name+=chari;
100 src_img_name+=".bmp";
101
102 img=imread(src_img_name);
103 if( img.empty() )
104 break;
105
106 //update the model
107 bg_model(img, fgmask, update_bg_model ? 0.005 : 0);//计算前景mask图像,其中输出fgmask为8-bit二进制图像,第3个参数为学习速率
108 refineSegments(img, fgmask, fgimg);
109
110 imshow("foreground image", fgimg);
111 imshow("image", img);
112
113 src_img_name="WavingTrees/b00";
114
115 }
116 char k = (char)waitKey(100);
117 if( k == 27 ) break;
118
119 if( k == ' ' )
120 {
121 pause=!pause;
122 }
123 }
124
125 return 0;
126 }

  可以看出gmm1效果比gmm2的好,但是研究发现,gmm2是在gmm1上改进的,不会越该越差吧,除非2个函数的使用方法不同(虽然函数形式一样),也就是说相同的参数值对函数功能的影响不同。以后有时间在研究了。


tornadomeet

前景检测算法_3(GMM)

http://www.cnblogs.com/tornadomeet/archive/2012/06/02/2531565.html

摘要

  本文通过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基本底层函数实现该算法。其主要过程如下:

  1. 首先将每个高斯的均值,方差,权值都设置为0,即初始化个模型矩阵参数。
  2. 采用视频中的T帧用来训练GMM模型。对每一个像素而言,建立其模型个数最大GMM_MAX_COMPONT个高斯的GMM模型。当第一个像素来,单独为其在程序中设置好其固定的初始均值,方差,并且权值设置为1。
  3. 非第一帧训练过程中,当后面来的像素值时,与前面已有的高斯的均值比较,如果该像素点的值与其模型均值差在3倍的方差内,则任务属于该高斯。此时用如下方程进行更新:

    

  4. 当到达训练的帧数T后,进行不同像素点GMM个数自适应的选择。首先用权值除以方差对各个高斯进行从大到小排序,然后选取最前面B个高斯,使

  

  这样就可以很好的消除训练过程中的噪声点。

  5. 在测试阶段,对新来像素点的值与B个高斯中的每一个均值进行比较,如果其差值在2倍的方差之间的话,则认为是背景,否则认为是前景。并且只要其中有一个高斯分量满足该条件就认为是前景。前景赋值为255,背景赋值为0。这样就形成了一副前景二值图。

  6. 由于前景二值图中含有很多噪声,所以采用了形态学的开操作将噪声缩减到0,紧接着用闭操作重建由于开操作丢失的边缘部分的信息。消除了不连通的小噪声点。

  上面是该算法实现的大概流程,但是当我们在具体编程时,还是有很多细节的地方需要注意,比如有些参数值的选择。在这里,经过试验将一些常用的参数值声明如下:

  1. 3个参数的值的更新方差中,取其中的学习率为0.005。也就是说T等于200。
  2. 定义每个像素的最大混合高斯个数取7。
  3. 取视频的前200帧进行训练。
  4. 取Cf为0.3。即满足权值大于0.7的个数为自适应高斯的个数B。
  5. 训练过程中,需要新建立一个高斯时,其权值取值设为与学习率大小值相等,即0.005。
  6. 训练过程中,需要新建立一个高斯时,取该高斯的均值为该输入像素值大小本身。方差为15。
  7. 训练过程中,需要新建立一个高斯时,取该高斯的方差15。

   程序流程框图

  该工程的流程框图如下图所示:

                                        

                          

                                                  

                                      试验结果

 

  本次试验的数据为摇摆的树枝作为背景,Waving Trees,其来源网址为:http://research.microsoft.com/en-us/um/people/jckrumm/WallFlower/TestImages.htm 由于该数据是286张bmp格式的图片,所以用的前200张图片来作为GMM参数训练,后186张作为测试。训练的过程中树枝被很大幅度的摆动,测试过程中有行人走动,该行人是需要迁就检测的部分。

  下图为训练过程中动态背景截图

  

  由上图可以看出,树枝在不断摇摆,可见其背景是动态的。

  没有形态学处理的结果如下:

  

下图是有简单形态学的试验结果:

  

(上面2幅图的左边为测试原始图片,右图为检测效果图)

总结

  通过本次试验,不仅学习到了GMM的相关理论知识,以及背景减图法在前景检测中的应用。更重要的时,对opencv在计算机视觉中的使用更进一步的熟悉了。另外对于目标检测的难点有了更深一层的理解。

参考文献

  1. Stauffer, C. and W. E. L. Grimson (1999). Adaptive background mixture models for real-time tracking, IEEE.
  2. KaewTraKulPong, P. and R. Bowden (2001). An improved adaptive background mixture model for real-time tracking with shadow detection.
  3. Zivkovic, Z. and F. van der Heijden (2004). "Recursive unsupervised learning of finite mixture models." Pattern Analysis and Machine Intelligence, IEEE Transactions on 26(5): 651-656.
  4. Zivkovic, Z. and F. van der Heijden (2006). "Efficient adaptive density estimation per image pixel for the task of background subtraction." Pattern recognition letters 27(7): 773-780.
  5. Bouzerdoum, A., A. Beghdadi, et al. (2010). "On the analysis of background subtraction techniques using Gaussian mixture models."
  6. Lin, H. H., J. H. Chuang, et al. (2011). "Regularized background adaptation: a novel learning rate control scheme for Gaussian mixture modeling." Image Processing, IEEE Transactions on 20(3): 822-836.

   后续改进

  需要改进的地方:

  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,deprecated
104 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 model
109 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 image
119
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 phase
127 //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 phase
132
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 proggram
165 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,其实内部存储的也是0
182 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 int
201 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.0
203
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 component
230 {
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/sigma
257 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 componen
284 {
285 //不能写为for(int h=0;h<MAX_GMM_COMPONT&&((0==w[h].at<float>(m,n)));h++),因为这样明显h不会每次都加1
286 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.0
293 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 1
297 }
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.0
307 }
308
309
310 }
311 }//end the train phase
312
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_num
323 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,因为每个像素都有一个权值接近1
328 {
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 cvbackgroundDiff
408 //
409 // mask Is a grayscale (8 bit depth) "raw" mask image which will be cleaned up
410 //
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 MASK
425 // //开运算作用:平滑轮廓,去掉细节,断开缺口
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 REGIONS
431 // 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 threshold
443 // if( len < q ) //Get rid of blob if it's perimeter is too small
444 // {
445 // cvSubstituteContour( scanner, NULL );
446 // }
447 // else //Smooth it's edges if it's large enough
448 // {
449 // CvSeq* c_new;
450 // if(poly1_hull0) //Polygonal approximation of the segmentation
451 // c_new = cvApproxPoly(c,sizeof(CvContour),mem_storage,CV_POLY_APPROX_DP, CVCONTOUR_APPROX_LEVEL,0);
452 // else //Convex Hull of the segmentation
453 // 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 IMAGE
461 // cvZero( mask );
462 // IplImage *maskTemp;
463 // //CALC CENTER OF MASS AND OR BOUNDING RECTANGLES
464 // 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 them
473 // {
474 // cvDrawContours(maskTemp,c,CV_CVX_WHITE, CV_CVX_WHITE,-1,CV_FILLED,8);
475 // //Find the center of each contour
476 // 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 blobs
486 // if(bbs != NULL)
487 // {
488 // bbs[i] = cvBoundingRect(c);
489 // }
490 // cvZero(maskTemp);
491 // numFilled++;
492 // }
493 // //Draw filled contours into mask
494 // cvDrawContours(mask,c,CV_CVX_WHITE,CV_CVX_WHITE,-1,CV_FILLED,8); //draw to central mask
495 // } //end looping over contours
496 // *num = numFilled;
497 // cvReleaseImage( &maskTemp);
498 // }
499 // //ELSE JUST DRAW PROCESSED CONTOURS INTO THE MASK
500 // else
501 // {
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 //}

计算机视觉小菜鸟的专栏

混合高斯模型GMM

http://www.cnblogs.com/tornadomeet/archive/2012/06/02/2531565.html

运动目标检测可以分为摄像机固定和摄像机运动两类;对于摄像机运动情况下的运动目标检测,光流法是比较常用的解决方法,通过求解偏微分方程求得图像序列的光流场,从而预测摄像机的运动状态。对于摄像机固定的情形,可以采用光流法也可以采用高斯背景模型,考虑到光流法计算量巨大,故而,高斯背景模型相对更常用一些。需要提醒的是,这里所谓的“背景”是指用户不需要的目标,而“前景”自然指代用户需要的特定目标了。背景模型有很多种,其中很多方法对光照的的突变和其它因素的适应能力不够,而高斯混合模型是最为成功的一种背景建模方法。

高斯背景模型是由Stauffer等人提出的经典的自适应混合高斯背景提取方法,是一种基于背景建模的方法,它是根据视频中的每个像素在时域上的分布情况来构建各个像素的颜色分布模型,依次来达到背景建模的目的。混合高斯背景模型是有限个高斯函数的加权和,它能描述像素的多峰状态,适用于对光照渐变、树木摇摆等复杂背景进行准确建模。此后经过很多研究人员的不断改进,该方法目前已经成为比较常用的背景提取方法。


[综]前景检测GMM的更多相关文章

  1. 运动检测(前景检测)之(二)混合高斯模型GMM

    运动检测(前景检测)之(二)混合高斯模型GMM zouxy09@qq.com http://blog.csdn.net/zouxy09 因为监控发展的需求,目前前景检测的研究还是很多的,也出现了很多新 ...

  2. [转]运动检测(前景检测)之(二)混合高斯模型GMM

    转自:http://blog.csdn.net/zouxy09/article/details/9622401 因为监控发展的需求,目前前景检测的研究还是很多的,也出现了很多新的方法和思路.个人了解的 ...

  3. [转]前景检测算法--ViBe算法

    原文:http://blog.csdn.net/zouxy09/article/details/9622285 转自:http://blog.csdn.net/app_12062011/article ...

  4. 运动检测(前景检测)之(一)ViBe

    运动检测(前景检测)之(一)ViBe zouxy09@qq.com http://blog.csdn.net/zouxy09 因为监控发展的需求,目前前景检测的研究还是很多的,也出现了很多新的方法和思 ...

  5. paper 83:前景检测算法_1(codebook和平均背景法)

    前景分割中一个非常重要的研究方向就是背景减图法,因为背景减图的方法简单,原理容易被想到,且在智能视频监控领域中,摄像机很多情况下是固定的,且背景也是基本不变或者是缓慢变换的,在这种场合背景减图法的应用 ...

  6. ViBe(Visual Background extractor)背景建模或前景检测

    ViBe算法:ViBe - a powerful technique for background detection and subtraction in video sequences 算法官网: ...

  7. 目标检测之vibe---ViBe(Visual Background extractor)背景建模或前景检测

    ViBe算法:ViBe - a powerful technique for background detection and subtraction in video sequences 算法官网: ...

  8. VIBE(前景检测)

    1.VIBE思想: 为每个像素点存储了一个样本集,样本集中采样值就是该像素点过去的像素值和其邻居点的像素值,然后将每一个新的像素值和样本集进行比较来判断是否属于背景点. 2.VIBE模型初始化 通用的 ...

  9. 运动目标前景检测之ViBe源代码分析

    一方面为了学习,一方面按照老师和项目的要求接触到了前景提取的相关知识,具体的方法有很多,帧差.背景减除(GMM.CodeBook. SOBS. SACON. VIBE. W4.多帧平均……).光流(稀 ...

随机推荐

  1. vs2010无可用源

    全选CPP文件内容,选择 “编辑”-“高级”-“设置选定内容的格式”,保存,重新编译. 当然这种方法是不能完全解决这个问题的奥,你需要在菜单栏的生成里面找到重新生成解决方案,重新生成解决方案试一下啦, ...

  2. bookstore网上书店测试缺陷报告2

    Bookstore网上书店系统测试缺陷报告   缺陷编号 01.01.0002 发现人 吴赵昕 记录日期 2016-06-10 所属模块 购物车 确认人 吴赵昕 确认日期 2016-06-10 当前状 ...

  3. Python笔记——类定义

    Python笔记——类定义 一.类定义: class <类名>: <语句> 类实例化后,可以使用其属性,实际上,创建一个类之后,可以通过类名访问其属性 如果直接使用类名修改其属 ...

  4. Code Review Tools

    Code Review中文应该译作“代码审查”或是“代码评审”,这是一个流程,当开发人员写好代码后,需要让别人来review一下他的代码,这是一种有效发现BUG的方法.由此,我们可以审查代码的风格.逻 ...

  5. The Swift Programming Language 英文原版官方文档下载

    The Swift Programming Language 英文原版官方文档下载 今天Apple公司发布了新的编程语言Swift(雨燕)将逐步代替Objective-C语言,大家肯定想学习这个语言, ...

  6. MCMC: The Metropolis Sampler

    本文主要译自 MCMC: The Metropolis Sampler 正如之前的文章讨论的,我们可以用一个马尔可夫链来对目标分布 \(p(x)\) 进行采样,通常情况下对于很多分布 \(p(x)\) ...

  7. LintCode Edit Distance

    LintCode Edit Distance Given two words word1 and word2, find the minimum number of steps required to ...

  8. [转]恢复 git reset -hard 的误操作

    转帖:http://hi.baidu.com/configuration/item/97fddeea252818d0eb34c964 有时候使用Git工作得小心翼翼,特别是涉及到一些高级操作,例如 r ...

  9. 最新AFNetworking

    1.网络监测 /** * 网络检测 */ - (void)networkingMonitoring { //打开网络监测 [[AFNetworkReachabilityManager sharedMa ...

  10. split,slice,splice,replace的用法

    split()方法用于把一个字符串分割成字符串数组 str.split("字符串/正则表达式从该参数制定额地方分割str",可选,可指定返回数组的最大长度,如果没设置参数,整个字符 ...