背景建模与前景检测(Background Generation And Foreground Detection)

背景建模与前景检测(Background Generation And Foreground Detection)

 

http://www.cnblogs.com/xrwang/archive/2010/02/21/ForegroundDetection.html


作者:王先荣

前言
    在很多情况下,我们需要从一段视频或者一系列图片中找到感兴趣的目标,比如说当人进入已经打烊的超市时发出警报。为了达到这个目的,我们首先需要“学习”背景模型,然后将背景模型和当前图像进行比较,从而得到前景目标。

背景建模
    背景与前景都是相对的概念,以高速公路为例:有时我们对高速公路上来来往往的汽车感兴趣,这时汽车是前景,而路面以及周围的环境是背景;有时我们仅仅对闯入高速公路的行人感兴趣,这时闯入者是前景,而包括汽车之类的其他东西又成了背景。背景建模的方式很多,或高级或简单。不过各种背景模型都有自己适用的场合,即使是高级的背景模型也不能适用于任何场合。下面我将逐一介绍OpenCv中已经实现,或者在《学习OpenCv》这本书中介绍的背景建模方法。
1.帧差
    帧差可说是最简单的一种背景模型,指定视频中的一幅图像为背景,用当前帧与背景进行比较,根据需要过滤较小的差异,得到的结果就是前景了。OpenCv中为我们提供了一种动态计算阀值,然后用帧差进行前景检测的函数——cvChangeDetection(注:EmguCv中没有封装cvChangeDetection,我将其声明到OpenCvInvoke类中,具体实现见文末代码)。而通过对两幅图像使用减法运算,然后再用指定阀值过滤的方法在《学习OpenCv》一书中有详细的介绍。它们的实现代码如下:

帧差

对于类似无人值守的仓库防盗之类的场合,使用帧差效果估计很好。

2.背景统计模型
    背景统计模型是:对一段时间的背景进行统计,然后计算其统计数据(例如平均值、平均差分、标准差、均值漂移值等等),将统计数据作为背景的方法。OpenCv中并未实现简单的背景统计模型,不过在《学习OpenCv》中对其中的平均背景统计模型有很详细的介绍。在模仿该算法的基础上,我实现了一系列的背景统计模型,包括:平均背景、均值漂移、标准差和标准协方差。对这些统计概念我其实不明白,在维基百科上看了好半天 -_-
调用背景统计模型很简单,只需4步而已:

复制代码
    
    
// (1)初始化对象
BackgroundStatModelBase < Bgr > bgModel = new BackgroundStatModelBase < Bgr > (BackgroundStatModelType.AccAvg);
// (2)更新一段时间的背景图像,视情况反复调用(2)
bgModel.Update(image);
// (3)设置当前帧
bgModel.CurrentFrame = currentFrame;
// (4)得到背景或者前景
Image < Gray,Byte > imageForeground = bgModel.ForegroundMask;
复制代码

背景统计模型的实现代码如下:

实现背景统计模型

 

3.编码本背景模型
    编码本的基本思路是这样的:针对每个像素在时间轴上的变动,建立多个(或者一个)包容近期所有变化的Box(变动范围);在检测时,用当前像素与Box去比较,如果当前像素落在任何Box的范围内,则为背景。
    在OpenCv中已经实现了编码本背景模型,不过实现方式与《学习OpenCv》中提到的方式略有不同,主要有:(1)使用单向链表来容纳Code Element;(2)清除消极的Code Element时,并未重置t。OpenCv中的以下函数与编码本背景模型相关:
cvCreateBGCodeBookModel  建立背景模型
cvBGCodeBookUpdate       更新背景模型
cvBGCodeBookClearStale   清除消极的Code Element
cvBGCodeBookDiff         计算得到背景与前景(注意:该函数仅仅设置背景像素为0,而对前景像素未处理,因此在调用前需要将所有的像素先置为前景)
cvReleaseBGCodeBookModel 释放资源
    在EmguCv中只实现了一部分编码本背景模型,在类BGCodeBookModel<TColor>中,可惜它把cvBGCodeBookDiff给搞忘记了 -_-
下面的代码演示了如果使用编码本背景模型:

编码本模型

 

4.高级背景统计模型
    在OpenCv还实现了两种高级的背景统计模型,它们为别是:(1)FGD——复杂背景下的前景物体检测(Foreground object detection from videos containing complex background);(2)MOG——高斯混合模型(Mixture Of Gauss)。包括以下函数:
CvCreateFGDetectorBase  建立前景检测对象
CvFGDetectorProcess     更新前景检测对象
CvFGDetectorGetMask     获取前景
CvFGDetectorRelease     释放资源
    EmguCv将其封装到类FGDetector<TColor>中。我个人觉得OpenCv在实现这个模型的时候做得不太好,因为它将背景建模和前景检测糅合到一起了,无论你是否愿意,在建模的过程中也会检测前景,而只希望前景检测的时候,同时也会建模。我比较喜欢将背景建模和前景检测进行分离的设计。
调用的过程很简单,代码如下:

高级背景统计模型

 

前景检测
    在建立好背景模型之后,通过对当前图像及背景的某种比较,我们可以得出前景。在上面的介绍中,已经包含了对前景的代码,在此不再重复。一般情况下,得到的前景包含了很多噪声,为了消除噪声,我们可以对前景图像进行开运算及闭运算,然后再丢弃比较小的轮廓。

本文的代码

复制代码
     
     
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
using Emgu.CV;
using Emgu.CV.CvEnum;
using Emgu.CV.Structure;
using Emgu.CV.UI;
using Emgu.CV.VideoSurveillance;

namespace ImageProcessLearn
{
public partial class FormForegroundDetect : Form
{
// 成员变量
Capture capture = null ; // 视频捕获对象
Thread captureThread = null ; // 视频捕获线程
private bool isVideoCapturing = true ; // 是否正在捕获视频
private bool isBackgroundModeling = false ; // 是否正在背景建模
private int backgroundModelFrameCount = 0 ; // 已经建模的视频帧数
private bool isForegroundDetecting = false ; // 是否正在进行前景检测
private object lockObject = new object (); // 用于锁定的对象

// 各种前景检测方法对应的对象
BGCodeBookModel < Bgr > bgCodeBookModel = null ; // 编码本前景检测
private const int CodeBookClearPeriod = 40 ; // 编码本的清理周期,更新这么多次背景之后,清理掉很少使用的陈旧条目
private const int CodeBookStaleThresh = 20 ; // 在清理编码本时,使用的阀值(stale大于该阀值的条目将被删除)
FGDetector < Bgr > fgDetector = null ; // Mog或者Fgd检测
BackgroundStatModelFrameDiff < Bgr > bgModelFrameDiff = null ; // 帧差
BackgroundStatModelAccAvg < Bgr > bgModelAccAvg = null ; // 平均背景
BackgroundStatModelRunningAvg < Bgr > bgModelRunningAvg = null ; // 均值漂移
BackgroundStatModelSquareAcc < Bgr > bgModelSquareAcc = null ; // 标准方差
BackgroundStatModelMultiplyAcc < Bgr > bgModelMultiplyAcc = null ; // 标准协方差


public FormForegroundDetect()
{
InitializeComponent();
}

// 窗体加载时
private void FormForegroundDetect_Load( object sender, EventArgs e)
{
// 设置Tooltip
toolTip.Active = true ;
toolTip.SetToolTip(rbMog,
" 高斯混合模型(Mixture Of Gauss) " );
toolTip.SetToolTip(rbFgd,
" 复杂背景下的前景物体检测(Foreground object detection from videos containing complex background) " );
toolTip.SetToolTip(txtMaxBackgroundModelFrameCount,
" 在背景建模时,使用的最大帧数,超出该值之后,将自动停止背景建模。\r\n对于帧差,总是只捕捉当前帧作为背景。\r\n如果设为零,背景检测将不会自动停止。 " );

// 打开摄像头视频捕获线程
capture = new Capture( 0 );
captureThread
= new Thread( new ParameterizedThreadStart(CaptureWithEmguCv));
captureThread.Start(
null );
}

// 窗体关闭前
private void FormForegroundDetect_FormClosing( object sender, FormClosingEventArgs e)
{
// 终止视频捕获
isVideoCapturing = false ;
if (captureThread != null )
captureThread.Abort();
if (capture != null )
capture.Dispose();
// 释放对象
if (bgCodeBookModel != null )
{
try
{
bgCodeBookModel.Dispose();
}
catch { }
}
if (fgDetector != null )
{
try
{
fgDetector.Dispose();
}
catch { }
}
if (bgModelFrameDiff != null )
bgModelFrameDiff.Dispose();
if (bgModelAccAvg != null )
bgModelAccAvg.Dispose();
if (bgModelRunningAvg != null )
bgModelRunningAvg.Dispose();
if (bgModelSquareAcc != null )
bgModelSquareAcc.Dispose();
if (bgModelMultiplyAcc != null )
bgModelMultiplyAcc.Dispose();
}

// EmguCv视频捕获
private void CaptureWithEmguCv( object objParam)
{
if (capture == null )
return ;
bool stop = false ;
while ( ! stop)
{
Image
< Bgr, Byte > image = capture.QueryFrame().Clone(); // 当前帧
bool isBgModeling, isFgDetecting; // 是否正在建模,是否正在前景检测
lock (lockObject)
{
stop
= ! isVideoCapturing;
isBgModeling
= isBackgroundModeling;
isFgDetecting
= isForegroundDetecting;
}
// 得到设置的参数
SettingParam param = (SettingParam) this .Invoke( new GetSettingParamDelegate(GetSettingParam));
// code book
if (param.ForegroundDetectType == ForegroundDetectType.CodeBook)
{
if (bgCodeBookModel != null && (isBgModeling || isFgDetecting))
{
// 背景建模
if (isBgModeling)
{
bgCodeBookModel.Update(image);
// 背景建模一段时间之后,清理陈旧的条目
if (backgroundModelFrameCount % CodeBookClearPeriod == CodeBookClearPeriod - 1 )
bgCodeBookModel.ClearStale(CodeBookStaleThresh, Rectangle.Empty,
null );
backgroundModelFrameCount
++ ;
pbBackgroundModel.Image
= bgCodeBookModel.BackgroundMask.Bitmap;
// 如果达到最大背景建模次数,停止背景建模
if (param.MaxBackgroundModelFrameCount != 0 && backgroundModelFrameCount > param.MaxBackgroundModelFrameCount)
this .Invoke( new NoParamAndReturnDelegate(StopBackgroundModel));
}
// 前景检测
if (isFgDetecting)
{
Image
< Gray, Byte > imageFg = new Image < Gray, byte > (image.Size);
imageFg.SetValue(255d);
// CodeBook在得出前景时,仅仅将背景像素置零,所以这里需要先将所有的像素都假设为前景
CvInvoke.cvBGCodeBookDiff(bgCodeBookModel.Ptr, image.Ptr, imageFg.Ptr, Rectangle.Empty);
pbBackgroundModel.Image
= imageFg.Bitmap;
}
}
}
// fgd or mog
else if (param.ForegroundDetectType == ForegroundDetectType.Fgd || param.ForegroundDetectType == ForegroundDetectType.Mog)
{
if (fgDetector != null && (isBgModeling || isFgDetecting))
{
// 背景建模
fgDetector.Update(image);
backgroundModelFrameCount
++ ;
pbBackgroundModel.Image
= fgDetector.BackgroundMask.Bitmap;
// 如果达到最大背景建模次数,停止背景建模
if (param.MaxBackgroundModelFrameCount != 0 && backgroundModelFrameCount > param.MaxBackgroundModelFrameCount)
this .Invoke( new NoParamAndReturnDelegate(StopBackgroundModel));
// 前景检测
if (isFgDetecting)
{
pbBackgroundModel.Image
= fgDetector.ForgroundMask.Bitmap;
}
}
}
// 帧差
else if (param.ForegroundDetectType == ForegroundDetectType.FrameDiff)
{
if (bgModelFrameDiff != null )
{
// 背景建模
if (isBgModeling)
{
bgModelFrameDiff.Update(image);
backgroundModelFrameCount
++ ;
this .Invoke( new NoParamAndReturnDelegate(StopBackgroundModel)); // 对于帧差,只需要捕获当前帧作为背景即可
}
// 前景检测
if (isFgDetecting)
{
bgModelFrameDiff.Threshold
= param.Threshold;
bgModelFrameDiff.CurrentFrame
= image;
pbBackgroundModel.Image
= bgModelFrameDiff.ForegroundMask.Bitmap;
}
}
}
// 平均背景
else if (param.ForegroundDetectType == ForegroundDetectType.AccAvg)
{
if (bgModelAccAvg != null )
{
// 背景建模
if (isBgModeling)
{
bgModelAccAvg.Update(image);
backgroundModelFrameCount
++ ;
// 如果达到最大背景建模次数,停止背景建模
if (param.MaxBackgroundModelFrameCount != 0 && backgroundModelFrameCount > param.MaxBackgroundModelFrameCount)
this .Invoke( new NoParamAndReturnDelegate(StopBackgroundModel));
}
// 前景检测
if (isFgDetecting)
{
bgModelAccAvg.CurrentFrame
= image;
pbBackgroundModel.Image
= bgModelAccAvg.ForegroundMask.Bitmap;
}
}
}
// 均值漂移
else if (param.ForegroundDetectType == ForegroundDetectType.RunningAvg)
{
if (bgModelRunningAvg != null )
{
// 背景建模
if (isBgModeling)
{
bgModelRunningAvg.Update(image);
backgroundModelFrameCount
++ ;
// 如果达到最大背景建模次数,停止背景建模
if (param.MaxBackgroundModelFrameCount != 0 && backgroundModelFrameCount > param.MaxBackgroundModelFrameCount)
this .Invoke( new NoParamAndReturnDelegate(StopBackgroundModel));
}
// 前景检测
if (isFgDetecting)
{
bgModelRunningAvg.CurrentFrame
= image;
pbBackgroundModel.Image
= bgModelRunningAvg.ForegroundMask.Bitmap;
}
}
}
// 计算方差
else if (param.ForegroundDetectType == ForegroundDetectType.SquareAcc)
{
if (bgModelSquareAcc != null )
{
// 背景建模
if (isBgModeling)
{
bgModelSquareAcc.Update(image);
backgroundModelFrameCount
++ ;
// 如果达到最大背景建模次数,停止背景建模
if (param.MaxBackgroundModelFrameCount != 0 && backgroundModelFrameCount > param.MaxBackgroundModelFrameCount)
this .Invoke( new NoParamAndReturnDelegate(StopBackgroundModel));
}
// 前景检测
if (isFgDetecting)
{
bgModelSquareAcc.CurrentFrame
= image;
pbBackgroundModel.Image
= bgModelSquareAcc.ForegroundMask.Bitmap;
}
}
}
// 协方差
else if (param.ForegroundDetectType == ForegroundDetectType.MultiplyAcc)
{
if (bgModelMultiplyAcc != null )
{
// 背景建模
if (isBgModeling)
{
bgModelMultiplyAcc.Update(image);
backgroundModelFrameCount
++ ;
// 如果达到最大背景建模次数,停止背景建模
if (param.MaxBackgroundModelFrameCount != 0 && backgroundModelFrameCount > param.MaxBackgroundModelFrameCount)
this .Invoke( new NoParamAndReturnDelegate(StopBackgroundModel));
}
// 前景检测
if (isFgDetecting)
{
bgModelMultiplyAcc.CurrentFrame
= image;
pbBackgroundModel.Image
= bgModelMultiplyAcc.ForegroundMask.Bitmap;
}
}
}
// 更新视频图像
pbVideo.Image = image.Bitmap;
}
}

// 用于在工作线程中更新结果的委托及方法
private delegate void AddResultDelegate( string result);
private void AddResultMethod( string result)
{
// txtResult.Text += result;
}

// 用于在工作线程中获取设置参数的委托及方法
private delegate SettingParam GetSettingParamDelegate();
private SettingParam GetSettingParam()
{
ForegroundDetectType type
= ForegroundDetectType.FrameDiff;
if (rbFrameDiff.Checked)
type
= ForegroundDetectType.FrameDiff;
else if (rbAccAvg.Checked)
type
= ForegroundDetectType.AccAvg;
else if (rbRunningAvg.Checked)
type
= ForegroundDetectType.RunningAvg;
else if (rbMultiplyAcc.Checked)
type
= ForegroundDetectType.MultiplyAcc;
else if (rbSquareAcc.Checked)
type
= ForegroundDetectType.SquareAcc;
else if (rbCodeBook.Checked)
type
= ForegroundDetectType.CodeBook;
else if (rbMog.Checked)
type
= ForegroundDetectType.Mog;
else
type
= ForegroundDetectType.Fgd;
int maxFrameCount = 0 ;
int .TryParse(txtMaxBackgroundModelFrameCount.Text, out maxFrameCount);
double threshold = 15d;
double .TryParse(txtThreshold.Text, out threshold);
if (threshold <= 0 )
threshold
= 15d;
return new SettingParam(type, maxFrameCount, threshold);
}

// 没有参数及返回值的委托
private delegate void NoParamAndReturnDelegate();

// 开始背景建模
private void btnStartBackgroundModel_Click( object sender, EventArgs e)
{
if (rbCodeBook.Checked)
{
if (bgCodeBookModel != null )
{
bgCodeBookModel.Dispose();
bgCodeBookModel
= null ;
}
bgCodeBookModel
= new BGCodeBookModel < Bgr > ();
}
else if (rbMog.Checked)
{
if (fgDetector != null )
{
fgDetector.Dispose();
fgDetector
= null ;
}
fgDetector
= new FGDetector < Bgr > (FORGROUND_DETECTOR_TYPE.FGD);
}
else if (rbFgd.Checked)
{
if (fgDetector != null )
{
fgDetector.Dispose();
fgDetector
= null ;
}
fgDetector
= new FGDetector < Bgr > (FORGROUND_DETECTOR_TYPE.MOG);
}
else if (rbFrameDiff.Checked)
{
if (bgModelFrameDiff != null )
{
bgModelFrameDiff.Dispose();
bgModelFrameDiff
= null ;
}
bgModelFrameDiff
= new BackgroundStatModelFrameDiff < Bgr > ();
}
else if (rbAccAvg.Checked)
{
if (bgModelAccAvg != null )
{
bgModelAccAvg.Dispose();
bgModelAccAvg
= null ;
}
bgModelAccAvg
= new BackgroundStatModelAccAvg < Bgr > ();
}
else if (rbRunningAvg.Checked)
{
if (bgModelRunningAvg != null )
{
bgModelRunningAvg.Dispose();
bgModelRunningAvg
= null ;
}
bgModelRunningAvg
= new BackgroundStatModelRunningAvg < Bgr > ();
}
else if (rbSquareAcc.Checked)
{
if (bgModelSquareAcc != null )
{
bgModelSquareAcc.Dispose();
bgModelSquareAcc
= null ;
}
bgModelSquareAcc
= new BackgroundStatModelSquareAcc < Bgr > ();
}
else if (rbMultiplyAcc.Checked)
{
if (bgModelMultiplyAcc != null )
{
bgModelMultiplyAcc.Dispose();
bgModelMultiplyAcc
= null ;
}
bgModelMultiplyAcc
= new BackgroundStatModelMultiplyAcc < Bgr > ();
}
backgroundModelFrameCount
= 0 ;
isBackgroundModeling
= true ;
btnStartBackgroundModel.Enabled
= false ;
btnStopBackgroundModel.Enabled
= true ;
btnStartForegroundDetect.Enabled
= false ;
btnStopForegroundDetect.Enabled
= false ;
}

// 停止背景建模
private void btnStopBackgroundModel_Click( object sender, EventArgs e)
{
StopBackgroundModel();
}

// 停止背景建模
private void StopBackgroundModel()
{
lock (lockObject)
{
isBackgroundModeling
= false ;
}
btnStartBackgroundModel.Enabled
= true ;
btnStopBackgroundModel.Enabled
= false ;
btnStartForegroundDetect.Enabled
= true ;
btnStopForegroundDetect.Enabled
= false ;
}

// 开始前景检测
private void btnStartForegroundDetect_Click( object sender, EventArgs e)
{
isForegroundDetecting
= true ;
btnStartBackgroundModel.Enabled
= false ;
btnStopBackgroundModel.Enabled
= false ;
btnStartForegroundDetect.Enabled
= false ;
btnStopForegroundDetect.Enabled
= true ;
}

// 停止前景检测
private void btnStopForegroundDetect_Click( object sender, EventArgs e)
{
lock (lockObject)
{
isForegroundDetecting
= false ;
}
btnStartBackgroundModel.Enabled
= true ;
btnStopBackgroundModel.Enabled
= false ;
btnStartForegroundDetect.Enabled
= true ;
btnStopForegroundDetect.Enabled
= false ;
}
}

// 前景检测方法枚举
public enum ForegroundDetectType
{
FrameDiff,
AccAvg,
RunningAvg,
MultiplyAcc,
SquareAcc,
CodeBook,
Mog,
Fgd
}

// 设置参数
public struct SettingParam
{
public ForegroundDetectType ForegroundDetectType;
public int MaxBackgroundModelFrameCount;
public double Threshold;

public SettingParam(ForegroundDetectType foregroundDetectType, int maxBackgroundModelFrameCount, double threshold)
{
ForegroundDetectType
= foregroundDetectType;
MaxBackgroundModelFrameCount
= maxBackgroundModelFrameCount;
Threshold
= threshold;
}
}
}
复制代码

    另外,细心的读者发现我忘记贴OpenCvInvoke类的实现代码了,这里补上。多谢指正。

复制代码
     
     
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Runtime.InteropServices;
using Emgu.CV.Structure;
using Emgu.CV.CvEnum;

namespace ImageProcessLearn
{
/// <summary>
/// 声明一些没有包含在EmguCv中的OpenCv函数
/// </summary>
public static class OpenCvInvoke
{
// 自适应动态背景检测
[DllImport( " cvaux200.dll " )]
public static extern void cvChangeDetection(IntPtr prev_frame, IntPtr curr_frame, IntPtr change_mask);

// 均值漂移分割
[DllImport( " cv200.dll " )]
public static extern void cvPyrMeanShiftFiltering(IntPtr src, IntPtr dst, double spatialRadius, double colorRadius, int max_level, MCvTermCriteria termcrit);

// 开始查找轮廓
[DllImport( " cv200.dll " )]
public static extern IntPtr cvStartFindContours(IntPtr image, IntPtr storage, int header_size, RETR_TYPE mode, CHAIN_APPROX_METHOD method, Point offset);

// 查找下一个轮廓
[DllImport( " cv200.dll " )]
public static extern IntPtr cvFindNextContour(IntPtr scanner);

// 用新轮廓替换scanner指向的当前轮廓
[DllImport( " cv200.dll " )]
public static extern void cvSubstituteContour(IntPtr scanner, IntPtr new_contour);

// 结束轮廓查找
[DllImport( " cv200.dll " )]
public static extern IntPtr cvEndFindContour( ref IntPtr scanner);
}
}
复制代码

 

 

后记
    值得注意的是,本文提到的OpenCv函数目前属于CvAux系列,以后也许会加入到正式的图像处理Cv系列,也许以后会消失。最重要的是它们还没有正式的文档。

    其实关于背景模型的方法还有很多,比如《Video-object segmentation using multi-sprite background subtraction》可以在摄像机运动的情况下建立背景,《Nonparametric background generation》利用mean-shift算法处理动态的背景模型,如果我的时间和能力允许,也许会去尝试实现它们。另外,《Wallflower: Principles and practice of background maintenance》比较了各种背景建模方式的差异,我希望能够尝试翻译出来。

    感谢您耐心看完本文,希望对您有所帮助。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值