vibe算法是一种像素级的前景检测算法,实时性高,内存占有率低,前景检测准确率高。但是会出现“鬼影”,当然基于对鬼影的处理,也会有相应的对vibe算法的改进。
把下面三篇文章看明白,基本就会掌握vibe算法的过程:
《 ViBe: a powerful random technique to estimate the background in video sequences》
《Background Subtraction: Experiments and Improvements for ViBe 》
《ViBe: A universal background subtraction algorithm for video sequences》
该算法的原文链接地址,作者已经给出了C代码。
以下用opencv复现一遍。
原理:
论文中以(x,y)为中心,取3x3的区域,即(x,y)处的8-领域。也可以选择5x5区域,即24-领域
对(x,y)处的8-领域,按平均分布 随机抽样numberSamples次,论文中给出的是numberSamples=20,假设以下为随机取样的结果:
做为下一帧(x,y)处的背景模型。
问题1:怎么判断视频流中的下一帧坐标(x,y)处是背景点还是前景点?
对于上述的结果,如果“1”的数量大于某一阈值minMatch(文章中设为2),则视为背景点,并更新背景模型,否则,为前景点。
问题2:更新背景模型的策略
文中给出了伪码,主要基于均匀随机抽样的方法。把背景点,按照一定的概率更新到背景模型中。
代码:(opencv实现)
class OriginalVibe{ public: //构造函数 OriginalVibe(int _numberSamples, int _minMatch,int _distanceThreshold,int _updateFactor,int _neighborWidth,int _neighborHeight):numberSamples(_numberSamples),minMatch(_minMatch),distanceThreshold(_distanceThreshold),updateFactor(_updateFactor),neighborWidth(_neighborWidth),neighborHeight(_neighborHeight){}; ~OriginalVibe(){}; //操作成员变量 void setUpdateFactor(int _updateFactor); //灰度图像 void originalVibe_Init_GRAY(const Mat &firstFrame); void originalVibe_ClassifyAndUpdate_GRAY(const Mat &frame,OutputArray &_segmentation); //RGB三通道 void originalVibe_Init_BGR(const Mat & firstFrame); void originalVibe_ClassifyAndUpdate_BGR(const Mat &frame,OutputArray &_segmentation); private: //背景模型 const int numberSamples; std::vector<Mat> backgroundModel; //像素点的分类判断的参数 const int minMatch; int distanceThreshold; //背景模型更新概率 int updateFactor; //8-领域(3 x 3) const int neighborWidth; const int neighborHeight; //前景和背景分割 const static unsigned char BACK_GROUND; const static unsigned char FORE_GROUND; //BGR的距离计算 int distanceL1(const Vec3b &src1,const Vec3b &src2); float distanceL2(const Vec3b &src1,const Vec3b &src2); };
#include"originalVibe.h" #include<iostream> const unsigned char OriginalVibe::BACK_GROUND = 0; const unsigned char OriginalVibe::FORE_GROUND = 255; //操作成员变量 void OriginalVibe::setUpdateFactor(int _updateFactor) { this->updateFactor = _updateFactor; } //第一种方法:最原始的vibe灰度通道 void OriginalVibe::originalVibe_Init_GRAY(const Mat &firstFrame) { int height = firstFrame.rows; int width = firstFrame.cols; //背景模型分配内存 backgroundModel.clear(); for(int index = 0;index < this->numberSamples;index++) { backgroundModel.push_back(Mat::zeros(height,width,CV_8UC1)); } //随机数 RNG rng; int cshift; int rshift; for(int r = 0;r < height ;r++) { for(int c = 0;c < width ; c++) { if( c < neighborWidth/2 || c > width - neighborWidth/2 -1|| r < neighborHeight/2 || r > height - neighborHeight/2 -1) { /*随机数的生成方式有很多种*/ /* cshift = randu<int>()%neighborWidth - neighborWidth/2; rshift = randu<int>()%neighborHeight - neighborHeight/2; */ cshift = rand()%neighborWidth - neighborWidth/2; rshift = rand()%neighborHeight - neighborHeight/2; for(std::vector<Mat>::iterator it = backgroundModel.begin();it != backgroundModel.end();it++) { for(;;) { /* cshift = rng.uniform(-neighborWidth/2,neighborWidth/2 + 1); rshift = rng.uniform(-neighborHeight/2,neighborHeight/2 +1 ); */ cshift = abs(randu<int>()%neighborWidth) - neighborWidth/2; rshift = abs(randu<int>()%neighborHeight) - neighborHeight/2; if(!(cshift == 0 && rshift==0)) break; } if(c + cshift < 0 || c + cshift >=width) cshift *= -1; if(r + rshift < 0 || r + rshift >= height) rshift *= -1; (*it).at<uchar>(r,c) = firstFrame.at<uchar>(r+rshift,c+cshift); } } else { for(std::vector<Mat>::iterator it = backgroundModel.begin();it != backgroundModel.end();it++) { for(;;) { /* cshift = rng.uniform(-neighborWidth/2,neighborWidth/2 + 1); rshift = rng.uniform(-neighborHeight/2,neighborHeight/2 +1 ); */ cshift = abs(randu<int>()%neighborWidth) - neighborWidth/2; rshift = abs(randu<int>()%neighborHeight) - neighborHeight/2; if(!(cshift == 0 && rshift == 0)) break; } (*it).at<uchar>(r,c) = firstFrame.at<uchar>(r+rshift,c+cshift); } } } } } void OriginalVibe::originalVibe_ClassifyAndUpdate_GRAY(const Mat &frame,OutputArray &_segmentation) { int width = frame.cols; int height = frame.rows; int rshift; int cshift; _segmentation.create(frame.size(),CV_8UC1); Mat segmentation = _segmentation.getMat(); RNG rng; for(int r = 0; r < height;r++) { for(int c = 0;c < width ;c++) { int count = 0; unsigned char pixel = frame.at<uchar>(r,c); //让pixel和背景模板中backgroundModel进行比较 for(std::vector<Mat>::iterator it = backgroundModel.begin();it != backgroundModel.end();it++) { if( abs( int(pixel) - int( (*it).at<uchar>(r,c)) ) < (this->distanceThreshold) ) { count++; //循环到一定阶段,判断count的值是否大于 minMatch,更新背景模型 if( count >= this->minMatch) { int random = rng.uniform(0,this->updateFactor); if(random == 0) { int updateIndex = rng.uniform(0,this->numberSamples); backgroundModel[updateIndex].at<uchar>(r,c) = pixel; } random = rng.uniform(0,this->updateFactor); if(random == 0) { if(c < neighborWidth/2 || c > width - neighborWidth/2-1 || r < neighborHeight/2 || r > height - neighborHeight/2-1) { for(;;) { /* cshift = rng.uniform(-neighborWidth/2,neighborWidth/2 + 1); rshift = rng.uniform(-neighborHeight/2,neighborHeight/2 +1 ); */ cshift = abs(randu<int>()%neighborWidth) - neighborWidth/2; rshift = abs(randu<int>()%neighborHeight) - neighborHeight/2; if(!(cshift == 0 && rshift ==0)) break; } if(c + cshift < 0 || c + cshift >=width) cshift *= -1; if(r + rshift < 0 || r + rshift >= height) rshift *= -1; int updateIndex = rng.uniform(0,this->numberSamples); backgroundModel[updateIndex].at<uchar>(r+rshift,c+cshift) = pixel; } else { for(;;) { /* cshift = rng.uniform(-neighborWidth/2,neighborWidth/2 + 1); rshift = rng.uniform(-neighborHeight/2,neighborHeight/2 +1 ); */ cshift = abs(randu<int>()%neighborWidth) - neighborWidth/2; rshift = abs(randu<int>()%neighborHeight) - neighborHeight/2; if(!(cshift == 0 && rshift==0)) break; } int updateIndex = rng.uniform(0,this->numberSamples); backgroundModel[updateIndex].at<uchar>(r+rshift,c+cshift) = pixel; } } segmentation.at<uchar>(r,c) = this ->BACK_GROUND; break; } } } if( count < this->minMatch) segmentation.at<uchar>(r,c) = this->FORE_GROUND; } } } //第三种方法:BGR通道 void OriginalVibe::originalVibe_Init_BGR(const Mat & fristFrame) { int height = fristFrame.rows; int width = fristFrame.cols; //背景模型分配内存 backgroundModel.clear(); for(int index = 0;index < this->numberSamples;index++) { backgroundModel.push_back( Mat::zeros(height,width,CV_8UC3) ); } //随机数 RNG rng; int cshift; int rshift; for(int r =0 ; r < height; r++) { for(int c = 0;c < width ;c++) { if( c < neighborWidth/2 || c > width - neighborWidth/2 -1|| r < neighborHeight/2 || r > height - neighborHeight/2 -1 ) { /* 初始化背景模型:开始 */ for(vector<Mat>::iterator iter = backgroundModel.begin(); iter != backgroundModel.end();iter++) { for(;;) { cshift = abs(randu<int>()%neighborWidth) - neighborWidth/2; rshift = abs(randu<int>()%neighborHeight) - neighborHeight/2; if(!(cshift == 0 && rshift==0)) break; } if(c + cshift < 0 || c + cshift >=width) cshift *= -1; if(r + rshift < 0 || r + rshift >= height) rshift *=-1; (*iter).at<Vec3b>(r,c) = fristFrame.at<Vec3b>(r+rshift,c+cshift); } } /*初始化背景模型:结束*/ else { /*******初始化背景模型:开始******/ for(vector<Mat>::iterator iter = backgroundModel.begin(); iter != backgroundModel.end();iter++) { for(;;) { cshift = abs(randu<int>()%neighborWidth) - neighborWidth/2; rshift = abs(randu<int>()%neighborHeight) - neighborHeight/2; if( !(cshift == 0 && rshift==0) ) break; } (*iter).at<Vec3b>(r,c) = fristFrame.at<Vec3b>(r+rshift,c+cshift); } /*****初始化背景模型:结束 ******/ } } } } float OriginalVibe::distanceL2(const Vec3b & src1,const Vec3b& src2) { return pow( pow(src1[0]-src2[0],2.0) +pow(src1[1]-src2[1],2.0) + pow(src1[2] - src2[2],2.0),0.5); } int OriginalVibe::distanceL1(const Vec3b & src1,const Vec3b& src2) { return abs(src1[0]-src2[0])+abs(src1[1] - src2[1])+abs(src1[2]-src2[2]) ; } void OriginalVibe::originalVibe_ClassifyAndUpdate_BGR(const Mat &frame,OutputArray &_segmentation) {//*编号1 int height = frame.rows; int width = frame.cols; int cshift; int rshift; _segmentation.create(frame.size(),CV_8UC1); Mat segmentation = _segmentation.getMat(); RNG rng; for(int r =0 ;r < height; r++) {//编号1-1 for(int c = 0;c < width ;c++) {//编号1-1-1 int count = 0; Vec3b pixel = frame.at<Vec3b>(r,c); for( vector<Mat>::iterator iter = backgroundModel.begin() ;iter != backgroundModel.end(); iter++) {//编号1-1-1-1 // // if( distanceL1(pixel,(*iter).at<Vec3b>(r,c)) < 4.5*this->distanceThreshold ) { count++; if(count >= this->minMatch) { //第一步:更新模型update /**********开始更新模型*************/ int random = rng.uniform(0,this->updateFactor); if(random == 0) { int updateIndex = rng.uniform(0,this->numberSamples); backgroundModel[updateIndex].at<Vec3b>(r,c) = pixel; } random = rng.uniform(0,this->updateFactor); if(random == 0) { /****************************************/ if( c < neighborWidth/2 || c > width - neighborWidth/2-1 || r < neighborHeight/2 || r > height - neighborHeight/2-1 ) { for(;;) { cshift = abs(randu<int>()%neighborWidth) - neighborWidth/2; rshift = abs(randu<int>()%neighborHeight) - neighborHeight/2; if(!(cshift == 0 && rshift==0)) break; } if(c + cshift < 0 || c + cshift >=width) cshift*=-1; if(r + rshift < 0 || r + rshift >= height) rshift*=-1; int updateIndex = rng.uniform(0,this->numberSamples); backgroundModel[updateIndex].at<Vec3b>(r+rshift,c+cshift) = pixel; } else { for(;;) { cshift = abs(rand()%neighborWidth) - neighborWidth/2; rshift = abs(rand()%neighborHeight) - neighborHeight/2; if(!(cshift == 0 && rshift==0)) break; } int updateIndex = rng.uniform(0,this->numberSamples); backgroundModel[updateIndex].at<Vec3b>(r+rshift,c+cshift) = pixel; } /****************************************/ } /* *********结束更新模型************ */ //第二步:分类classify segmentation.at<uchar>(r,c) = this->BACK_GROUND; break; } } }//编号1-1-1-1 if(count < this->minMatch)//classify segmentation.at<uchar>(r,c) = this->FORE_GROUND; }//编号1-1-1 }//编号1-1 }//*编号1
vibe前景检测算法的结果:(可以和帧差法做一比较)
【结论】
可以看到vibe算法,并没有像帧差法那样,产生大的空洞,但是会有鬼影出现
《Background Subtraction: Experiments and Improvements for ViBe》对上述原始的vibe算法,做了很多改进:
1、把固定的距离阈值,变为自适应的阈值
2、距离的计算方法改为codebook算法中距离计算公式
3、针对闪光点的判断
等等。下面还得接着研究对vide算法的改进