背景建模算法(一)-------颜 {MOD}背景模型
2011-01-23 10:38:50
http://underthehood.blog.51cto.com/2531780/484191
背景建模算法
1 基本原理
视频图像中运动目标检测的一种方法,其基本思想是对图像的背景进行建模。一旦背景模型建立,将当前的图像与背景模型进行某种比较,根据比较结果确定前景目标(需要检测的运动目标)。
2 难点
(1) 环境光照的变化(光照突然性的变化和缓慢的变化)
(2) 背景的多模态性(背景中存在的细微的运动将影响前景目标检测的结果)
(3) 运动物体的阴影
(4) 图像噪声
(5) 新的不动的物体进入到背景中(如何快速适应背景的变化)
3 分类
背景建模方法可以分为两类,颜 {MOD}背景模型和纹理背景模型。
3.1 颜 {MOD}背景模型
颜 {MOD}背景模型其思想是对图像中每个像素的颜 {MOD}值(灰度或彩 {MOD})进行建模。如果当前图像坐标(x,y)上的像素颜 {MOD}值与背景模型中(x,y)上的像素颜 {MOD}值有较大差异时,当前像素被认为是前景,否则为背景。
颜 {MOD}背景模型的一个最明显的缺点是对阴影敏感,即将阴影误检测为运动目标。在特定场合下,需要在检测后对阴影进行抑制和消除。
3.1.1 平均背景模型
平均背景模型(Average Background Model)是一种简单、计算速度快但对环境光照变化和背景的多模态性比较敏感的一种背景建模算法。其基本思想是:计算每个像素的平均值作为它的背景模型。检测当前帧时,只需要将当前帧像素值I(x,y)减去背景模型中相同位置像素的平均值u(x,y),得到差值d(x,y),将d(x,y)与一个阈值TH进行比较,那么得到输出图像output的值如下:
(3-1)
(3-2)
这里TH可以采用自适应算法进行确定,需要计算每个像素的帧间差的平均值
和标准差
。公式如下:
令
代表t时刻的图像中(x,y)处的像素值,inter代表两帧之间的间隔,通常设置为3,令
如下:
(3-3)
(3-4)
(3-5)
M通常要足够大(>30+inter)来保证
和
的精确性。得到了
和
后TH可以这样确定:
TH =
+
(3-6)
其中
一般设置为2。
为了提高算法的鲁棒性,可以在检测完之后要对背景模型进行更新,对于所有像素(x,y),令
,
,
更新后分别为
,
,
:
(3-7)
(3-8)
(3-9)
这里
为学习率(0~1),
越大,对背景变化的适应速度越快。
于是,平均背景建模算法的流程如下:
a. 计算M帧图像的平均值建立一个初始背景BG并计算
和
。
b. 将当前图像减去BG得到差D,通过公式(3-2)检测前景像素和背景像素。
c. 通过公式(3-7)、(3-8)、(3-9)对BG、
和
进行更新。
d. 返回b直至停止。
算法的改进:增加一个辅助背景SBG(Secondary Background),将SBG的初始值设置为BG,即:
SBG(x,y) = BG(x,y) (3-10)
得到的新的输出图像output_s的值如下:
(3-11)
其中:
THS =
(3-12)
检测后通过下式对辅助背景进行更新:
(3-13)
3.1.2高斯背景模型
单高斯背景模型(Single Gaussian Background Model)的基本思想是:将图像中每一个像素点的颜 {MOD}值看成是一个随机过程X,并假设该点的某一像素值出现的概率服从高斯分布。令I(x,y,t)表示像素点(x,y,t)在t时刻的像素值,则有:
(3-14)
其中
分别为t时刻该像素高斯分布的期望值和标准差。简单来说,每一个像素点的背景模型包含一个期望值
和一个偏差
。
假设一个图像序列I0,I1,…,In,对于坐标为(x,y)的像素,它的初始背景模型的期望值u0(x,y)和偏差
(x,y),另外为了计算偏差,增加一个方差
(x,y):
(3-15)
(3-16)
(3-17)
其中std_init通常设置为20。
对于t时刻的像素值I(x,y,t),按照下面的公式来判断它是否为背景像素,令output为输出图像:
(3-18)
检测完毕后对那些被判定为背景的像素的背景模型进行更新:
(3-19)
(3-20)
(3-21)
高斯背景建模算法的流程如下:
a. 用第一帧图像的数据通过公式(3-15)、(3-16)、(3-17)初始化背景模型
b. 通过公式(3-18)检测前景像素和背景像素。
c. 通过公式(3-19)、(3-20)、(3-21)对背景模型进行更新
d. 返回b直至停止。
算法的改进:混合高斯背景模型(Gaussian Mixture Model)。在单高斯背景模型中将单个高斯分布作为相应某一像素值的概率密度分布,混合高斯背景模型对其进行了扩展,通过多个高斯概率密度函数的加权平均来平滑地近似任意形状的密度分布函数。令I(x,y,t)表示像素点(x,y,t)在t时刻的像素值,则有:
(3-22)
其中K为高斯分布的个数,称为高斯混合概率密度的混合系数。
为t时刻第i个高斯分量的加权系数,也即权重。
对于一个像素的K个高斯分量,根据
的值对它们从大到小进行排列,对于满足下式的前B个高斯分布被当作是背景模型。
(3-23)
其中T是背景模型占有高斯分布的最小比例,通常为0.7,如果T太小退化为单高斯,T较大则可以描述复杂的动态背景。
对于当前像素(x,y,t),如果它的值I(x,y,t)与它的背景模型中第k(k<=B)个高斯分布匹配,即I(x,y,t)在
范围之内,
一般设置为2.5,那么该像素被认为是背景,否则是前景。令输出图像为output,公式如下:
(3-24)
在检测完前景之后,若该像素被认为是前景,即前B个高斯分布中没有一个与之匹配,则用一个新的高斯分布取代权重最小的那个高斯分布。新的分布的期望值即为当前的像素值,同时为它分配一个较大的初始偏差std_init和较小的初始权重值weight_init。
若该像素被认为是背景,则对该像素的各个高斯分布的权重做如下调整:
(3-25)
其中
为学习率,值在0~1之间。如果第i个高斯分布与当前像素匹配,则
,否则
。
对于与当前像素匹配的高斯分布,更新它们的期望值和偏差值:
(3-26)
(3-27)
(3-28)
3.1.3非参数化背景模型
高斯背景模型对像素点值的概率密度分布做了假设,而这个假设不一定成立。非参数化背景模型不对像素点值做出任何假设,而通过概率密度估计的方法建立像素的背景统计模型。其基本思想是:为被建模场景中的像素点保存一段时间内的一系列颜 {MOD}样本值,并根据这些样本值来估计当前帧图像中每一个像素点的概率。
给定一维空间n个数据点集合S={xi}i=1…n,它的未知概率密度函数为P(x),取核函数为Kh(x),那么在x点处密度可用如下式估计:
(3-29)
这里h是核函数的带宽,x为核函数的中心点,核函数估计可以理解为:将在每个采样点为中心的局部函数的平均效果,作为该采样点概率密度函数的估计值,或是核估计器在被估计点为中心的窗口内,计算数据点加权的局部平均。
均值漂移(Mean Shift)算法是目前最流行的非参数化背景建模方法。对于目标像素,将它建模为q={qu},u=1…m, m为特征值的个数,在图像处理中为灰度等级划分的个数,也是灰度直方图的bins值。且有:
(3-30)
其中X0为目标区域窗口的中心像素的坐标向量,Xi为窗口内第i点的坐标向量,C为归一化常数,使得
。k(x)为核函数,H为核函数的带宽向量。δ(x)为脉冲函数(当x
不为0时,δ(x)=0),保证只有u特征值的像素才对概率分布做出贡献。函数b:R2->{1…m}是位于Xi的像素向颜 {MOD}索引的映像。由于受到遮挡或者背景的影响,目标模型中心附近的像素比其他像素更可靠,因此对于不同位置的像素赋予不用的权值,靠近中心的像素赋予一个较大的权值,远离中心的像素赋予一个小的权值。因此引入k(x),其中
的作用是消除不同大小目标计算时的影响。由
可以得到:
(3-31)
匹配对象,也称为候选区域,也采用特征值加权概率分布。匹配窗口的中心坐标为Y,也是核函数的中心坐标。Xi为匹配窗口中第i点的坐标向量,Hh为匹配窗口的核函数带宽向量。Ch为匹配窗口特征向量的归一化常数。匹配对象的被描述为p={pu},u=1…m
(3-32)
匹配对象和目标模型的相似程度采用Bhattacharyya系数进行描述。
(3-33)
匹配过程就是寻找相似函数最大值的寻优过程,Mean-Shift 采用的是梯度下降法。首先将
(Y)在p(Y0)附近进行泰勒级数展开,取前两项。即:
(3-34)
定义
从而
因此
要使得
向最大值迭代,只要Y的搜索方向与梯度方向一致即可,通过求导可得到Y-0的梯度方向为:
其中
为权值,g(x)=-k’(x),因此如果如下确定Y1,那么Y1-Y0将于梯度方向一致。
(3-35)
根据上面的基础,mean shift算法的过程如下:
(1) 计算目标模型的初始分布:{qu}u=1…m,目标被估计位置为Y0。
(2) 用Y0初始化当前帧的候选区域位置,计算分布{pu(Y0)}u=1…m,估计Bhattacharyya系数:
。
(3) 根据
计算得到权值
。
(4) 根据均值平移向量,计算目标的新位置:
(5) 计算{pu(Y1)}u=1…m和
。
(6) 如果
<
,则Y1
<- (Y0+Y1)/2。
(7) 如果Y1-Y0 < ε则停止,否则 Y0 <- Y1,执行步骤(2)。
3.1.4 CodeBook背景模型
CodeBook算法的基本思想是得到每个像素的时间序列模型。这种模型能很好地处理时间起伏,缺点是需要消耗大量的内存。CodeBook算法为当前图像的每一个像素建立一个CodeBook(CB)结构,每个CodeBook结构又由多个CodeWord(CW)组成。CB和CW的形式如下:
CB={CW1,CW2,…CWn,t}
CW={lHigh,lLow,max,min,t_last,stale}
其中n为一个CB中所包含的CW的数目,当n太小时,退化为简单背景,当n较大时可以对复杂背景进行建模;t为CB更新的次数。CW是一个6元组,其中IHigh和ILow作为更新时的学习上下界,max和min记录当前像素的最大值和最小值。上次更新的时间t_last和陈旧时间stale(记录该CW多久未被访问)用来删除很少使用的CodeWord。
假设当前训练图像I中某一像素为I(x,y),该像素的CB的更新算法如下,另外记背景阈值的增长判定阈值为Bounds:
(1) CB的访问次数加1;
(2) 遍历CB中的每个CW,如果存在一个CW中的IHigh,ILow满足ILow≤I(x,y)≤IHigh,则转(4);
(3) 创建一个新的码字CWnew加入到CB中, CWnew的max与min都赋值为I(x,y),IHigh <- I(x,y) + Bounds,ILow <- I(x,y) – Bounds,并且转(6);
(4) 更新该码字的t_last,若当前像素值I(x,y)大于该码字的max,则max <- I(x,y),若I(x,y)小于该码字的min,则min <- I(x,y);
(5) 更新该码字的学习上下界,以增加背景模型对于复杂背景的适应能力,具体做法是:若IHigh < I(x,y) + Bounds,则IHigh 增长1,若ILow > I(x,y) – Bounds,则ILow减少1;
(6) 更新CB中每个CW的stale。
使用已建立好的CB进行运动目标检测的方法很简单,记判断前景的范围上下界为minMod和maxMod,对于当前待检测图像上的某一像素I(x,y),遍历它对应像素背景模型CB中的每一个码字CW,若存在一个CW,使得I(x,y) < max + maxMod并且I(x,y) > min – minMod,则I(x,y)被判断为背景,否则被判断为前景。
在实际使用CodeBook进行运动检测时,除了要隔一定的时间对CB进行更新的同时,需要对CB进行一个时间滤波,目的是去除很少被访问到的CW,其方法是访问每个CW的stale,若stale大于一个阈值(通常设置为总更新次数的一半),移除该CW。
综上所述,CodeBook算法检测运动目标的流程如下:
(1) 选择一帧到多帧使用更新算法建立CodeBook背景模型;
(2) 按上面所述方法检测前景(运动目标);
(3) 间隔一定时间使用更新算法更新CodeBook模型,并对CodeBook进行时间滤波;
(4) 若检测继续,转(2),否则结束。
(未完待续)
p.s:公式看不清楚,可以点开看
本文出自 “
UnderTheHood” 博客,请务必保留此出处
http://underthehood.blog.51cto.com/2531780/484191
********************************************
(下面代码转自:http://www.eefocus.com/gcxalxm/blog/14-03/302386_0ef72.html)
#include "highgui.h"
#include "cv.h"
#include "cxcore.h"
/*为不同临时图像和统计属性图像创建指针*/
//三通道float图像
IplImage *IavgF,*IdiffF,*IprevF,*IhiF,*IlowF;
IplImage *Iscratch,*Iscratch2;
//单通道float图像
IplImage *Igray1,*Igray2,*Igray3;
IplImage *Ilow1, *Ilow2, *Ilow3;
IplImage *Ihi1, *Ihi2, *Ihi3;
//单通道Byte图像
IplImage *Imaskt;
//自己定义的图像
//IplImage *mypic;
//计算背景建模使用图片的张数
float Icount;
/*调用函数的声明*/
void AllocateImages(IplImage *I);
void accumulateBackground(IplImage * I);
void createModelsfromStats();
void setHighThreshold(float scale);
void setLowThreshold(float scale);
void backgroundDiff(IplImage *I,IplImage * Imask);
void DeallocateImages();
int main(int argc,char** argv)
{
cvNamedWindow("intput",CV_WINDOW_AUTOSIZE); //创建输入显示窗口
cvNamedWindow("output",CV_WINDOW_AUTOSIZE); //创建输出显示窗口
CvCapture* capture = cvCreateFileCapture("bike.avi"); //返回一个capture指针,指向视频
IplImage*Img = cvQueryFrame(capture); //从视频中取出的图片
IplImage*Imask = cvCreateImage(cvGetSize(Img),IPL_DEPTH_8U,1);//创建输出图片,这里不能去掉cvCreateImage(cvGetSize(Img),IPL_DEPTH_8U,1),虽然我看例程里省略了
AllocateImages(Img); //调用创建临时图片函数
/*累积图像,只取了前30帧图片*/
while(Icount<30){
accumulateBackground(Img); //调用累积图像的函数,循环30次
Img = cvQueryFrame(capture);
cvShowImage("intput",Img);
cvWaitKey(20);
}
createModelsfromStats(); //背景建模
while(1)
{
Img = cvQueryFrame(capture);
if(!Img) break;
backgroundDiff(Img,Imask); //根据模型分割前景
cvShowImage("output",Imask); //显示图像,视频是一张一张图片连续播放的结果
cvShowImage("intput",Img);
char c = cvWaitKey(33); //当前帧被显示后,等待33ms再读取下一张图片
if(c==27) break; //等待期间按下esc键,ASCII码为27,则循环退出
}
cvReleaseCapture(&capture);
cvDestroyWindow("output");
cvDestroyWindow("intput");
DeallocateImages();
}
/*给需要的所有临时图像分配内存。为了方便,传递一副图像作为大小参考来分配临时图像*/
void AllocateImages( IplImage* I ){
CvSize sz = cvGetSize(I);
IavgF = cvCreateImage(sz,IPL_DEPTH_32F,3);
IdiffF = cvCreateImage(sz,IPL_DEPTH_32F,3);
IprevF = cvCreateImage(sz,IPL_DEPTH_32F,3);
IhiF = cvCreateImage(sz,IPL_DEPTH_32F,3);
IlowF = cvCreateImage(sz,IPL_DEPTH_32F,3);
Ilow1 = cvCreateImage(sz,IPL_DEPTH_32F,1);
Ilow2 = cvCreateImage(sz,IPL_DEPTH_32F,1);
Ilow3 = cvCreateImage(sz,IPL_DEPTH_32F,1);
Ihi1 = cvCreateImage(sz,IPL_DEPTH_32F,1);
Ihi2 = cvCreateImage(sz,IPL_DEPTH_32F,1);
Ihi3 = cvCreateImage(sz,IPL_DEPTH_32F,1);
cvZero( IavgF );
cvZero( IdiffF );
cvZero( IprevF );
cvZero( IhiF );
cvZero( IlowF );
Icount = 0.0001;
Iscratch = cvCreateImage(sz,IPL_DEPTH_32F,3);
Iscratch2 = cvCreateImage(sz,IPL_DEPTH_32F,3);
Igray1 = cvCreateImage(sz,IPL_DEPTH_32F,1);
Igray2 = cvCreateImage(sz,IPL_DEPTH_32F,1);
Igray3 = cvCreateImage(sz,IPL_DEPTH_32F,1);
Imaskt = cvCreateImage(sz,IPL_DEPTH_8U,1);
cvZero( Iscratch );
cvZero( Iscratch2 );
//
mypic = cvCreateImage(sz,IPL_DEPTH_8U,1);
}
/*累积背景图像和每一帧图像差值的绝对值*/
void accumulateBackground( IplImage *I ){
static int first = 1;
cvCvtScale( I,Iscratch,1,0);
if( !first ){
cvAcc( Iscratch,IavgF );
cvAbsDiff( Iscratch,IprevF,Iscratch2 );
cvAcc( Iscratch2,IdiffF );
Icount += 1.0;
}
first = 0;
cvCopy( Iscratch,IprevF );
}
/*累积背景图像与每一帧图像差值的绝对值后,建立一个背景的统计模型*/
/*先定义setHighThreshold与setLowThreshold*/
void setHighThreshold( float scale ){
cvConvertScale(IdiffF,Iscratch,scale);
cvAdd( Iscratch,IavgF,IhiF);
cvSplit( IhiF,Ihi1,Ihi2,Ihi3,0);
}
void setLowThreshold( float scale ){
cvConvertScale(IdiffF,Iscratch,scale);
cvSub( IavgF,Iscratch,IlowF);
cvSplit( IlowF,Ilow1,Ilow2,Ilow3,0);
}
/*建立模型*/
void createModelsfromStats(){
cvConvertScale( IavgF, IavgF, (double)(1.0/Icount)); //IgvgF = IgvgF *(1.0/Icount) + 0
cvConvertScale( IdiffF,IdiffF,(double)(1.0/Icount));
cvAddS( IdiffF,cvScalar(1.0,1.0,1.0),IdiffF);
setHighThreshold(7.0); //函数设置一个阈值,使得对于每一帧图像的绝对差大于平局值7倍的像素都认为是前景
setLowThreshold(6.0);
}
/*建立模型同时有了高低阈值,我们就能把图像分割成前景(不能被背景模型“解释”的图像部分)*/
void backgroundDiff(
IplImage *I,
IplImage *Imask
){
cvCvtScale(I,Iscratch,1,0);
cvSplit( Iscratch,Igray1,Igray2,Igray3,0);
//Channel 1
cvInRange(Igray1,Ilow1,Ihi1,Imask);
//Channel 2
cvInRange(Igray2,Ilow2,Ihi2,Imaskt);
cvOr(Imask,Imaskt,Imask);
//Channel 3
cvInRange(Igray3,Ilow3,Ihi3,Imaskt);
cvOr(Imask,Imaskt,Imask);
//cvCvtScale(Imask,mypic,1,0);
cvSubRS( Imask,cvScalar(255),Imask); //这里书上例程写cvSubRS(Imask,255,Imask);但是这样编译的时候会报错,必须先转换成cvScalar型
}
/*释放内存*/
void DeallocateImages()
{
cvReleaseImage(&IavgF);
cvReleaseImage(&IdiffF);
cvReleaseImage(&IprevF);
cvReleaseImage(&IhiF);
cvReleaseImage(&IlowF);
cvReleaseImage(&Ilow1);
cvReleaseImage(&Ilow2);
cvReleaseImage(&Ilow3);
cvReleaseImage(&Ihi1);
cvReleaseImage(&Ihi2);
cvReleaseImage(&Ihi3);
cvReleaseImage(&Iscratch);
cvReleaseImage(&Iscratch2);
cvReleaseImage(&Igray1);
cvReleaseImage(&Igray2);
cvReleaseImage(&Igray3);
cvReleaseImage(&Imaskt);
}