VIBE运动目标检测算法实现

来源:互联网 发布:域名策略名词解释 编辑:程序博客网 时间:2024/05/01 03:56

      近来,有不少人咨询我关于VIBE算法的问题,而且对于有些细节问题懵懵懂懂,索要源码类的,考虑这个算法的应用以及很多人对此有比较深的兴趣,遂将其放在博客上供大家学习。该版本的代码是在学校的时候写的,里面也加入了一些其他的后处理内容,尽管还有不足,但是对于加深对VIBE算法的理解肯定有一定帮助。另外,由于最新版本的代码在公司电脑上,不便提供。关于理论方面的请参考下面二篇文章:

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如下:

#pragma once#include "stdafx.h"#define  WINSIZE 3class 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=15int 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,默认为20int g_MinMatch; // 当前像素与背景模型匹配的最少个数,默认为2int g_Height;int g_Width;int g_Radius;// 球体的半径,默认为20int 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=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 thresholdif( len < q ) //Get rid of blob if it's perimeter is too smallcvSubstituteContour( 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 segmentationnewC = cvConvexHull2( c, storage, CV_CLOCKWISE, 1 );cvSubstituteContour( scanner, newC );nContours++;}}contours = cvEndFindContours( &scanner );// paint the found regions back into the imagecvZero( 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;elsereturn 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函数;

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";  //test2capture=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建模算法共有的问题,其他必要辅助信息的融合是必要的。

原创粉丝点击