1. 程式人生 > >前景檢測演算法(七)--ViBe演算法

前景檢測演算法(七)--ViBe演算法

原文:

因為監控發展的需求,目前前景檢測的研究還是很多的,也出現了很多新的方法和思路。個人瞭解的大概概括為以下一些:

       幀差、背景減除(GMM、CodeBook、 SOBS、 SACON、 VIBE、 W4、多幀平均……)、光流(稀疏光流、稠密光流)、運動競爭(Motion Competition)、運動模版(運動歷史影象)、時間熵……等等。如果加上他們的改進版,那就是很大的一個家族了。

      對於上一些方法的一點簡單的對比分析可以參考下:

       至於哪個最好,看使用環境吧,各有千秋,有一些適用的情況更多,有一些在某些情況下表現更好。這些都需要針對自己的使用情況作測試確定的。呵呵。

       還有王先榮部落格上存在不少的分析:

       下面的部落格上轉載王先榮的上面幾篇,然後加上自己分析了兩篇:

       本文主要關注其中的一種背景減除方法:ViBe。stellar0的部落格上對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如下:

[cpp] view plain copy print?
  1. #pragma once
  2. #include "stdafx.h"
  3. #define  WINSIZE 3
  4. class Vibe  
  5. {  
  6. public:  
  7.     Vibe(void);  
  8.     Vibe(IplImage *img);  
  9.     void SetMinMatch(int nthreshold){g_MinMatch=nthreshold;}  
  10.     void SetRadius(int radius){g_Radius=radius;}  
  11.     void SetSampleNum(int num){g_SampleNum=num;}  
  12.     void SetThreshold(double t){g_threshold=t;}  
  13.     IplImage* GetForeground(){return g_ForeImg;}  
  14.     IplImage* GetSegMask(){return g_SegementMask;}  
  15.     void Detect(IplImage *img);   
  16.     void ForegroundCombineEdge(); // 結合邊緣資訊
  17.     void DeleteSmallAreaInForeground(double minArea=20);//刪除小面積區域
  18.     // 實現背景更新機制
  19.     void Update();  
  20.     // 實現後處理,主要用形態學運算元
  21.     void PostProcess();  
  22. public:  
  23.     ~Vibe(void);  
  24. private:      
  25.     void ClearLongLifeForeground(int i_lifeLength=200); // 清除場景中存在時間較長的畫素,i_lifeLength用於控制允許存在的最長時間
  26.     double AreaDense(IplImage *pFr,int AI,int AJ,int W,int H); //計算(i,j)處鄰域大小為W×H的密度
  27.     int GetRandom(int istart,int iend); // 預設istart=0,iend=15
  28.     int GetRandom(int random);  
  29.     int GetRandom();// 產生一個隨機數
  30.     // 計算兩個畫素之間的歐式距離
  31.     double CalcPixelDist(CvScalar bkCs,CvScalar curCs);  
  32.     // 按照Kim的方法來計算顏色畸變
  33.     double CalcuColorDist(CvScalar bkCs,CvScalar curCs);  
  34.     int g_SampleNum;// Sample number for the models,預設為20
  35.     int g_MinMatch; // 當前畫素與背景模型匹配的最少個數,預設為2
  36.     int g_Height;  
  37.     int g_Width;  
  38.     int g_Radius;// 球體的半徑,預設為20
  39.     int g_offset; //邊界的寬和高
  40.     double g_threshold; // 距離度量的閾值
  41.     unsigned char ***g_Model;// 儲存背景模型  
  42.     IplImage *g_ForeImg;// 儲存前景圖
  43.     IplImage *g_Edge;  
  44.     IplConvKernel* element;  
  45.     IplImage *g_SegementMask; //分割掩膜
  46.     IplImage *g_UpdateMask; // 更新掩膜
  47.     IplImage *g_Gray;  
  48.     int ** LifeLength; // 記錄前景點的生命長度,如果前景點的生命長度到達一定的閾值,則將其融入背景中去,且要隨機兩次。    
  49. };  
#pragma once
#include "stdafx.h"
#define  WINSIZE 3

class Vibe
{
public:
	Vibe(void);
	Vibe(IplImage *img);
	void SetMinMatch(int nthreshold){g_MinMatch=nthreshold;}
	void SetRadius(int radius){g_Radius=radius;}
	void SetSampleNum(int num){g_SampleNum=num;}
	void SetThreshold(double t){g_threshold=t;}
	IplImage* GetForeground(){return g_ForeImg;}
	IplImage* GetSegMask(){return g_SegementMask;}
	void Detect(IplImage *img);	
	void ForegroundCombineEdge(); // 結合邊緣資訊
	void DeleteSmallAreaInForeground(double minArea=20);//刪除小面積區域
	// 實現背景更新機制
	void Update();
	// 實現後處理,主要用形態學運算元
	void PostProcess();

public:
	~Vibe(void);

private:	
	void ClearLongLifeForeground(int i_lifeLength=200); // 清除場景中存在時間較長的畫素,i_lifeLength用於控制允許存在的最長時間
	double AreaDense(IplImage *pFr,int AI,int AJ,int W,int H); //計算(i,j)處鄰域大小為W×H的密度
	int GetRandom(int istart,int iend); // 預設istart=0,iend=15
	int GetRandom(int random);
	int GetRandom();// 產生一個隨機數
	// 計算兩個畫素之間的歐式距離
	double CalcPixelDist(CvScalar bkCs,CvScalar curCs);
	// 按照Kim的方法來計算顏色畸變
	double CalcuColorDist(CvScalar bkCs,CvScalar curCs);
	int g_SampleNum;// Sample number for the models,預設為20
	int g_MinMatch; // 當前畫素與背景模型匹配的最少個數,預設為2
	int g_Height;
	int g_Width;
	int g_Radius;// 球體的半徑,預設為20
	int g_offset; //邊界的寬和高
	double g_threshold; // 距離度量的閾值
	unsigned char ***g_Model;// 儲存背景模型	
	IplImage *g_ForeImg;// 儲存前景圖
	IplImage *g_Edge;

	IplConvKernel* element;

	IplImage *g_SegementMask; //分割掩膜
	IplImage *g_UpdateMask; // 更新掩膜
	IplImage *g_Gray;
	int ** LifeLength; // 記錄前景點的生命長度,如果前景點的生命長度到達一定的閾值,則將其融入背景中去,且要隨機兩次。	
};


對應的實現檔案如下Vibe.cpp所示:

[cpp] view plain copy print?
  1. #include "StdAfx.h"
  2. #include "Vibe.h"
  3. Vibe::Vibe(void)  
  4. {  
  5.     g_Radius=20;  
  6.     g_MinMatch=2;     
  7.     g_SampleNum=20;  
  8.     g_offset=(WINSIZE-1)/2;  
  9. }  
  10. Vibe::Vibe(IplImage *img)  
  11. {  
  12.     if (!img)  
  13.     {  
  14.         cout<<" The parameter referenced to NUll Pointer!"<<endl;  
  15.         return;  
  16.     }  
  17.     this->g_Height=img->height;  
  18.     this->g_Width=img->width;  
  19.     g_Radius=20;  
  20.     g_MinMatch=2;     
  21.     g_SampleNum=20;  
  22.     g_threshold=50;  
  23.     g_offset=(WINSIZE-1)/2;  
  24.     g_ForeImg=cvCreateImage(cvGetSize(img),IPL_DEPTH_8U,1);  
  25.     g_Gray=cvCreateImage(cvGetSize(img),IPL_DEPTH_8U,1);  
  26.     g_Edge=cvCreateImage(cvGetSize(img),IPL_DEPTH_8U,1);  
  27.     g_SegementMask=cvCreateImage(cvGetSize(img),IPL_DEPTH_8U,1);  
  28.     g_UpdateMask=cvCreateImage(cvGetSize(img),IPL_DEPTH_8U,1);  
  29.     element=cvCreateStructuringElementEx(3,3,1,1,CV_SHAPE_CROSS,NULL);  
  30.     cvCvtColor(img,g_Gray,CV_BGR2GRAY);  
  31.     // 以上完成相關的初始化操作
  32.     /**********************  以下實現第一幀在每個畫素的8鄰域內的取樣功能,建立對應的背景模型*****************************/
  33.     int i=0,j=0,k=0;  
  34.     g_Model=new unsigned char**[g_SampleNum];  
  35.     for (k=0;k<g_SampleNum;k++)  
  36.     {  
  37.         g_Model[k]=new unsigned char *[g_Height];  
  38.         for(i=0;i<g_Height;i++)  
  39.         {  
  40.             g_Model[k][i]=new unsigned char [g_Width];  
  41.             for (j=0;j<g_Width;j++)  
  42.             {  
  43.                 g_Model[k][i][j]=0;  
  44.             }  
  45.         }  
  46.     }  
  47.     // 取樣進行背景建模 
  48.     double dVal;  
  49.     int ri=0,rj=0; //隨機取樣的值
  50.     for (i=g_offset;i<g_Height-g_offset;i++)  
  51.     {  
  52.         for (j=g_offset;j<g_Width-g_offset;j++)  
  53.         {  
  54.             // 周圍3*3的鄰域內進行取樣
  55.             for(k=0;k<g_SampleNum;k++)  
  56.             {  
  57.                 ri=GetRandom(i);  
  58.                 rj=GetRandom(j);  
  59.                 dVal=cvGetReal2D(g_Gray,ri,rj);       
  60.                 g_Model[k][i][j]=dVal;                            
  61.             }  
  62.         }  
  63.     }  
  64.     // 初始化前景點掩膜的生命長度
  65.     LifeLength=newint *[g_Height];  
  66.     for (i=0;i<g_Height;i++)  
  67.     {  
  68.         LifeLength[i]=newint [g_Width];  
  69.         for(j=0;j<g_Width;j++)  
  70.         {  
  71.             LifeLength[i][j]=0;  
  72.         }  
  73.     }  
  74. }  
  75. void Vibe::Detect(IplImage *img)  
  76. {  
  77.     cvZero(g_ForeImg);    
  78.     cvCvtColor(img,g_Gray,CV_BGR2GRAY);  
  79.     int i=0,j=0,k=0;  
  80.     double dModVal,dCurrVal;  
  81.     int tmpCount=0;// 距離比較在閾值內的次數
  82.     double tmpDist=0;     
  83.     int iR1,iR2;//產生隨機數
  84.     int Ri,Rj; // 產生鄰域內X和Y的隨機數
  85.     for (i=0;i<g_Height;i++)  
  86.     {         
  87.         for (j=0;j<g_Width;j++)  
  88.         {             
  89.             if( i < g_offset || j < g_offset || i> g_Height - g_offset || j> g_Width - g_offset )  
  90.             {  
  91.                 cvSetReal2D(g_ForeImg,i,j,0);  
  92.                 continue;  
  93.             }  
  94.             else
  95.             {  
  96.                 tmpCount=0;  
  97.                 dCurrVal=cvGetReal2D(g_Gray,i,j);                 
  98.                 for (k=0;k<g_SampleNum && tmpCount<g_MinMatch  ;k++)  
  99.                 {                     
  100.                     dModVal=g_Model[k][i][j];  
  101.                     //tmpDist=CalcPixelDist(dCurrVal,dModVal);
  102.                     //tmpDist=CalcuColorDist(dCurrVal,dModVal); 
  103.                     tmpDist=fabs(dModVal-dCurrVal);                                   
  104.                     if (tmpDist<g_Radius)  
  105.                     {  
  106.                         tmpCount++;  
  107.                     }                     
  108.                 }  
  109.                 //判斷是否匹配上
  110.                 if (tmpCount>=g_MinMatch)  
  111.                 {  
  112.                     cvSetReal2D(g_ForeImg,i,j,0);  
  113.                     // 背景模型的更新                  
  114.                     iR1=GetRandom(0,15);  
  115.                     if (iR1==0)  
  116.                     {  
  117.                         iR2=GetRandom();  
  118.                         g_Model[iR2][i][j]=dCurrVal;                          
  119.                     }  
  120.                     //進一步更新鄰域模型
  121.                     iR1=GetRandom(0,15);  
  122.                     if (iR1==0)  
  123.                     {  
  124.                         Ri=GetRandom(i);  
  125.                         Rj=GetRandom(j);  
  126.                         iR2=GetRandom();  
  127.                         g_Model[iR2][Ri][Rj]=dCurrVal;                        
  128.                     }                         
  129.                 }  
  130.                 else
  131.                 {  
  132.                     cvSetReal2D(g_ForeImg,i,j,255);  
  133.                 }  
  134.             }  
  135.         }         
  136.     }         
  137.     //ForegroundCombineEdge();
  138.     DeleteSmallAreaInForeground(80);  
  139.     ClearLongLifeForeground();  
  140.     //PostProcess();
  141. }  
  142. double Vibe::AreaDense(IplImage *pFr,int AI,int AJ,int W,int H)  
  143. {  
  144.     if (AI<=2 || AJ<=2 || AJ>=(g_Width-2) || AI>=(g_Height-2))  
  145.     {  
  146.         return 0;  
  147.     }  
  148.     int Num=0,i=0,j=0;  
  149.     double dVal=0,dense=0;  
  150.     int Total=(2*H+1)*(2*W+1);  
  151.     for (i=AI-H;i<=AI+H;i++)  
  152.     {  
  153.         for (j=AJ-W;j<=AJ+W;j++)  
  154.         {  
  155.             dVal=cvGetReal2D(pFr,i,j);  
  156.             if (dVal>200)  
  157.             {  
  158.                 Num++;  
  159.             }  
  160.         }  
  161.     }  
  162.     dense=(double)Num/(double)Total;  
  163.     return dense;  
  164. }  
  165. void Vibe::ForegroundCombineEdge()  
  166. {     
  167.     cvZero(g_Edge);  
  168.     //cvZero(g_SegementMask);
  169.     //cvCopy(g_ForeImg,g_SegementMask);
  170.     cvCanny(g_Gray,g_Edge,30,200,3);  
  171.     int i=0,j=0;  
  172.     double dense;  
  173.     double dVal;  
  174.     for (i=g_offset;i<g_Height-g_offset;i++)  
  175.     {  
  176.         for (j=g_offset;j<g_Width-g_offset;j++)  
  177.         {  
  178.             dense=AreaDense(g_ForeImg,i,j,2,2);  
  179.             dVal=cvGetReal2D(g_Edge,i,j);  
  180.             if (dense>0.2 && dVal>200)  
  181.             {  
  182.                 cvSetReal2D(g_ForeImg,i,j,255);  
  183.             }  
  184.         }  
  185.     }  
  186. }  
  187. void Vibe::DeleteSmallAreaInForeground(double minArea/* =20 */)  
  188. {  
  189.     //cvZero(g_SegementMask);
  190.     //cvCopy(g_ForeImg,g_SegementMask);
  191.     int region_count = 0;  
  192.     CvSeq *first_seq = NULL, *prev_seq = NULL, *seq = NULL;  
  193.     CvMemStorage*  storage = cvCreateMemStorage();  
  194.     cvClearMemStorage(storage);  
  195.     cvFindContours( g_ForeImg, storage, &first_seq, sizeof(CvContour), CV_RETR_LIST );  
  196.     for( seq = first_seq; seq; seq = seq->h_next )  
  197.     {  
  198.         CvContour* cnt = (CvContour*)seq;  
  199.         if( cnt->rect.width * cnt->rect.height < minArea )  
  200.         {  
  201.             prev_seq = seq->h_prev;  
  202.             if( prev_seq )  
  203.             {  
  204.                 prev_seq->h_next = seq->h_next;  
  205.                 if( seq->h_next ) seq->h_next->h_prev = prev_seq;  
  206.             }  
  207.             else
  208.             {  
  209.                 first_seq = seq->h_next;  
  210.                 if( seq->h_next ) seq->h_next->h_prev = NULL;  
  211.             }  
  212.         }  
  213.         else
  214.         {             
  215.             region_count++;  
  216.         }  
  217.     }              
  218.     cvZero(g_ForeImg);  
  219.     cvDrawContours(g_ForeImg, first_seq, CV_RGB(0, 0, 255), CV_RGB(0, 0, 255), 10, -1);   
  220.     /* 
  221.     CvContourScanner scanner = cvStartFindContours( g_ForeImg, storage,sizeof(CvContour), CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE, cvPoint(0,0) ); 
  222.     CvSeq *contours=NULL,*c=NULL; 
  223.     int poly1Hull0=0; 
  224.     int nContours=0; 
  225.     double perimScale=100; 
  226.     while( (c = cvFindNextContour( scanner )) != 0 )  
  227.     { 
  228.         double len = cvContourPerimeter( c ); 
  229.         double q = (g_ForeImg->height + g_ForeImg->width)/perimScale; // calculate perimeter len threshold 
  230.         if( len < q ) //Get rid of blob if it's perimeter is too small 
  231.             cvSubstituteContour( scanner, 0 ); 
  232.         else //Smooth it's edges if it's large enough 
  233.         { 
  234.             CvSeq* newC; 
  235.             if( poly1Hull0 ) //Polygonal approximation of the segmentation  
  236.                 newC = cvApproxPoly( c, sizeof(CvContour), storage, CV_POLY_APPROX_DP, 2, 0 );  
  237.             else //Convex Hull of the segmentation 
  238.                 newC = cvConvexHull2( c, storage, CV_CLOCKWISE, 1 ); 
  239.             cvSubstituteContour( scanner, newC ); 
  240.             nContours++; 
  241.         } 
  242.     } 
  243.     contours = cvEndFindContours( &scanner ); 
  244.     // paint the found regions back into the image 
  245.     cvZero( g_ForeImg ); 
  246.     for( c=contours; c != 0; c = c->h_next )  
  247.         cvDrawContours( g_ForeImg, c, cvScalarAll(255), cvScalarAll(0), -1, CV_FILLED, 8,cvPoint(0,0)); 
  248.     */
  249.     cvReleaseMemStorage(&storage);    
  250. }  
  251. void Vibe::ClearLongLifeForeground(int i_lifeLength/* =200 */)  
  252. {  
  253.     int i=0,j=0;  
  254.     double dVal=0;  
  255.     double dLife=0;  
  256.     int iR1,iR2=0;  
  257.     double dCurrVal=0;  
  258.     for (i=g_offset;i<g_Height-g_offset;i++)  
  259.     {  
  260.         for (j=g_offset;j<g_Width-g_offset;j++)  
  261.         {  
  262.             dVal=cvGetReal2D(g_ForeImg,i,j);  
  263.             dLife=LifeLength[i][j];  
  264.             if (dLife>i_lifeLength)  
  265.             {  
  266.                 LifeLength[i][j]=0;  
  267.                 dCurrVal=cvGetReal2D(g_Gray,i,j);  
  268.                 // 更新背景模型
  269.                 iR1=GetRandom();  
  270.                 iR2=GetRandom();  
  271.                 g_Model[iR1][i][j]=dCurrVal;  
  272.                 g_Model[iR2][i][j]=dCurrVal;  
  273.             }  
  274.             else
  275.             {  
  276.                 LifeLength[i][j]=dLife+1;  
  277.             }  
  278.         }  
  279.     }  
  280. }  
  281. void Vibe::Update()  
  282. {  
  283.     cvZero(g_UpdateMask);     
  284. }  
  285. void Vibe::PostProcess()  
  286. {  
  287.     cvZero(g_SegementMask);  
  288.     cvMorphologyEx(g_ForeImg,g_SegementMask,NULL,element,CV_MOP_OPEN,1);  
  289. }  
  290. //算顏色畸變
  291. double Vibe::CalcuColorDist(CvScalar bkCs,CvScalar curCs)  
  292. {  
  293.     double r,g,b,br,bg,bb;  
  294.     r=curCs.val[0];  
  295.     g=curCs.val[1];  
  296.     b=curCs.val[2];  
  297.     br=bkCs.val[0];  
  298.     bg=bkCs.val[1];  
  299.     bb=bkCs.val[2];  
  300.     double curDist=r*r+g*g*b*b;   
  301.     double bkDist=br*br+bg*bg+bb*bb;  
  302.     double curBK=r*br+g*bg+b*bb;  
  303.     double curbkDist=curBK*curBK;  
  304.     double SquareP;  
  305.     if (bkDist==0.0)  
  306.     {  
  307.         SquareP=0;  
  308.     }  
  309.     else
  310.     {  
  311.         SquareP=curbkDist/bkDist;  
  312.     }  
  313.     double dist=sqrtf(curDist-SquareP);  
  314.     return dist;      
  315. }  
  316. double Vibe::CalcPixelDist(CvScalar bkCs,CvScalar curCs)  
  317. {  
  318.     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);  
  319.     return sqrtf(tmpDist);  
  320. }  
  321. int Vibe::GetRandom()  
  322. {  
  323.     int val = g_SampleNum * 1.0 * rand() / RAND_MAX;      
  324.     if( val == g_SampleNum )  
  325.         return val - 1;  
  326.     else
  327.         return val;  
  328. }  
  329. int Vibe::GetRandom(int random)  
  330. {  
  331.     int val=random-g_offset+rand()%(2*g_offset);  
  332.     if (val<random-g_offset)  
  333.     {  
  334.         val=random-g_offset;  
  335.     }  
  336.     if (val>random+g_offset)  
  337.     {  
  338.         val=random+g_offset;  
  339.     }     
  340.     return val;   
  341. }  
  342. int Vibe::GetRandom(int istart,int iend)  
  343. {  
  344.     int val=istart+rand()%(iend-istart);  
  345.     return val;  
  346. }  
  347. Vibe::~Vibe(void)  
  348. {  
  349.     if (g_ForeImg)  
  350.     {  
  351.         cvReleaseImage(&g_ForeImg);  
  352.     }  
  353.     if (g_SegementMask)  
  354.     {  
  355.         cvReleaseImage(&g_SegementMask);  
  356.     }  
  357.     if (g_UpdateMask)  
  358.     {  
  359.         cvReleaseImage(&g_UpdateMask);  
  360.     }  
  361.     if (g_Gray)  
  362.     {  
  363.         cvReleaseImage(&g_Gray);  
  364.     }  
  365.     if (g_Model!=NULL)  
  366.     {  
  367.         delete[]g_Model;  
  368.         g_Model=NULL;  
  369.     }  
  370. }  
#include "StdAfx.h"
#include "Vibe.h"

Vibe::Vibe(void)
{
	g_Radius=20;
	g_MinMatch=2;	
	g_SampleNum=20;
	g_offset=(WINSIZE-1)/2;

}

Vibe::Vibe(IplImage *img)
{
	if (!img)
	{
		cout<<" The parameter referenced to NUll Pointer!"<<endl;
		return;
	}
	this->g_Height=img->height;
	this->g_Width=img->width;

	g_Radius=20;
	g_MinMatch=2;	
	g_SampleNum=20;
	g_threshold=50;
	g_offset=(WINSIZE-1)/2;

	g_ForeImg=cvCreateImage(cvGetSize(img),IPL_DEPTH_8U,1);
	g_Gray=cvCreateImage(cvGetSize(img),IPL_DEPTH_8U,1);
	g_Edge=cvCreateImage(cvGetSize(img),IPL_DEPTH_8U,1);
	g_SegementMask=cvCreateImage(cvGetSize(img),IPL_DEPTH_8U,1);
	g_UpdateMask=cvCreateImage(cvGetSize(img),IPL_DEPTH_8U,1);

	element=cvCreateStructuringElementEx(3,3,1,1,CV_SHAPE_CROSS,NULL);

	cvCvtColor(img,g_Gray,CV_BGR2GRAY);
	
	// 以上完成相關的初始化操作
	/**********************  以下實現第一幀在每個畫素的8鄰域內的取樣功能,建立對應的背景模型*****************************/
	
	int i=0,j=0,k=0;
	g_Model=new unsigned char**[g_SampleNum];
	for (k=0;k<g_SampleNum;k++)
	{
		g_Model[k]=new unsigned char *[g_Height];
		for(i=0;i<g_Height;i++)
		{
			g_Model[k][i]=new unsigned char [g_Width];
			for (j=0;j<g_Width;j++)
			{
				g_Model[k][i][j]=0;
			}
		}
	}
	
	// 取樣進行背景建模	
	double dVal;
	int ri=0,rj=0; //隨機取樣的值
	for (i=g_offset;i<g_Height-g_offset;i++)
	{
		for (j=g_offset;j<g_Width-g_offset;j++)
		{
			// 周圍3*3的鄰域內進行取樣
			for(k=0;k<g_SampleNum;k++)
			{
				ri=GetRandom(i);
				rj=GetRandom(j);
				dVal=cvGetReal2D(g_Gray,ri,rj);		
				g_Model[k][i][j]=dVal;							
			}
		}
	}

	// 初始化前景點掩膜的生命長度
	LifeLength=new int *[g_Height];
	for (i=0;i<g_Height;i++)
	{
		LifeLength[i]=new int [g_Width];
		for(j=0;j<g_Width;j++)
		{
			LifeLength[i][j]=0;
		}
	}
}


void Vibe::Detect(IplImage *img)
{
	cvZero(g_ForeImg);	
	cvCvtColor(img,g_Gray,CV_BGR2GRAY);
	int i=0,j=0,k=0;
	double dModVal,dCurrVal;
	int tmpCount=0;// 距離比較在閾值內的次數
	double tmpDist=0;	
	int iR1,iR2;//產生隨機數
	int Ri,Rj; // 產生鄰域內X和Y的隨機數

	for (i=0;i<g_Height;i++)
	{		
		for (j=0;j<g_Width;j++)
		{			
			if( i < g_offset || j < g_offset || i> g_Height - g_offset || j> g_Width - g_offset )
			{
				cvSetReal2D(g_ForeImg,i,j,0);
				continue;
			}
			else
			{
				tmpCount=0;
				dCurrVal=cvGetReal2D(g_Gray,i,j);				
				for (k=0;k<g_SampleNum && tmpCount<g_MinMatch  ;k++)
				{					
					dModVal=g_Model[k][i][j];
					//tmpDist=CalcPixelDist(dCurrVal,dModVal);
					//tmpDist=CalcuColorDist(dCurrVal,dModVal);	
					tmpDist=fabs(dModVal-dCurrVal);									
					if (tmpDist<g_Radius)
					{
						tmpCount++;
					}					
				}

				//判斷是否匹配上
				if (tmpCount>=g_MinMatch)
				{
					cvSetReal2D(g_ForeImg,i,j,0);
					// 背景模型的更新					
					iR1=GetRandom(0,15);
					if (iR1==0)
					{
						iR2=GetRandom();
						g_Model[iR2][i][j]=dCurrVal;						
					}

					//進一步更新鄰域模型
					
					iR1=GetRandom(0,15);
					if (iR1==0)
					{
						Ri=GetRandom(i);
						Rj=GetRandom(j);
						iR2=GetRandom();
						g_Model[iR2][Ri][Rj]=dCurrVal;						
					}						
				}
				else
				{
					cvSetReal2D(g_ForeImg,i,j,255);
				}
			}
		}		
	}		
	
	//ForegroundCombineEdge();
	DeleteSmallAreaInForeground(80);
	ClearLongLifeForeground();
	//PostProcess();
}

double Vibe::AreaDense(IplImage *pFr,int AI,int AJ,int W,int H)
{
	if (AI<=2 || AJ<=2 || AJ>=(g_Width-2) || AI>=(g_Height-2))
	{
		return 0;
	}
	int Num=0,i=0,j=0;
	double dVal=0,dense=0;
	int Total=(2*H+1)*(2*W+1);
	for (i=AI-H;i<=AI+H;i++)
	{
		for (j=AJ-W;j<=AJ+W;j++)
		{
			dVal=cvGetReal2D(pFr,i,j);
			if (dVal>200)
			{
				Num++;
			}
		}
	}
	dense=(double)Num/(double)Total;
	return dense;
}

void Vibe::ForegroundCombineEdge()
{	
	cvZero(g_Edge);
	//cvZero(g_SegementMask);
	//cvCopy(g_ForeImg,g_SegementMask);
	cvCanny(g_Gray,g_Edge,30,200,3);
	int i=0,j=0;
	double dense;
	double dVal;
	for (i=g_offset;i<g_Height-g_offset;i++)
	{
		for (j=g_offset;j<g_Width-g_offset;j++)
		{
			dense=AreaDense(g_ForeImg,i,j,2,2);
			dVal=cvGetReal2D(g_Edge,i,j);
			if (dense>0.2 && dVal>200)
			{
				cvSetReal2D(g_ForeImg,i,j,255);
			}
		}
	}

}


void Vibe::DeleteSmallAreaInForeground(double minArea/* =20 */)
{
	//cvZero(g_SegementMask);
	//cvCopy(g_ForeImg,g_SegementMask);
	int region_count = 0;
	CvSeq *first_seq = NULL, *prev_seq = NULL, *seq = NULL;
	CvMemStorage*  storage = cvCreateMemStorage();
	cvClearMemStorage(storage);
	cvFindContours( g_ForeImg, storage, &first_seq, sizeof(CvContour), CV_RETR_LIST );
	for( seq = first_seq; seq; seq = seq->h_next )
	{
		CvContour* cnt = (CvContour*)seq;
		if( cnt->rect.width * cnt->rect.height < minArea )
		{
			prev_seq = seq->h_prev;
			if( prev_seq )
			{
				prev_seq->h_next = seq->h_next;
				if( seq->h_next ) seq->h_next->h_prev = prev_seq;
			}
			else
			{
				first_seq = seq->h_next;
				if( seq->h_next ) seq->h_next->h_prev = NULL;
			}
		}
		else
		{			
			region_count++;
		}
	}			 
	cvZero(g_ForeImg);
	cvDrawContours(g_ForeImg, first_seq, CV_RGB(0, 0, 255), CV_RGB(0, 0, 255), 10, -1);	

	/*
	CvContourScanner scanner = cvStartFindContours( g_ForeImg, storage,sizeof(CvContour), CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE, cvPoint(0,0) );
	CvSeq *contours=NULL,*c=NULL;
	int poly1Hull0=0;
	int nContours=0;
	double perimScale=100;
	while( (c = cvFindNextContour( scanner )) != 0 ) 
	{
		double len = cvContourPerimeter( c );
		double q = (g_ForeImg->height + g_ForeImg->width)/perimScale; // calculate perimeter len threshold
		if( len < q ) //Get rid of blob if it's perimeter is too small
			cvSubstituteContour( scanner, 0 );
		else //Smooth it's edges if it's large enough
		{
			CvSeq* newC;
			if( poly1Hull0 ) //Polygonal approximation of the segmentation 
				newC = cvApproxPoly( c, sizeof(CvContour), storage, CV_POLY_APPROX_DP, 2, 0 ); 
			else //Convex Hull of the segmentation
				newC = cvConvexHull2( c, storage, CV_CLOCKWISE, 1 );
			cvSubstituteContour( scanner, newC );
			nContours++;
		}
	}
	contours = cvEndFindContours( &scanner );
	// paint the found regions back into the image
	cvZero( g_ForeImg );
	for( c=contours; c != 0; c = c->h_next ) 
		cvDrawContours( g_ForeImg, c, cvScalarAll(255), cvScalarAll(0), -1, CV_FILLED, 8,cvPoint(0,0));
	*/

	cvReleaseMemStorage(&storage);	
}

void Vibe::ClearLongLifeForeground(int i_lifeLength/* =200 */)
{
	int i=0,j=0;
	double dVal=0;
	double dLife=0;
	int iR1,iR2=0;
	double dCurrVal=0;
	for (i=g_offset;i<g_Height-g_offset;i++)
	{
		for (j=g_offset;j<g_Width-g_offset;j++)
		{
			dVal=cvGetReal2D(g_ForeImg,i,j);
			dLife=LifeLength[i][j];
			if (dLife>i_lifeLength)
			{
				LifeLength[i][j]=0;
				dCurrVal=cvGetReal2D(g_Gray,i,j);
				// 更新背景模型
				iR1=GetRandom();
				iR2=GetRandom();
				g_Model[iR1][i][j]=dCurrVal;
				g_Model[iR2][i][j]=dCurrVal;
			}
			else
			{
				LifeLength[i][j]=dLife+1;
			}

		}
	}
}

void Vibe::Update()
{
	cvZero(g_UpdateMask);	

}

void Vibe::PostProcess()
{
	cvZero(g_SegementMask);
	cvMorphologyEx(g_ForeImg,g_SegementMask,NULL,element,CV_MOP_OPEN,1);

}

//算顏色畸變
double Vibe::CalcuColorDist(CvScalar bkCs,CvScalar curCs)
{
	double r,g,b,br,bg,bb;
	r=curCs.val[0];
	g=curCs.val[1];
	b=curCs.val[2];

	br=bkCs.val[0];
	bg=bkCs.val[1];
	bb=bkCs.val[2];

	double curDist=r*r+g*g*b*b; 
	double bkDist=br*br+bg*bg+bb*bb;

	double curBK=r*br+g*bg+b*bb;
	double curbkDist=curBK*curBK;
	double SquareP;
	if (bkDist==0.0)
	{
		SquareP=0;
	}
	else
	{
		SquareP=curbkDist/bkDist;
	}
	double dist=sqrtf(curDist-SquareP);
	return dist;	
}

double Vibe::CalcPixelDist(CvScalar bkCs,CvScalar curCs)
{
	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);
	return sqrtf(tmpDist);
}

int Vibe::GetRandom()
{
	int val = g_SampleNum * 1.0 * rand() / RAND_MAX;	
	if( val == g_SampleNum )
		return val - 1;
	else
		return val;
}

int Vibe::GetRandom(int random)
{
	int val=random-g_offset+rand()%(2*g_offset);
	if (val<random-g_offset)
	{
		val=random-g_offset;
	}
	if (val>random+g_offset)
	{
		val=random+g_offset;
	}	
	return val;	
}

int Vibe::GetRandom(int istart,int iend)
{
	int val=istart+rand()%(iend-istart);
	return val;
}


Vibe::~Vibe(void)
{
	if (g_ForeImg)
	{
		cvReleaseImage(&g_ForeImg);
	}
	if (g_SegementMask)
	{
		cvReleaseImage(&g_SegementMask);
	}
	if (g_UpdateMask)
	{
		cvReleaseImage(&g_UpdateMask);
	}
	if (g_Gray)
	{
		cvReleaseImage(&g_Gray);
	}

	if (g_Model!=NULL)
	{
		delete[]g_Model;
		g_Model=NULL;
	}
}

最後附上呼叫的main函式;

[cpp] view plain copy print?
  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.     char *file_path="E:\\testVideo\\VTS_01_4.avi";  // m1  test2 錦帶河  VTS_01_4_2  head rear  VTS_01_6_2  VTS_01_4
  8.     //const char* file_path="E:\\suntektechvideo\\錦帶河.avi";  //test2
  9.     capture=cvCreateFileCapture(file_path);  
  10.     if (!capture)  
  11.     {  
  12.         //cout<<"Read Video File Error!"<<endl;
  13.         return -1;  
  14.     }  
  15.     frame=cvQueryFrame(capture);  
  16.     frame=cvQueryFrame(capture);  
  17.     cvNamedWindow("img",1);  
  18.     cvNamedWindow("foreN",1);  
  19.     //cvNamedWindow("seg",1);
  20.     Vibe* pV=new Vibe(frame);  
  21.     while(frame=cvQueryFrame(capture))  
  22.     {  
  23.         pV->Detect(frame);  
  24.         pForeImg=pV->GetForeground();  
  25.         //segImg=pV->GetSegMask();
  26.         //frame->origin=1;
  27.         //pForeImg->origin=1;
  28.         cvShowImage("img",frame);  
  29.         cvShowImage("foreN",pForeImg);  
  30.         //cvShowImage("seg",segImg);
  31.         cvWaitKey(1);  
  32.     }  
  33.     cvReleaseImage(&frame);  
  34.     cvReleaseImage(&pForeImg);  
  35.     cvReleaseCapture(&capture);  
  36.     return 0;     
  37. }  
int _tmain(int argc, _TCHAR* argv[])
{	
	CvCapture *capture=NULL;
	IplImage* frame=NULL;
	IplImage* pForeImg=NULL;
	IplImage* segImg=NULL;	

	char *file_path="E:\\testVideo\\VTS_01_4.avi";  // m1  test2 錦帶河  VTS_01_4_2  head rear  VTS_01_6_2  VTS_01_4
	//const char* file_path="E:\\suntektechvideo\\錦帶河.avi";  //test2

	capture=cvCreateFileCapture(file_path);
	if (!capture)
	{
		//cout<<"Read Video File Error!"<<endl;
		return -1;
	}
	frame=cvQueryFrame(capture);
	frame=cvQueryFrame(capture);

	cvNamedWindow("img",1);
	cvNamedWindow("foreN",1);
	//cvNamedWindow("seg",1);

	Vibe* pV=new Vibe(frame);

	while(frame=cvQueryFrame(capture))
	{
		pV->Detect(frame);
		pForeImg=pV->GetForeground();
		//segImg=pV->GetSegMask();
		//frame->origin=1;
		//pForeImg->origin=1;
		cvShowImage("img",frame);
		cvShowImage("foreN",pForeImg);
		//cvShowImage("seg",segImg);
		cvWaitKey(1);
	}

	cvReleaseImage(&frame);
	cvReleaseImage(&pForeImg);
	cvReleaseCapture(&capture);
	return 0;	
}

       程式碼沒做過多的註釋,但現有的註釋應該對於理解程式碼足夠了。另外,對於計算機視覺裡的任何一種演算法都不是萬能的,VIBE也不例外,只能說VIBE相對其他演算法有一定的優勢,但是還是有相當的不足,其pixel-wise-based的灰度建模方式解決不了pixel-wise建模演算法共有的問題,其他必要輔助資訊的融合是必要的。