前景檢測演算法(七)--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?- #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; // 記錄前景點的生命長度,如果前景點的生命長度到達一定的閾值,則將其融入背景中去,且要隨機兩次。
- };
#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所示:
- #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=newint *[g_Height];
- for (i=0;i<g_Height;i++)
- {
- LifeLength[i]=newint [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;
- }
- }
#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?- 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;
- }
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建模演算法共有的問題,其他必要輔助資訊的融合是必要的。