Adaptive background mixture models for real-time tracking
1.算法介绍:
1.1.单高斯背景建模是混合高斯背景建模的基础,适合较长时间光照变化不明显的情况,也不能处理背景中有运动目标的问题。如果光照变化比较大,可引入混合高斯模型(MOG)为图像中的每个像素建模。
用代表t时刻的某个像素,并用K个高斯函数来为其建模,K的取值一般在3-5之间,这取决于计算机的内存和对算法速度的需求,K 越大,处理波动的能力越强,处理时间也会变长。每个像素的概率密度函数可以用
该式表示了t时刻第i个高斯分布。为该分布所占的权重,,分别为该分布的均值和协方差矩阵。是一个高斯分布函数:
为了计算方便,假设像素的各个通道相互独立且具有同样的方差,协方差矩阵一般可以表示为。这样就为每个像素建立了一个混合高斯模型。
1.2.(背景更新)在获得新的像素值后,将当前帧的像素值与混合高斯模型的K个高斯分布分别匹配。匹配规则为:
S实际中一般取2.5。若匹配成功,则按下列公式更新混合高斯模型中第一个匹配的高斯模型参数和权重,其他模型的参数和权重保持不变。
其中,是用户定义的学习率,决定着背景更新的速度,值越大,更新越快。是参数学习率,一般为/ ,程序中设置为。如果在MOG中没有一个高斯模型与获得的新像素所匹配,则在MOG中,/的值最小的模型将被新的高斯模型替代,即最不可能为背景的模型将被替换。新的高斯模型的均值为新的像素值,初始化一个较大的方差和一个较小的权值,其他的模型保持本来的均值和方差,但权重会衰减,即按下式更新:
1.3.在MOG中一些模型表示了背景,而其他的模型就表示了前景。故此需要一个方法来分别。如果背景是静态的,那么所有的模型就有一个稳定而较小的方差,且权值也会比较大。当一个运动目标进入背景时,新的像素值不匹配任一模型,方差势必会增大,并且会一直保持下去,直到该运动目标静止不动。受此启发,可以根据每个模型的/的值对混合模型进行降序排列,选取靠在前面的B个模型作为背景,其中需满足:
2.代码:
2.1.头文件
#ifndef MOG_H_
#define MOG_H_
#include "opencv2\opencv.hpp"
#include "opencv2\core\core.hpp"
#include "opencv2\core\types_c.h"
/* default parameters of gaussian background detection algorithm */
#define CV_MOG_BACKGROUND_THRESHOLD 0.7 /* threshold sum of weights for background test */
#define CV_MOG_STD_THRESHOLD 2.5 /* lambda=2.5 is 99% */
#define CV_MOG_WINDOW_SIZE 200 /* Learning rate; alpha = 1/CV_GBG_WINDOW_SIZE */
#define CV_MOG_NGAUSSIANS 5 /* = K = number of Gaussians in mixture */
#define CV_MOG_WEIGHT_INIT 0.05
#define CV_MOG_SIGMA_INIT 30
#define CV_MOG_MINAREA 15.f
using namespace std;
using namespace cv;
class m_BackgroundSubtractorMOG
{
public:
//!the default constructor
m_BackgroundSubtractorMOG();
//! the full constructor that takes the length of the history, the number of gaussian mixtures, the background ratio parameter and the noise strength
m_BackgroundSubtractorMOG(int history,int nmixtures,double backgroundRatio,double noiseSigma=0);
//!the destructor
virtual ~m_BackgroundSubtractorMOG();
//! the update operator that takes the next video frame and returns the current foreground mask as 8-bit binary image
virtual void operator()(InputArray image,OutputArray fgmask,double learningRate=0 );
//! computes a background image
virtual void getBackgroundImage(Mat backgroundImage)const;
//!re-initialization method
virtual void initialize(Size frameSize,int frameType);
Size frameSize;
int frameType;
Mat bgmodel;
int nframes;
int history;
int nmixtures;
double varThreshold;
double backgroundRatio;
double noiseSigma;
};
#endif
#include "MOG.h"
static const double defaultBackgroundRatio=CV_MOG_BACKGROUND_THRESHOLD;
static const double defaultVarThreshold=CV_MOG_STD_THRESHOLD*CV_MOG_STD_THRESHOLD;
static const int defaultHistory=CV_MOG_WINDOW_SIZE;
static const int defaultNMixtures=CV_MOG_NGAUSSIANS;
static const double defaultNoiseSigma=CV_MOG_SIGMA_INIT*0.5;
#ifndef min
#define min(x,y) ( ( (x) < (y) )? (x) : (y) )
#endif
#ifndef max
#define max(x,y) ((x)>(y)?(x):(y))
#endif
#ifndef FLT_EPSILON
#define FLT_EPSILON 1.192092896e-07F
#endif
m_BackgroundSubtractorMOG::m_BackgroundSubtractorMOG()
{
frameSize=Size(0,0);
frameType=0;
nframes=0;
backgroundRatio=defaultBackgroundRatio;
varThreshold=defaultVarThreshold;
history=defaultHistory;
nmixtures=defaultNMixtures;
noiseSigma=defaultNoiseSigma;
}
m_BackgroundSubtractorMOG:: m_BackgroundSubtractorMOG(int _history,int _nmixtures,double _backgroundRatio,double _noiseSigma)
{
frameSize=Size(0,0);
frameType=0;
nframes=0;
backgroundRatio=min(_backgroundRatio>0?_backgroundRatio:defaultBackgroundRatio,0.95);
varThreshold=defaultVarThreshold;
history=_history>0?_history:defaultHistory;
nmixtures=min(_nmixtures>0 ? _nmixtures:defaultNMixtures,8);
noiseSigma=_noiseSigma>0?_noiseSigma:defaultNoiseSigma;
}
m_BackgroundSubtractorMOG::~m_BackgroundSubtractorMOG()
{
}
void m_BackgroundSubtractorMOG::initialize(Size _frameSize,int _frameType)
{
frameSize=_frameSize;
frameType=_frameType;
nframes=0;
int nchannels=CV_MAT_CN(frameType);
//printf("nchannels=%d\n",nchannels);
// for each gaussian mixture of each pixel bg model we store ...
// the mixture sort key (w/sum_of_variances), the mixture weight (w),
// the mean (nchannels values) and
// the diagonal covariance matrix (another nchannels values)
bgmodel.create(1,frameSize.height*frameSize.width*nmixtures*(2+2*nchannels),CV_32F);
bgmodel=Scalar::all(0);
}
templatestruct MixData
{
float sortkey;
float weight;
VT mean;
VT var;
};
static void process8uC1(m_BackgroundSubtractorMOG & obj,const Mat & image,Mat &fgmask,double learningRate)
{
int x,y,k,k1,rows=image.rows,cols=image.cols;
float alpha=(float)learningRate;//learningrate
float T=(float)obj.backgroundRatio;//threshold sum of weights for background test
float vT=(float)obj.varThreshold;//determine whether the pixel matched background
int K=obj.nmixtures;
MixData* mptr=(MixData *)obj.bgmodel.data;
const float w0=(float)CV_MOG_WEIGHT_INIT;
const float sk0=(float)CV_MOG_SIGMA_INIT;
const float var0=(float)(CV_MOG_SIGMA_INIT*CV_MOG_SIGMA_INIT);
const float minVar=(float)(obj.noiseSigma*obj.noiseSigma);
for(y=0;y(y);
uchar * dst=fgmask.ptr(y);
if(alpha>0)
{
for(x=0;x=0;k1--)
{
if(mptr[k1].sortkey>=mptr[k1+1].sortkey)
break;
std::swap(mptr[k1],mptr[k1+1]);
}
kHit=k1+1;
break;
}
}
//no appropriate gaussian mixture found at all models,remove the weakest mixture
//and create a new one
if(kHit<0)
{
kHit=k=min(k,K-1);
wsum+=w0-mptr[k].weight;
mptr[k].weight=w0;
mptr[k].mean=pix;
mptr[k].var=var0;
mptr[k].sortkey=sk0;
}
else
{
for(;kT&&kForeground<0)
kForeground=k+1;
}
dst[x]=(uchar)(-(kHit>=kForeground));
}
}
else
{
for(x=0;x=0)
{
float wsum=0;
for(k=0;kT)
{
kForeground=k+1;
break;
}
}
}
dst[x]=(uchar)(kHit<0||kHit>=kForeground?255:0);
}
}
}
}
static void process8uC3(m_BackgroundSubtractorMOG & obj,const Mat & image,Mat & fgmask,double learningRate)
{
int x,y,k,k1,rows=image.rows,cols=image.cols;
float alpha=(float)learningRate;
float T=(float)obj.backgroundRatio;
float vT=(float)obj.varThreshold;
int K=obj.nmixtures;
const float w0=(float)CV_MOG_WEIGHT_INIT;
const float sk0=(float)(w0/(CV_MOG_SIGMA_INIT*sqrt(3.)));
const float var0=(float)(CV_MOG_SIGMA_INIT*CV_MOG_SIGMA_INIT);
const float minVar=(float)(obj.noiseSigma*obj.noiseSigma);
MixData* mptr=(MixData*)obj.bgmodel.data;
for(y=0;y(y);
uchar * dst=fgmask.ptr(y);
if(alpha>0)
{
for(x=0;x=0;k1--)
{
if(mptr[k1].sortkey>=mptr[k1+1].sortkey)
break;
std::swap(mptr[k1],mptr[k1+1]);
}
kHit=k1+1;
break;
}
}
if(kHit<0)
{
kHit=k=min(k,K-1);
wsum+=w0-mptr[k].weight;
mptr[k].weight=w0;
mptr[k].mean=pix;
mptr[k].var=Vec3f(var0,var0,var0);
mptr[k].sortkey=sk0;
}
else
for(;k T && kForeground < 0 )
kForeground = k+1;
}
dst[x] = (uchar)(-(kHit >= kForeground));
}
}
else
{
for( x = 0; x < cols; x++, mptr += K )
{
Vec3f pix(src[x*3], src[x*3+1], src[x*3+2]);
int kHit = -1, kForeground = -1;
for( k = 0; k < K; k++ )
{
if( mptr[k].weight < FLT_EPSILON )
break;
Vec3f mu = mptr[k].mean;
Vec3f var = mptr[k].var;
Vec3f diff = pix - mu;
float d2 = diff.dot(diff);
if( d2 < vT*(var[0] + var[1] + var[2]) )
{
kHit = k;
break;
}
}
if( kHit >= 0 )
{
float wsum = 0;
for( k = 0; k < K; k++ )
{
wsum += mptr[k].weight;
if( wsum > T )
{
kForeground = k+1;
break;
}
}
}
dst[x] = (uchar)(kHit < 0 || kHit >= kForeground ? 255 : 0);
}
}
}
}
void m_BackgroundSubtractorMOG::operator()(InputArray _image, OutputArray _fgmask, double learningRate)
{
Mat image=_image.getMat();
bool needToInitialize=(nframes==0)||learningRate>=1||image.size() != frameSize || image.type() != frameType;;
//printf("needToIntialize=%d,the image type=%d\n",needToInitialize,image.depth());
if(needToInitialize)
initialize(image.size(),image.type());
_fgmask.create(image.size(),CV_8U);
Mat fgmask=_fgmask.getMat();
++nframes;
learningRate=learningRate>=0&&nframes>1?learningRate:1./min(nframes,history);
if(image.type()==CV_8UC1)
process8uC1(*this,image,fgmask,learningRate);
else if(image.type()==CV_8UC3)
process8uC3(*this,image,fgmask,learningRate);
else
printf("cannot process this image type!\n");
}
void m_BackgroundSubtractorMOG::getBackgroundImage(Mat image) const
{
}
#include "MOG.h"
#include
int main(int argc, char** argv)
{
VideoCapture cap;
bool update_bg_model = true;
if( argc < 2 )
cap.open(0);
else
cap.open(argv[1]);
if( !cap.isOpened() )
{
printf("can not open camera or video file\n");
return -1;
}
namedWindow("image", CV_WINDOW_NORMAL);
namedWindow("foreground mask", CV_WINDOW_NORMAL);
namedWindow("foreground image", CV_WINDOW_NORMAL);
namedWindow("mean background image", CV_WINDOW_NORMAL);
m_BackgroundSubtractorMOG bg_model;
Mat img, fgmask, fgimg;
for(;;)
{
cap >> img;
if( img.empty() )
break;
if( fgimg.empty() )
fgimg.create(img.size(), img.type());
//update the model
bg_model(img, fgmask, update_bg_model ? -1 : 0);
fgimg = Scalar::all(0);
img.copyTo(fgimg, fgmask);
Mat bgimg;
bg_model.getBackgroundImage(bgimg);
imshow("image", img);
imshow("foreground mask", fgmask);
imshow("foreground image", fgimg);
if(!bgimg.empty())
imshow("mean background image", bgimg );
char k = (char)waitKey(30);
if( k == 27 ) break;
if( k == ' ' )
{
update_bg_model = !update_bg_model;
if(update_bg_model)
printf("Background update is on\n");
else
printf("Background update is off\n");
}
}
return 0;
}