opencv-视频处理-实时的前景检测-Vibe算法

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算法的改进

时间: 2024-10-03 22:15:42

opencv-视频处理-实时的前景检测-Vibe算法的相关文章

运动目标检测ViBe算法

一.运动目标检测简介   视频中的运动目标检测这一块现在的方法实在是太多了.运动目标检测的算法依照目标与摄像机之间的关系可以分为静态背景下运动检测和动态背景下运动检测.先简单从视频中的背景类型来讨论.        静态背景下的目标检测,就是从序列图像中将实际的变化区域和背景区分开了.在背景静止的大前提下进行运动目标检测的方法有很多,这些方法比较侧重于背景扰动小噪声的消除,如:1.背景差分法2.帧间差分法3.光流法4.混合高斯模型(GMM)5.码本(codebook)还有这些方法的变种,例如三帧

ViBe(Visual Background extractor)背景建模或前景检测

ViBe算法:ViBe - a powerful technique for background detection and subtraction in video sequences 算法官网:http://www2.ulg.ac.be/telecom/research/vibe/ 描述: ViBe是一种像素级视频背景建模或前景检测的算法,效果优于所熟知的几种算法,对硬件内存占用也少. Code: 算法执行效率测试程序,windows和linux操作系统下的程序和c/c++文件都可以在作者

目标检测之vibe---ViBe(Visual Background extractor)背景建模或前景检测

ViBe算法:ViBe - a powerful technique for background detection and subtraction in video sequences 算法官网:http://www2.ulg.ac.be/telecom/research/vibe/ 描述: ViBe是一种像素级视频背景建模或前景检测的算法,效果优于所熟知的几种算法,对硬件内存占用也少. Code: 算法执行效率测试程序,windows和linux操作系统下的程序和c/c++文件都可以在作者

[综]前景检测GMM

tornadomeet 前景检测算法_4(opencv自带GMM) http://www.cnblogs.com/tornadomeet/archive/2012/06/02/2531705.html 前面已经有3篇博文介绍了背景减图方面相关知识(见下面的链接),在第3篇博文中自己也实现了gmm简单算法,但效果不是很好,下面来体验下opencv自带2个gmm算法. opencv实现背景减图法1(codebook和平均背景法) http://www.cnblogs.com/tornadomeet/

前景检测算法_3(GMM)

摘要 本文通过opencv来实现一种前景检测算法——GMM,算法采用的思想来自论文[1][2][4].在进行前景检测前,先对背景进行训练,对图像中每个背景采用一个混合高斯模型进行模拟,每个背景的混合高斯的个数可以自适应.然后在测试阶段,对新来的像素进行GMM匹配,如果该像素值能够匹配其中一个高斯,则认为是背景,否则认为是前景.由于整个过程GMM模型在不断更新学习中,所以对动态背景有一定的鲁棒性.最后通过对一个有树枝摇摆的动态背景进行前景检测,取得了较好的效果. 关键字:GMM,opencv,前景

paper 83:前景检测算法_1(codebook和平均背景法)

前景分割中一个非常重要的研究方向就是背景减图法,因为背景减图的方法简单,原理容易被想到,且在智能视频监控领域中,摄像机很多情况下是固定的,且背景也是基本不变或者是缓慢变换的,在这种场合背景减图法的应用驱使了其不少科研人员去研究它. 但是背景减图获得前景图像的方法缺点也很多:比如说光照因素,遮挡因素,动态周期背景,且背景非周期背景,且一般情况下我们考虑的是每个像素点之间独立,这对实际应用留下了很大的隐患. 这一小讲主要是讲简单背景减图法和codebook法. 一.简单背景减图法的工作原理. 在视频

学习OpenCV范例(二十四)—ViBe前景检测(二)

最近导师没给什么项目做,所以有那么一点点小时间,于是就研究起了前景检测,既然前景检测有很多种算法,那干脆就把这些模型都学起来吧,以后用到前景检测时至少还有那么几种方法可以选择,上次介绍的是GMM模型,其实GMM模型本身就是一个很不错的模型,现在也很多人在研究,并且做改进,主要是OpenCV有函数调用,用起来非常方便,当我们都在兴高采烈的讨论GMM各种好的时候,B哥不爽了,他说老子是搞前景检测的,怎么可能让你们这么嚣张,而且老子就不按照你那套路来,什么高斯模型,混合高斯模型,我统统不用,就来个简单

opencv-视频处理-实时前景检测-阈值法

阈值法: 对每一帧进行阈值处理,取较低的一个阈值进行二值化处理.假设以下为视频流中的任意一帧 代表任意一点处的亮度值(灰度空间),代表一个固定的阈值,对当前帧做以下二值化处理: 该算法比较适合运动物体的亮度大于周围环境的情况,如夜晚的汽车前灯.尾灯等. 下面基于阈值法的前景检测,完成夜晚视频中车辆的检测.跟踪和计数: [算法的步骤] 1.首先画出感兴趣区域,步骤再此博文已详细描述:视频中画出感兴趣区域 2.对进入感兴趣区域的车辆进行前灯的检测,跟踪和计数 代码如下: #include<iostr

【OpenCV入门教程之十七】OpenCV重映射 &amp; SURF特征点检测合辑

本系列文章由@浅墨_毛星云 出品,转载请注明出处. 文章链接:http://blog.csdn.net/poem_qianmo/article/details/30974513 作者:毛星云(浅墨)    微博:http://weibo.com/u/1723155442 知乎:http://www.zhihu.com/people/mao-xing-yun 邮箱: [email protected] 写作当前博文时配套使用的OpenCV版本: 2.4.9 本篇文章中,我们一起探讨了OpenCV中