首页 > 代码库 > 背景减法——Vibe
背景减法——Vibe
VIBE是Barnich和Droogenbroeck在2011年发表的《VIBE:A universalbackground subtraction algorithm for video sequence》中提出。其在模型中大量的使用了随机策略,有着意想不到的准确率和鲁棒性,该方法简单实用,计算代价低,可以应用于嵌入式系统中。
模型表示:
对输入视频帧的每个像素都建立一个背景模型M(x)={},里面包含N个样本值,每个样本值是从之前的帧提取的像素的颜色值。
分类方法:一个像素当前输入的颜色值v(x),该像素对应的背景模型M(x);
在RGB颜色空间中,以v(x)为球心,R为半径画一个球体,统计包含在球体内的M(x)中样本点的个数,如果数目超过给定的阈值的话判定其为背景点。
在编程实现的时候只要计算v(x)和每个样本点在RGB空间的距离,如果距离小于R就表示样本点在v(x)为球心的球体内。
图6.1.8.1 在2-D空间中比较输入的颜色值和样本点
在实验过程中设定了背景模型的样本数为20,R值为20,球体内样本点数的阈值为2.
模型更新:
与自组织背景模型的核心思想一样,VIBE也认为第一帧极大的近似于真实模型。同时也基于另一个假设:相邻的像素点有着相似的空间分布关系。
其优势就是可以最大程度的减少训练背景模型的时间。而且在遇到全局的光源突变时可以抛弃背景模型中所有的样本点,重新初始化,然后在第二帧就可以继续进行前景检测
具体的实施方法:从相邻的像素点中随机的抽取N个像素点作为其背景模型的样本点
模型更新:
(1)无记忆的更新策略:
作者认为在更新背景时使用先进-先出(即用新的样本值替代模型中最久的样本值)的策略是不合适的,,这样只能维持在很少一段时间内保留下来的样本值,时间相关性很低。过去的背景减法为了处理这种方法往往是将模型中的样本数目扩大,这会只用大量内存,而且计算量也大大提高。
作者使用了无记忆的更新策略,具体方法是从N个样本中随机的选择一个样本,然后用新的样本点进行替换。这样可以极大(理论上是无限)的延长其时间相关性。假设我们输入一个图6.1.8.1的颜色值v(x),用其进行更新背景模型,如下图随机的选择。
图6.1.8.2 无记忆更新图示
(2)随机的时间子采样
作者认为没有必要每当输入一个颜色值并且其判定为背景值时都进行模型更新。但是如果每隔一个固定时间进行更新也会错过一些重要的更新。
再次作者依然采取了一个随机策略,在该像素输入的16个判定为背景的值的过程中,有一次机会进行模型更新。
(3)空间传播策略
作者认为在好的样本点不能独用,通过空间传播的方法可以让邻居像素点共享更新的像素点,这一策略是VIBE中极富革新性的一个策略,可以有效的消除ghost,并且在相机抖动的情况下也能有一定程度的改善。
#include <stdio.h>
#include <cv.h>
#include <cxcore.h>
#include <highgui.h>
#include <stdlib.h>
#include <time.h>
#define cvConvert( src, dst ) cvConvertScale( (src), (dst), 1, 0 )
int random(int a,int b) //产生随机数函数
{
int i;
i=rand()%b+a ; //产生a--a+b的随机数。
return (i) ;
}
int main( int argc, char** argv )
{
IplImage* pFrame = NULL;
IplImage* segmap = NULL;
IplImage* simple[20]={NULL};
//IplImage* smooth=NULL;
CvCapture* pCapture = NULL;
int nFrmNum = 0;
int T=0;
int N=20;double R=20;int min=2;int ts=16; //定义vibe基本参数
int height,width,step,chanels;
uchar* data; //定义指针型访问!!!!
uchar* simdata[20];
uchar* sg;
int change=0;
int index; //定义样本索引
double beta; //设定一个光源突变检测阈值
int fgcount=0; //定义一帧检测中属于前景的像素点数,用于判断是否光源突变;
int i,j,x,y;
cvNamedWindow("video", 1); //创建视频窗口
cvNamedWindow("segment map",1); //创建切割图窗口
cvMoveWindow("video", 30, 0);
cvMoveWindow("segment map", 690, 0);
if( argc > 2 )
{
fprintf(stderr, "Usage: bkgrd [video_file_name]\n");
return -1;
}
//打开视频文件
if(argc == 2)
if( !(pCapture = cvCaptureFromFile(argv[1])))
{
fprintf(stderr, "Can not open video file %s\n", argv[1]);
return -2;
}
//打开摄像头
if (argc == 1)
if( !(pCapture = cvCaptureFromCAM(-1)))
{
fprintf(stderr, "Can not open camera.\n");
return -2;
}
srand( (int)time( NULL ) ); //初始化随机数产生器,它产生随机数种子
while(pFrame = cvQueryFrame( pCapture )) //获取视频帧
{
nFrmNum++;
T++; //T=0时初始化,T>0时开始减背景
cvSmooth(pFrame,pFrame,CV_GAUSSIAN,3,3); //添加高斯滤波
data=(uchar *)pFrame->imageData;
if(nFrmNum==1) //第一帧建立样本数组,切割图,样本的像素数据
{
for(index=0;index<20;index++)
{
simple[index] = cvCreateImage(cvSize(pFrame->width, pFrame->height), IPL_DEPTH_8U,3);
simdata[index]=(uchar *)simple[index]->imageData;
}
segmap = cvCreateImage(cvSize(pFrame->width, pFrame->height), IPL_DEPTH_8U,3);
sg=(uchar *)segmap->imageData;
height =pFrame->height; //获取视频的高度,宽度,step,通道
width =pFrame->width;
step =pFrame->widthStep;
chanels =pFrame->nChannels;
beta=0.55*height*width;
}
if(T==1) //当T=1时,初始化样本
{ for(index=0;index<20;index++)
{
for(i=0;i<height;i++) //将周围像素值赋予样本空间
for(j=0;j<width;j++)
{
if(i>1&&i<height-1&&j>1&&j<width-2) //根据像素点位置选取,考虑到样本像素点位于图像边缘的时候要特殊处理
{ x=i+index/5-2;y=j+index%5-2;
}
if(i<2&&j<2) //图像左上角
{ x=i+index/5+1;y=j+index%5+1;
}
if(i>height-3&&j<2) //图像左下角
{ x=i+index/5-7 ;y=j+index%5+1;
}
if(i<2&&j>width-3) //图像右上角
{ x=i+index/5 ;y=j+index%5-7;
}
if(i>height-3&&j>width-3) //图像右下角
{ x=i+index/5-7 ;y=j+index%5-7;
}
if(i<2&&j>1&&j<width-2) //图像上边
{ x=i+index/5;y=j+index%5-2;
}
if(i>height-3&&j>1&&j<width-2) //图像下边
{ x=i+index/5-7;y=j+index%5-2;
}
if(i>1&&i<height-1&&j<2) //图像左边
{ x=i+index/5-2;y=j+index%5;
}
if(i>1&&i<height-1&&j>width-3) //图像右边
{ x=i+index/5-2;y=j+index%5-7;
}
simdata[index][i*step+j*chanels+0]=data[x*step+y*chanels+0];
simdata[index][i*step+j*chanels+1]=data[x*step+y*chanels+1];
simdata[index][i*step+j*chanels+2]=data[x*step+y*chanels+2];
}
}
}
if(T!=1&&nFrmNum%3==2) //T不等于1时减背景
{
double a[3]={0,0,0};
for(x=1;x<width-1;x++) //按行列顺序执行
{
for(y=1;y<height-1;y++)
{
int count=0; //定义分类阈值count,距离dist,
double dist=0;
index=0;
while((count<min)&&(index<N)) //循环方法将图像检测值与样本空间值进行比较
{
a[0]=data[y*step+x*chanels+0]-simdata[index][y*step+x*chanels+0];
a[1]=data[y*step+x*chanels+1]-simdata[index][y*step+x*chanels+1];
a[2]=data[y*step+x*chanels+2]-simdata[index][y*step+x*chanels+2];
dist=sqrt(a[0]*a[0]+a[1]*a[1]+a[2]*a[2]); //计算出欧氏颜色空间的距离
if(dist<R) //判断是否小于设定的距离,是的话计数器加1
{count++; }
index++;
}
if(count>=min) //该检测值属于背景,则分割图显示为背景,且存在1/5的可能性存为样本值
{ //printf("count=%d\n",count); //调试用
sg[y*step+x*chanels+0]=0; //令切割图中相应点为黑色
sg[y*step+x*chanels+1]=0;
sg[y*step+x*chanels+2]=0;
int rdm=random(0,5);
if(rdm==0)
{ rdm=random(0,19); //随即选择一个样本并替换
simdata[rdm][y*step+x*chanels+0]=data[y*step+x*chanels+0];
simdata[rdm][y*step+x*chanels+1]=data[y*step+x*chanels+1];
simdata[rdm][y*step+x*chanels+2]=data[y*step+x*chanels+2];
}
rdm=random(0,5);
if(rdm==0) //检测值属于背景,存在1/5的可能性向邻居一个节点传播
{ int xng=0, yng=0;
while(xng==0 && yng==0)
{
xng=random(-1,2); //随机选择八个中的一个邻居像素点
yng=random(-1,2);
}
rdm=random(0,19);
simdata[rdm][(y+yng)*step+(x+xng)*chanels+0]=data[y*step+x*chanels+0];
simdata[rdm][(y+yng)*step+(x+xng)*chanels+1]=data[y*step+x*chanels+1];
simdata[rdm][(y+yng)*step+(x+xng)*chanels+2]=data[y*step+x*chanels+2];
}
}
else
{
sg[y*step+x*chanels+0]=255; //令切割图中相应点为白色
sg[y*step+x*chanels+1]=255;
sg[y*step+x*chanels+2]=255;
fgcount++;
}
}
}
if(fgcount>beta) //超越阈值时判定为光源突变,令T=0,重新初始化
{
T=0;
}
fgcount=0; //初始fgcount=0,为下一帧检测做准备;
}
cvShowImage("video", pFrame);
cvShowImage("segment map", segmap);
if( cvWaitKey(2) >= 0 )
break;
}
cvDestroyWindow("video");
cvDestroyWindow("segment map");
cvReleaseImage(&pFrame);
cvReleaseImage(&segmap);
cvReleaseImage(simple);
//cvReleaseImage(&smooth);
cvReleaseCapture(&pCapture);
return 0;
}