标签:
摘要
本文通过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