[转]前景检测算法--ViBe算法
原文:http://blog.csdn.net/zouxy09/article/details/9622285
转自:http://blog.csdn.net/app_12062011/article/details/51866319
因为监控发展的需求,目前前景检测的研究还是很多的,也出现了很多新的方法和思路。个人了解的大概概括为以下一些:
帧差、背景减除(GMM、CodeBook、 SOBS、 SACON、 VIBE、 W4、多帧平均……)、光流(稀疏光流、稠密光流)、运动竞争(Motion Competition)、运动模版(运动历史图像)、时间熵……等等。如果加上他们的改进版,那就是很大的一个家族了。
对于上一些方法的一点简单的对比分析可以参考下:
http://www.cnblogs.com/ronny/archive/2012/04/12/2444053.html
至于哪个最好,看使用环境吧,各有千秋,有一些适用的情况更多,有一些在某些情况下表现更好。这些都需要针对自己的使用情况作测试确定的。呵呵。
推荐一个牛逼的库:http://code.google.com/p/bgslibrary/里面包含了各种背景减除的方法,可以让自己少做很多力气活。
还有王先荣博客上存在不少的分析:
http://www.cnblogs.com/xrwang/archive/2010/02/21/ForegroundDetection.html
下面的博客上转载王先荣的上面几篇,然后加上自己分析了两篇:
本文主要关注其中的一种背景减除方法:ViBe。stellar0的博客上对ViBe进行了分析,我这里就不再啰嗦了,具体的理论可以参考:
http://www2.ulg.ac.be/telecom/research/vibe/
http://blog.csdn.net/stellar0/article/details/8777283
http://blog.csdn.net/yongshengsilingsa/article/details/6659859
http://www2.ulg.ac.be/telecom/research/vibe/download.html
http://www.cvchina.info/2011/12/25/vibe/
ViBe是一种像素级的背景建模、前景检测算法,该算法主要不同之处是背景模型的更新策略,随机选择需要替换的像素的样本,随机选择邻域像素进行更新。在无法确定像素变化的模型时,随机的更新策略,在一定程度上可以模拟像素变化的不确定性。
背景模型的初始化
初始化是建立背景模型的过程,一般的检测算法需要一定长度的视频序列学习完成,影响了检测的实时性,而且当视频画面突然变化时,重新学习背景模型需要较长时间。
ViBe算法主要是利用单帧视频序列初始化背景模型,对于一个像素点,结合相邻像素点拥有相近像素值的空间分布特性,随机的选择它的邻域点的像素值作为它的模型样本值。
优点:不仅减少了背景模型建立的过程,还可以处理背景突然变化的情况,当检测到背景突然变化明显时,只需要舍弃原始的模型,重新利用变化后的首帧图像建立背景模型。
缺点:由于可能采用了运动物体的像素初始化样本集,容易引入拖影(Ghost)区域。
前景检测过程
背景模型为每个背景点存储一个样本集,然后每个新的像素值和样本集比较判断是否属于背景。
计算新像素值和样本集中每个样本值的距离,若距离小于阈值,则近似样本点数目增加。
如果近似样本点数目大于阈值,则认为新的像素点为背景。
检测过程主要由三个参数决定:样本集数目N,阈值#min和距离相近判定的阈值R,一般具体实现,参数设置为N=20,#min=2,R=20。
背景模型的更新策略
1).无记忆更新策略
每次确定需要更新像素点的背景模型时,以新的像素值随机取代该像素点样本集的一个样本值。
2).时间取样更新策略
并不是每处理一帧数据,都需要更新处理,而是按一定的更新率更新背景模型。当一个像素点被判定为背景时,它有1/rate的概率更新背景模型。rate是时间采样因子,一般取值为16。
3).空间邻域更新策略
针对需要更新像素点,随机的选择一个该像素点邻域的背景模型,以新的像素点更新被选中的背景模型。
ViBe的改进
1).距离计算方法
以圆椎模型代替原来的几何距离计算方法
以自适应阈值代替原来固定的距离判定阈值,阈值大小与样本集的方差成正比,样本集方差越大,说明背景越复杂,判定阈值应该越大。
2).分离updating mask和segmentation mask
引入目标整体的概念,弥补基于像素级前景检测的不足。针对updating mask和segmentation mask采用不同尺寸的形态学处理方法,提高检测准确率。
3).抑制邻域更新
在updating mask里,计算像素点的梯度,根据梯度大小,确定是否需要更新邻域。梯度值越大,说明像素值变化越大,说明该像素值可能为前景,不应该更新。
4).检测闪烁像素点
引入闪烁程度的概念,当一个像素点的updating label与前一帧的updating label不一样时,blinking level增加15,否则,减少1,然后根据blinking level的大小判断该像素点是否为闪烁点。闪烁像素主要出现在背景复杂的场景,如树叶、水纹等,这些场景会出现像素背景和前景的频繁变化,因而针对这些闪烁应该单独处理,可以作为全部作为背景。
5).增加更新因子
ViBe算法中,默认的更新因子是16,当背景变化很快时,背景模型无法快速的更新,将会导致前景检测的较多的错误。因而,需要根据背景变化快慢程度,调整更新因子的大小,可将更新因子分多个等级,如rate = 16,rate = 5,rate = 1。
1)VIBE-A powerful random technique to estimatie the background in video sequences.
2) VIBE-A universal background subtraction algorithms for video sequences
VIBE的头文件Vibe.hpp如下:
1 #pragma once 2 #include "stdafx.h" 3 #define WINSIZE 3 4 5 class Vibe 6 { 7 public: 8 Vibe(void); 9 Vibe(IplImage *img); 10 void SetMinMatch(int nthreshold){g_MinMatch=nthreshold;} 11 void SetRadius(int radius){g_Radius=radius;} 12 void SetSampleNum(int num){g_SampleNum=num;} 13 void SetThreshold(double t){g_threshold=t;} 14 IplImage* GetForeground(){return g_ForeImg;} 15 IplImage* GetSegMask(){return g_SegementMask;} 16 void Detect(IplImage *img); 17 void ForegroundCombineEdge(); // 结合边缘信息 18 void DeleteSmallAreaInForeground(double minArea=20);//删除小面积区域 19 // 实现背景更新机制 20 void Update(); 21 // 实现后处理,主要用形态学算子 22 void PostProcess(); 23 24 public: 25 ~Vibe(void); 26 27 private: 28 void ClearLongLifeForeground(int i_lifeLength=200); // 清除场景中存在时间较长的像素,i_lifeLength用于控制允许存在的最长时间 29 double AreaDense(IplImage *pFr,int AI,int AJ,int W,int H); //计算(i,j)处邻域大小为W×H的密度 30 int GetRandom(int istart,int iend); // 默认istart=0,iend=15 31 int GetRandom(int random); 32 int GetRandom();// 产生一个随机数 33 // 计算两个像素之间的欧式距离 34 double CalcPixelDist(CvScalar bkCs,CvScalar curCs); 35 // 按照Kim的方法来计算颜色畸变 36 double CalcuColorDist(CvScalar bkCs,CvScalar curCs); 37 int g_SampleNum;// Sample number for the models,默认为20 38 int g_MinMatch; // 当前像素与背景模型匹配的最少个数,默认为2 39 int g_Height; 40 int g_Width; 41 int g_Radius;// 球体的半径,默认为20 42 int g_offset; //边界的宽和高 43 double g_threshold; // 距离度量的阈值 44 unsigned char ***g_Model;// 保存背景模型 45 IplImage *g_ForeImg;// 保存前景图 46 IplImage *g_Edge; 47 48 IplConvKernel* element; 49 50 IplImage *g_SegementMask; //分割掩膜 51 IplImage *g_UpdateMask; // 更新掩膜 52 IplImage *g_Gray; 53 int ** LifeLength; // 记录前景点的生命长度,如果前景点的生命长度到达一定的阈值,则将其融入背景中去,且要随机两次。 54 };
对应的实现文件如下Vibe.cpp所示:
1 #include "StdAfx.h" 2 #include "Vibe.h" 3 4 Vibe::Vibe(void) 5 { 6 g_Radius=20; 7 g_MinMatch=2; 8 g_SampleNum=20; 9 g_offset=(WINSIZE-1)/2; 10 11 } 12 13 Vibe::Vibe(IplImage *img) 14 { 15 if (!img) 16 { 17 cout<<" The parameter referenced to NUll Pointer!"<<endl; 18 return; 19 } 20 this->g_Height=img->height; 21 this->g_Width=img->width; 22 23 g_Radius=20; 24 g_MinMatch=2; 25 g_SampleNum=20; 26 g_threshold=50; 27 g_offset=(WINSIZE-1)/2; 28 29 g_ForeImg=cvCreateImage(cvGetSize(img),IPL_DEPTH_8U,1); 30 g_Gray=cvCreateImage(cvGetSize(img),IPL_DEPTH_8U,1); 31 g_Edge=cvCreateImage(cvGetSize(img),IPL_DEPTH_8U,1); 32 g_SegementMask=cvCreateImage(cvGetSize(img),IPL_DEPTH_8U,1); 33 g_UpdateMask=cvCreateImage(cvGetSize(img),IPL_DEPTH_8U,1); 34 35 element=cvCreateStructuringElementEx(3,3,1,1,CV_SHAPE_CROSS,NULL); 36 37 cvCvtColor(img,g_Gray,CV_BGR2GRAY); 38 39 // 以上完成相关的初始化操作 40 /********************** 以下实现第一帧在每个像素的8邻域内的采样功能,建立对应的背景模型*****************************/ 41 42 int i=0,j=0,k=0; 43 g_Model=new unsigned char**[g_SampleNum]; 44 for (k=0;k<g_SampleNum;k++) 45 { 46 g_Model[k]=new unsigned char *[g_Height]; 47 for(i=0;i<g_Height;i++) 48 { 49 g_Model[k][i]=new unsigned char [g_Width]; 50 for (j=0;j<g_Width;j++) 51 { 52 g_Model[k][i][j]=0; 53 } 54 } 55 } 56 57 // 采样进行背景建模 58 double dVal; 59 int ri=0,rj=0; //随机采样的值 60 for (i=g_offset;i<g_Height-g_offset;i++) 61 { 62 for (j=g_offset;j<g_Width-g_offset;j++) 63 { 64 // 周围3*3的邻域内进行采样 65 for(k=0;k<g_SampleNum;k++) 66 { 67 ri=GetRandom(i); 68 rj=GetRandom(j); 69 dVal=cvGetReal2D(g_Gray,ri,rj); 70 g_Model[k][i][j]=dVal; 71 } 72 } 73 } 74 75 // 初始化前景点掩膜的生命长度 76 LifeLength=new int *[g_Height]; 77 for (i=0;i<g_Height;i++) 78 { 79 LifeLength[i]=new int [g_Width]; 80 for(j=0;j<g_Width;j++) 81 { 82 LifeLength[i][j]=0; 83 } 84 } 85 } 86 87 88 void Vibe::Detect(IplImage *img) 89 { 90 cvZero(g_ForeImg); 91 cvCvtColor(img,g_Gray,CV_BGR2GRAY); 92 int i=0,j=0,k=0; 93 double dModVal,dCurrVal; 94 int tmpCount=0;// 距离比较在阈值内的次数 95 double tmpDist=0; 96 int iR1,iR2;//产生随机数 97 int Ri,Rj; // 产生邻域内X和Y的随机数 98 99 for (i=0;i<g_Height;i++) 100 { 101 for (j=0;j<g_Width;j++) 102 { 103 if( i < g_offset || j < g_offset || i> g_Height - g_offset || j> g_Width - g_offset ) 104 { 105 cvSetReal2D(g_ForeImg,i,j,0); 106 continue; 107 } 108 else 109 { 110 tmpCount=0; 111 dCurrVal=cvGetReal2D(g_Gray,i,j); 112 for (k=0;k<g_SampleNum && tmpCount<g_MinMatch ;k++) 113 { 114 dModVal=g_Model[k][i][j]; 115 //tmpDist=CalcPixelDist(dCurrVal,dModVal); 116 //tmpDist=CalcuColorDist(dCurrVal,dModVal); 117 tmpDist=fabs(dModVal-dCurrVal); 118 if (tmpDist<g_Radius) 119 { 120 tmpCount++; 121 } 122 } 123 124 //判断是否匹配上 125 if (tmpCount>=g_MinMatch) 126 { 127 cvSetReal2D(g_ForeImg,i,j,0); 128 // 背景模型的更新 129 iR1=GetRandom(0,15); 130 if (iR1==0) 131 { 132 iR2=GetRandom(); 133 g_Model[iR2][i][j]=dCurrVal; 134 } 135 136 //进一步更新邻域模型 137 138 iR1=GetRandom(0,15); 139 if (iR1==0) 140 { 141 Ri=GetRandom(i); 142 Rj=GetRandom(j); 143 iR2=GetRandom(); 144 g_Model[iR2][Ri][Rj]=dCurrVal; 145 } 146 } 147 else 148 { 149 cvSetReal2D(g_ForeImg,i,j,255); 150 } 151 } 152 } 153 } 154 155 //ForegroundCombineEdge(); 156 DeleteSmallAreaInForeground(80); 157 ClearLongLifeForeground(); 158 //PostProcess(); 159 } 160 161 double Vibe::AreaDense(IplImage *pFr,int AI,int AJ,int W,int H) 162 { 163 if (AI<=2 || AJ<=2 || AJ>=(g_Width-2) || AI>=(g_Height-2)) 164 { 165 return 0; 166 } 167 int Num=0,i=0,j=0; 168 double dVal=0,dense=0; 169 int Total=(2*H+1)*(2*W+1); 170 for (i=AI-H;i<=AI+H;i++) 171 { 172 for (j=AJ-W;j<=AJ+W;j++) 173 { 174 dVal=cvGetReal2D(pFr,i,j); 175 if (dVal>200) 176 { 177 Num++; 178 } 179 } 180 } 181 dense=(double)Num/(double)Total; 182 return dense; 183 } 184 185 void Vibe::ForegroundCombineEdge() 186 { 187 cvZero(g_Edge); 188 //cvZero(g_SegementMask); 189 //cvCopy(g_ForeImg,g_SegementMask); 190 cvCanny(g_Gray,g_Edge,30,200,3); 191 int i=0,j=0; 192 double dense; 193 double dVal; 194 for (i=g_offset;i<g_Height-g_offset;i++) 195 { 196 for (j=g_offset;j<g_Width-g_offset;j++) 197 { 198 dense=AreaDense(g_ForeImg,i,j,2,2); 199 dVal=cvGetReal2D(g_Edge,i,j); 200 if (dense>0.2 && dVal>200) 201 { 202 cvSetReal2D(g_ForeImg,i,j,255); 203 } 204 } 205 } 206 207 } 208 209 210 void Vibe::DeleteSmallAreaInForeground(double minArea/* =20 */) 211 { 212 //cvZero(g_SegementMask); 213 //cvCopy(g_ForeImg,g_SegementMask); 214 int region_count = 0; 215 CvSeq *first_seq = NULL, *prev_seq = NULL, *seq = NULL; 216 CvMemStorage* storage = cvCreateMemStorage(); 217 cvClearMemStorage(storage); 218 cvFindContours( g_ForeImg, storage, &first_seq, sizeof(CvContour), CV_RETR_LIST ); 219 for( seq = first_seq; seq; seq = seq->h_next ) 220 { 221 CvContour* cnt = (CvContour*)seq; 222 if( cnt->rect.width * cnt->rect.height < minArea ) 223 { 224 prev_seq = seq->h_prev; 225 if( prev_seq ) 226 { 227 prev_seq->h_next = seq->h_next; 228 if( seq->h_next ) seq->h_next->h_prev = prev_seq; 229 } 230 else 231 { 232 first_seq = seq->h_next; 233 if( seq->h_next ) seq->h_next->h_prev = NULL; 234 } 235 } 236 else 237 { 238 region_count++; 239 } 240 } 241 cvZero(g_ForeImg); 242 cvDrawContours(g_ForeImg, first_seq, CV_RGB(0, 0, 255), CV_RGB(0, 0, 255), 10, -1); 243 244 /* 245 CvContourScanner scanner = cvStartFindContours( g_ForeImg, storage,sizeof(CvContour), CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE, cvPoint(0,0) ); 246 CvSeq *contours=NULL,*c=NULL; 247 int poly1Hull0=0; 248 int nContours=0; 249 double perimScale=100; 250 while( (c = cvFindNextContour( scanner )) != 0 ) 251 { 252 double len = cvContourPerimeter( c ); 253 double q = (g_ForeImg->height + g_ForeImg->width)/perimScale; // calculate perimeter len threshold 254 if( len < q ) //Get rid of blob if it's perimeter is too small 255 cvSubstituteContour( scanner, 0 ); 256 else //Smooth it's edges if it's large enough 257 { 258 CvSeq* newC; 259 if( poly1Hull0 ) //Polygonal approximation of the segmentation 260 newC = cvApproxPoly( c, sizeof(CvContour), storage, CV_POLY_APPROX_DP, 2, 0 ); 261 else //Convex Hull of the segmentation 262 newC = cvConvexHull2( c, storage, CV_CLOCKWISE, 1 ); 263 cvSubstituteContour( scanner, newC ); 264 nContours++; 265 } 266 } 267 contours = cvEndFindContours( &scanner ); 268 // paint the found regions back into the image 269 cvZero( g_ForeImg ); 270 for( c=contours; c != 0; c = c->h_next ) 271 cvDrawContours( g_ForeImg, c, cvScalarAll(255), cvScalarAll(0), -1, CV_FILLED, 8,cvPoint(0,0)); 272 */ 273 274 cvReleaseMemStorage(&storage); 275 } 276 277 void Vibe::ClearLongLifeForeground(int i_lifeLength/* =200 */) 278 { 279 int i=0,j=0; 280 double dVal=0; 281 double dLife=0; 282 int iR1,iR2=0; 283 double dCurrVal=0; 284 for (i=g_offset;i<g_Height-g_offset;i++) 285 { 286 for (j=g_offset;j<g_Width-g_offset;j++) 287 { 288 dVal=cvGetReal2D(g_ForeImg,i,j); 289 dLife=LifeLength[i][j]; 290 if (dLife>i_lifeLength) 291 { 292 LifeLength[i][j]=0; 293 dCurrVal=cvGetReal2D(g_Gray,i,j); 294 // 更新背景模型 295 iR1=GetRandom(); 296 iR2=GetRandom(); 297 g_Model[iR1][i][j]=dCurrVal; 298 g_Model[iR2][i][j]=dCurrVal; 299 } 300 else 301 { 302 LifeLength[i][j]=dLife+1; 303 } 304 305 } 306 } 307 } 308 309 void Vibe::Update() 310 { 311 cvZero(g_UpdateMask); 312 313 } 314 315 void Vibe::PostProcess() 316 { 317 cvZero(g_SegementMask); 318 cvMorphologyEx(g_ForeImg,g_SegementMask,NULL,element,CV_MOP_OPEN,1); 319 320 } 321 322 //算颜色畸变 323 double Vibe::CalcuColorDist(CvScalar bkCs,CvScalar curCs) 324 { 325 double r,g,b,br,bg,bb; 326 r=curCs.val[0]; 327 g=curCs.val[1]; 328 b=curCs.val[2]; 329 330 br=bkCs.val[0]; 331 bg=bkCs.val[1]; 332 bb=bkCs.val[2]; 333 334 double curDist=r*r+g*g*b*b; 335 double bkDist=br*br+bg*bg+bb*bb; 336 337 double curBK=r*br+g*bg+b*bb; 338 double curbkDist=curBK*curBK; 339 double SquareP; 340 if (bkDist==0.0) 341 { 342 SquareP=0; 343 } 344 else 345 { 346 SquareP=curbkDist/bkDist; 347 } 348 double dist=sqrtf(curDist-SquareP); 349 return dist; 350 } 351 352 double Vibe::CalcPixelDist(CvScalar bkCs,CvScalar curCs) 353 { 354 double tmpDist=pow(bkCs.val[0]-curCs.val[0],2)+pow(bkCs.val[1]-curCs.val[1],2)+pow(bkCs.val[2]-curCs.val[2],2); 355 return sqrtf(tmpDist); 356 } 357 358 int Vibe::GetRandom() 359 { 360 int val = g_SampleNum * 1.0 * rand() / RAND_MAX; 361 if( val == g_SampleNum ) 362 return val - 1; 363 else 364 return val; 365 } 366 367 int Vibe::GetRandom(int random) 368 { 369 int val=random-g_offset+rand()%(2*g_offset); 370 if (val<random-g_offset) 371 { 372 val=random-g_offset; 373 } 374 if (val>random+g_offset) 375 { 376 val=random+g_offset; 377 } 378 return val; 379 } 380 381 int Vibe::GetRandom(int istart,int iend) 382 { 383 int val=istart+rand()%(iend-istart); 384 return val; 385 } 386 387 388 Vibe::~Vibe(void) 389 { 390 if (g_ForeImg) 391 { 392 cvReleaseImage(&g_ForeImg); 393 } 394 if (g_SegementMask) 395 { 396 cvReleaseImage(&g_SegementMask); 397 } 398 if (g_UpdateMask) 399 { 400 cvReleaseImage(&g_UpdateMask); 401 } 402 if (g_Gray) 403 { 404 cvReleaseImage(&g_Gray); 405 } 406 407 if (g_Model!=NULL) 408 { 409 delete[]g_Model; 410 g_Model=NULL; 411 } 412 }
最后附上调用的main函数:
1 int _tmain(int argc, _TCHAR* argv[]) 2 { 3 CvCapture *capture=NULL; 4 IplImage* frame=NULL; 5 IplImage* pForeImg=NULL; 6 IplImage* segImg=NULL; 7 8 char *file_path="E:\\testVideo\\VTS_01_4.avi"; // m1 test2 锦带河 VTS_01_4_2 head rear VTS_01_6_2 VTS_01_4 9 //const char* file_path="E:\\suntektechvideo\\锦带河.avi"; //test2 10 11 capture=cvCreateFileCapture(file_path); 12 if (!capture) 13 { 14 //cout<<"Read Video File Error!"<<endl; 15 return -1; 16 } 17 frame=cvQueryFrame(capture); 18 frame=cvQueryFrame(capture); 19 20 cvNamedWindow("img",1); 21 cvNamedWindow("foreN",1); 22 //cvNamedWindow("seg",1); 23 24 Vibe* pV=new Vibe(frame); 25 26 while(frame=cvQueryFrame(capture)) 27 { 28 pV->Detect(frame); 29 pForeImg=pV->GetForeground(); 30 //segImg=pV->GetSegMask(); 31 //frame->origin=1; 32 //pForeImg->origin=1; 33 cvShowImage("img",frame); 34 cvShowImage("foreN",pForeImg); 35 //cvShowImage("seg",segImg); 36 cvWaitKey(1); 37 } 38 39 cvReleaseImage(&frame); 40 cvReleaseImage(&pForeImg); 41 cvReleaseCapture(&capture); 42 return 0; 43 }
代码没做过多的注释,但现有的注释应该对于理解代码足够了。另外,对于计算机视觉里的任何一种算法都不是万能的,VIBE也不例外,只能说VIBE相对其他算法有一定的优势,但是还是有相当的不足,其pixel-wise-based的灰度建模方式解决不了pixel-wise建模算法共有的问题,其他必要辅助信息的融合是必要的。