原文:
http://my.oschina.net/JiamingMai/blog/190901
《Learning OpenCV》中的第9中开篇介绍了一种建立背景模型的方法——平均背景法。 但书上只是简单地介绍了一下这种方法的思想,更多的原理蕴含在它给出的代码当中。
平均背景法是一种建立背景模型的方法。简单地说,就是从视频或摄像头中获取一系列的帧,然后取这些帧中的平均像素值来表征背景。然后再给这些平均像素值加上一定的阈值范围,这就构成了背景模型。在新加入的图像中,如果对应位置的像素超出了这个背景模型中对应位置像素的阈值范围,就把它当作前景。
应用的例子:举一个例子来说明背景模型的作用,比如说深夜里,摄像头检测是否有可疑人物进去,一旦建立了背景模型,摄像头所拍摄的图像将会与背景模型图像进行比较,如果图像中的像素变动很大,则说明前景发生了变化。如果有可疑人物进入摄像头的范围,这种变化就可以被捕捉到,可以进行报警等操作。
参考后面附上的代码,下面介绍平均背景法的建模和分割的步骤:
步骤1: 累积背景
从视频或摄像头中连续获取301帧图像,把这301帧图像分别标为I1、I2 ... I300、 I301
l 从第2帧图像开始,把后面的300帧图像累加起来(这种累加是矩阵加法,下同)I2+…+ I300+I301 (因为考虑到后面的帧差需要从第2帧开始计算,所以这里也是从第2帧开始),得到图像IavgF
定义:帧差 = In-In-1
l 把300帧图像里面的每一个帧差累加起来[I2-I1+I3-I4+…+I299-I300+(I300-I301)]得到图像 Iscratch
步骤2: 创建基于统计学的背景模型
由步骤1得到了两个图像,IavgF 和Iscratch
l 计算出这300帧中每帧的平均值IavgF * (1.0/300) --> IavgF
l 从这300帧的帧差和中计算出平均帧差IdiffF * (1.0/300) --> IdiffF
l 计算阈值的上限:(这里以7.0作为一个计算阈值上限的参数)
IavgF + [(IdiffF + 1.0) * 7.0] --> IhiF
把IhiF这个3通道图分割成3个单通道图Ihi1, Ihi2, Ihi3分别代表B、G、R通道
l 计算阈值的下限:(这里以6.0作为一个计算阈值下限的参数)
IavgF - [(IdiffF + 1.0) * 6.0] --> IlowF
把IhiF这个3通道图分割成3个单通道图Ilow1, Ilow2, Ilow3分别代表B、G、R通道
注意:帧差越大的地方,计算出来的阈值范围就越大。最直观的感受是,变化得比较厉害的那些位置,拥有的阈值范围较大。
步骤3: 利用背景模型分割背景
继续从视频或摄像头中获取图像帧,把获取到的图像帧标为I
l 把I这个3通道图分割成3个单通道图Igray1, Igray2, Igray3分别代表B、G、R通道
l 对Igray1, Igray2,Igray3进行阈值处理:
对于Igray1,如果图像中的像素值在(low1, lhi1)的范围之内,则把该像素值设置为255,否则把该像素值设为0。对Igray2和Igray3作同样的处理。然后合并Igray1, Igray2, Igray3这3个单通道图为1个3通道图Imask。
l 作最后处理: 255 – Imask à Imask
结果是:
l 在建立背景模型Imask的过程中,像素值不常变化的地方,在最后得到的背景模型当中阈值范围较小,只要图像帧I中的像素在这些阈值范围较小的敏感地方,对应于背景模型图像Imask中的像素稍微出现一些变动,结果图像中那些变动的位置就会呈现出白色(因为通过计算所得到的像素值为255),而没有变动的地方则呈现黑色(因为通过计算所得到的像素值为0)。
l 然而,在建立背景模型Imask的过程中,在像素值经常变化的地方,在最后得到的背景模型当中阈值范围较大,所以这些区域就不那么敏感了,需要变化较大时才会显现白色。
参考代码
http://hi.baidu.com/lin65505578/item/3e2faa42d4dd791c896d10d2这篇博文对《Learning OpenCV》一书当中的代码进行了一点扩充,而且给出了一些有关背景模型的资料,我在学习这种方法的时候也是参考了这篇博文,通过代码来理解平均背景法的原理。这里引用一下博文里给出的代码,为了帮助理解代码,我同时给出了一个帮助理解这些代码的伪代码:
#include <cv.h>
#include <highgui.h>
#include <stdio.h>
IplImage *IavgF,*IdiffF,*IprevF,*IhiF,*IlowF;
IplImage *Iscratch,*Iscratch2;
IplImage * Igray1,*Igray2,*Igray3;
IplImage *Ilow1,*Ilow2 ,*Ilow3;
IplImage *Ihi1,*Ihi2,*Ihi3;
IplImage *Imaskt;
IplImage *Imask;
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)
{
CvCapture *capture=cvCreateCameraCapture(0);
if (NULL==capture)
{
printf("没有找到摄像头装置!\n");
return -1;
}
IplImage * Img=cvQueryFrame(capture);
cvNamedWindow("原图",0);
cvNamedWindow("检测图",0);
cvShowImage("原图",Img);
AllocateImages(Img);//初始化
printf("开始统计背景模型\n");
while (Icount<300)//统计背景模型
{
accumulateBackground(Img);
Img=cvQueryFrame(capture);
cvWaitKey(100);
cvShowImage("原图",Img);
printf(".");
}
createModelsfromStats();//就算每一个像素的均值和方差观测
printf("\n统计背景模型结束.....\n");
printf("按任意键开始分割图像.....\n");
cvWaitKey(NULL);
printf("开始分割图像.....\n");
while (1)
{
Img=cvQueryFrame(capture);
backgroundDiff(Img,Imask);
cvShowImage("原图",Img);
cvShowImage("检测图",Imask);
if (27==cvWaitKey(100))
break;
}
cvDestroyAllWindows();
//释放内存
DeallocateImages();
cvReleaseCapture(&capture);
return 0;
}
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.00001;
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);
Imask=cvCreateImage(sz,IPL_DEPTH_8U,1);
cvZero(Iscratch);
cvZero(Iscratch2);
cvZero(Imask);
cvZero(Imaskt);
}
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);
}
void createModelsfromStats()
{
//通过除以输入图像积累的数目计算平均原始图像和绝对差分图像
cvConvertScale(IavgF,IavgF,(double)(1.0/Icount));
cvConvertScale(IdiffF,IdiffF,(double)(1.0/Icount));
//make sure diff is slways something
cvAddS(IdiffF,cvScalar(1.0,1.0,1.0),IdiffF);//确保平均差分图像的值最小是1
setHighThreshold(7.0);//当计算前景和背景阈值以及避免前景阈值和背景阈值相等而出现的退化情况时,我们要缩放这个因素
setLowThreshold(6.0);
}
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 backgroundDiff(IplImage *I,IplImage * Imask)
{
cvConvertScale(I,Iscratch,1,0);
cvSplit(Iscratch,Igray1,Igray2,Igray3,0);
cvInRange(Igray1,Ilow1,Ihi1,Imask);
cvInRange(Igray2,Ilow2,Ihi2,Imaskt);
cvOr(Imask,Imaskt,Imask);
cvInRange(Igray3,Ilow3,Ihi3,Imask);
cvOr(Imask,Imaskt,Imask);
cvSubRS(Imask,cvScalar(255),Imask);
}
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);
cvReleaseImage(&Imask);
}
对应的伪代码
Step1 : Accumulate backgroundIcount = 300 times
convert to float
I(a frame) ------------------> Iscratch + IavgF--> IavgF (do the loop for 300 times)
-
IprevF
=
Iscratch2
Iscratch2 + IdiffF --> IdiffF
Step2: Create model from statistics
Icount = 300
IavgF * (1.0/Icount) --> IavgF
IdiffF * (1.0/Icount) --> IdiffF
IavgF + [(IdiffF + 1.0) * 7.0] --> IhiF --> Ihi1, Ihi2, Ihi3
IavgF - [(IdiffF + 1.0) * 6.0] --> IlowF --> Ilow1, Ilow2, Ilow3
Step3: Segmentation
convert to float
I(a frame) ------------------> Iscratch --> Igray1, Igray2, Igray3
(Ilow1,Ihi1)-> 255 otherwise -> 0
Igray1 ------------------------------------> Imask
(Ilow1,Ihi1)-> 255 otherwise -> 0
Igray2 ------------------------------------> Imaskt
Imask | Imaskt --> Imask
(Ilow1,Ihi1)-> 255 otherwise -> 0
Igray3 -------------------------------------> Imask
Imask | Imaskt --> Imask
255 - Imask --> Imask