原文: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建模算法共有的問題,其他必要輔助信息的融合是必要的。