• [转载]背景建模与前景检测(Background Generation And Foreground Detection)


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

    作者:王先荣

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

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

    帧差
    复制代码
    [DllImport("cvaux200.dll")]
    public static extern void cvChangeDetection(IntPtr prev_frame, IntPtr curr_frame, IntPtr change_mask);
    //backgroundMask为背景,imageBackgroundModel为背景模型,currentFrame为当前帧
    if (backgroundMask == null)
    backgroundMask
    = new Image<Gray, byte>(imageBackgroundModel.Size);
    if (threshold == 0d)
    //如果阀值为0,使用OpenCv中的自适应动态背景检测
    OpenCvInvoke.cvChangeDetection(imageBackgroundModel.Ptr, currentFrame.Ptr, backgroundMask.Ptr);
    else
    {
    //如果设置了阀值,使用帧差
    Image<TColor, Byte> imageTemp = imageBackgroundModel.AbsDiff(currentFrame);
    Image
    <Gray, Byte>[] images = imageTemp.Split();
    backgroundMask.SetValue(0d);
    foreach (Image<Gray, Byte> image in images)
    backgroundMask._Or(image.ThresholdBinary(
    new Gray(threshold), new Gray(255d)));
    }
    backgroundMask._Not();
    复制代码

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

    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;
    复制代码

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

    实现背景统计模型
    复制代码
    /*
    背景统计模型
    作者:王先荣
    时间:2010年2月19日
    */
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Drawing;
    using System.Diagnostics;
    using System.Runtime.InteropServices;
    using Emgu.CV;
    using Emgu.CV.CvEnum;
    using Emgu.CV.Structure;
    using Emgu.CV.UI;
    using Emgu.CV.VideoSurveillance;

    namespace ImageProcessLearn
    {
    //背景模型接口,在IBGFGDetector接口的基础上增加了一个CurrentFrame属性
    public interface IBackgroundStatModel<TColor> : IDisposable
    where TColor : struct, IColor
    {
    /// <summary>
    /// 获取前景
    /// </summary>
    Image<Gray, byte> BackgroundMask { get; }

    /// <summary>
    /// 获取背景
    /// </summary>
    Image<Gray, byte> ForegroundMask { get; }

    /// <summary>
    /// 更新背景模型
    /// </summary>
    /// <param name="image"></param>
    void Update(Image<TColor, byte> image);

    /// <summary>
    /// 计算统计数据
    /// </summary>
    void CalcStatData();

    /// <summary>
    /// 获取或者设置当前帧
    /// </summary>
    Image<TColor, Byte> CurrentFrame
    {
    get;
    set;
    }
    }

    /// <summary>
    /// 使用帧差的方式来建立背景模型
    /// </summary>
    /// <typeparam name="TColor"></typeparam>
    public class BackgroundStatModelFrameDiff<TColor> : IBackgroundStatModel<TColor>
    where TColor : struct, IColor
    {
    //成员
    private Image<TColor, Byte> imageBackgroundModel; //背景模型图像
    private Image<TColor, Byte> currentFrame; //当前帧
    private double threshold; //计算前景时所用的阀值,如果当前帧和背景的差别大于阀值,则被认为是前景
    private Image<Gray, Byte> backgroundMask; //计算得到的背景图像

    /// <summary>
    /// 构造函数
    /// </summary>
    /// <param name="image">用于背景统计模型的背景</param>
    public BackgroundStatModelFrameDiff(Image<TColor, Byte> image)
    {
    imageBackgroundModel
    = image;
    currentFrame
    = null;
    threshold
    = 15d;
    backgroundMask
    = null;
    }

    public BackgroundStatModelFrameDiff()
    :
    this(null)
    {
    }

    /// <summary>
    /// 设置或者获取计算前景时所用的阀值;如果阀值为0,则使用自适应的阀值
    /// </summary>
    public double Threshold
    {
    get
    {
    return threshold;
    }
    set
    {
    threshold
    = value >= 0 ? value : 15d;
    }
    }

    /// <summary>
    /// 更新背景模型
    /// </summary>
    /// <param name="image"></param>
    public void Update(Image<TColor, Byte> image)
    {
    imageBackgroundModel
    = image;
    }

    /// <summary>
    /// 获取或者设置当前帧
    /// </summary>
    public Image<TColor, Byte> CurrentFrame
    {
    get
    {
    return currentFrame;
    }
    set
    {
    currentFrame
    = value;
    CalcBackgroundMask();
    }
    }

    /// <summary>
    /// 计算统计数据
    /// </summary>
    public void CalcStatData()
    {
    }

    /// <summary>
    /// 计算背景
    /// </summary>
    private void CalcBackgroundMask()
    {
    if (imageBackgroundModel == null || currentFrame == null || imageBackgroundModel.Size != currentFrame.Size)
    throw new ArgumentException("在计算背景时发现参数错误。可能是:背景模型图像为空,当前帧为空,或者背景模型图像和当前帧的尺寸不一致。");
    if (backgroundMask == null)
    backgroundMask
    = new Image<Gray, byte>(imageBackgroundModel.Size);
    if (threshold == 0d)
    //如果阀值为0,使用OpenCv中的自适应动态背景检测
    OpenCvInvoke.cvChangeDetection(imageBackgroundModel.Ptr, currentFrame.Ptr, backgroundMask.Ptr);
    else
    {
    //如果设置了阀值,使用帧差
    Image<TColor, Byte> imageTemp = imageBackgroundModel.AbsDiff(currentFrame);
    Image
    <Gray, Byte>[] images = imageTemp.Split();
    backgroundMask.SetValue(0d);
    foreach (Image<Gray, Byte> image in images)
    backgroundMask._Or(image.ThresholdBinary(
    new Gray(threshold), new Gray(255d)));
    }
    backgroundMask._Not();
    }

    /// <summary>
    /// 获取背景
    /// </summary>
    public Image<Gray, Byte> BackgroundMask
    {
    get
    {
    return backgroundMask;
    }
    }

    /// <summary>
    /// 获取前景
    /// </summary>
    public Image<Gray, Byte> ForegroundMask
    {
    get
    {
    return backgroundMask.Not();
    }
    }

    /// <summary>
    /// 释放资源
    /// </summary>
    public void Dispose()
    {
    if (backgroundMask != null)
    backgroundMask.Dispose();
    }
    }

    /// <summary>
    /// 使用平均背景来建立背景模型
    /// </summary>
    /// <typeparam name="TColor"></typeparam>
    public class BackgroundStatModelAccAvg<TColor> : IBackgroundStatModel<TColor>
    where TColor : struct, IColor
    {
    //成员
    private Image<TColor, Single> imageAccSum; //累计图像
    private Image<TColor, Single> imageAccDiff; //累计差值图像
    private int frameCount; //已经累计的背景帧数
    private Image<TColor, Single> previousFrame; //在背景建模时使用的前一帧图像
    private Image<TColor, Byte> currentFrame; //当前帧图像
    private double scale; //计算背景时所使用的缩放系数,大于平均值*scale倍数的像素认为是前景
    private Image<Gray, Byte> backgroundMask; //计算得到的背景图像
    private Image<TColor, Single> imageTemp; //临时图像
    private bool isStatDataReady; //是否已经准备好统计数据
    private Image<Gray, Single>[] imagesHi; //背景模型中各通道的最大值图像
    private Image<Gray, Single>[] imagesLow; //背景模型中各通道的最小值图像

    /// <summary>
    /// 构造函数
    /// </summary>
    public BackgroundStatModelAccAvg()
    {
    imageAccSum
    = null;
    imageAccDiff
    = null;
    frameCount
    = 0;
    previousFrame
    = null;
    currentFrame
    = null;
    scale
    = 6d;
    backgroundMask
    = null;
    isStatDataReady
    = false;
    imagesHi
    = null;
    imagesLow
    = null;
    }

    /// <summary>
    /// 设置或者获取计算前景时所用的阀值
    /// </summary>
    public double Scale
    {
    get
    {
    return scale;
    }
    set
    {
    scale
    = value > 0 ? value : 6d;
    }
    }

    /// <summary>
    /// 更新背景模型
    /// </summary>
    /// <param name="image"></param>
    public void Update(Image<TColor, Byte> image)
    {
    if (frameCount==0)
    {
    imageAccSum
    = new Image<TColor, Single>(image.Size);
    imageAccSum.SetValue(0d);
    imageAccDiff
    = new Image<TColor, float>(image.Size);
    imageAccDiff.SetValue(0d);
    }
    imageTemp
    = image.ConvertScale<Single>(1d, 0d); //将图像转换成浮点型
    imageAccSum.Acc(imageTemp);
    if (previousFrame != null)
    imageAccDiff.Acc(imageTemp.AbsDiff(previousFrame));
    previousFrame
    = imageTemp.Copy();
    frameCount
    ++;
    }

    /// <summary>
    /// 获取或者设置当前帧
    /// </summary>
    public Image<TColor, Byte> CurrentFrame
    {
    get
    {
    return currentFrame;
    }
    set
    {
    currentFrame
    = value;
    CalcBackgroundMask();
    }
    }

    /// <summary>
    /// 计算统计数据
    /// </summary>
    public void CalcStatData()
    {
    //计算出最高及最低阀值图像
    Image<TColor, Single> imageAvg = imageAccSum.ConvertScale<Single>(1d / frameCount, 0d);
    Image
    <TColor, Single> imageAvgDiff = imageAccDiff.ConvertScale<Single>(1d / frameCount, 1d); //将平均值加1,为了确保总是存在差异
    Image<TColor, Single> imageHi = imageAvg.Add(imageAvgDiff.ConvertScale<Single>(scale, 0d));
    Image
    <TColor, Single> imageLow = imageAvg.Sub(imageAvgDiff.ConvertScale<Single>(scale, 0d));
    imagesHi
    = imageHi.Split();
    imagesLow
    = imageLow.Split();
    isStatDataReady
    = true;
    //释放资源
    imageAvg.Dispose();
    imageAvgDiff.Dispose();
    imageHi.Dispose();
    imageLow.Dispose();
    }

    /// <summary>
    /// 计算背景
    /// </summary>
    private void CalcBackgroundMask()
    {
    if (imageAccSum == null || imageAccDiff == null || imageAccSum.Size != currentFrame.Size)
    throw new ArgumentException("在计算背景时发生参数错误。可能是:还没有建立背景模型;或者当前帧的尺寸与背景尺寸不一致。");
    if (!isStatDataReady)
    CalcStatData();
    imageTemp
    = currentFrame.ConvertScale<Single>(1d, 0d);
    Image
    <Gray, Single>[] images = imageTemp.Split();
    //计算背景图像
    if (backgroundMask == null)
    backgroundMask
    = new Image<Gray, byte>(currentFrame.Size);
    backgroundMask.SetZero();
    for (int i = 0; i < currentFrame.NumberOfChannels; i++)
    backgroundMask._Or(images[i].InRange(imagesLow[i], imagesHi[i]));
    //释放资源
    for (int i = 0; i < images.Length; i++)
    images[i].Dispose();
    }

    /// <summary>
    /// 获取背景
    /// </summary>
    public Image<Gray, Byte> BackgroundMask
    {
    get
    {
    return backgroundMask;
    }
    }

    /// <summary>
    /// 获取前景
    /// </summary>
    public Image<Gray, Byte> ForegroundMask
    {
    get
    {
    return backgroundMask.Not();
    }
    }

    /// <summary>
    /// 释放资源
    /// </summary>
    public void Dispose()
    {
    if (imageAccSum != null)
    imageAccSum.Dispose();
    if (imageAccDiff != null)
    imageAccDiff.Dispose();
    if (previousFrame != null)
    previousFrame.Dispose();
    if (currentFrame != null)
    currentFrame.Dispose();
    if (backgroundMask != null)
    backgroundMask.Dispose();
    if (isStatDataReady)
    {
    for (int i = 0; i < imagesHi.Length; i++)
    {
    imagesHi[i].Dispose();
    imagesLow[i].Dispose();
    }
    }
    }
    }

    /// <summary>
    /// 使用均值漂移来建立背景模型
    /// </summary>
    /// <typeparam name="TColor"></typeparam>
    public class BackgroundStatModelRunningAvg<TColor> : IBackgroundStatModel<TColor>
    where TColor : struct, IColor
    {
    //成员
    private Image<TColor, Single> imageAcc; //累计图像
    private Image<TColor, Single> imageAccDiff; //累计差值图像
    private int frameCount; //已经累计的背景帧数
    private Image<TColor, Single> previousFrame; //在背景建模时使用的前一帧图像
    private Image<TColor, Byte> currentFrame; //当前帧图像
    private double scale; //计算背景时所使用的缩放系数,大于平均值*scale倍数的像素认为是前景
    private double alpha; //计算均值漂移时使用的权值
    private Image<Gray, Byte> backgroundMask; //计算得到的背景图像
    private Image<TColor, Single> imageTemp; //临时图像
    private bool isStatDataReady; //是否已经准备好统计数据
    private Image<Gray, Single>[] imagesHi; //背景模型中各通道的最大值图像
    private Image<Gray, Single>[] imagesLow; //背景模型中各通道的最小值图像

    /// <summary>
    /// 构造函数
    /// </summary>
    public BackgroundStatModelRunningAvg()
    {
    imageAcc
    = null;
    imageAccDiff
    = null;
    frameCount
    = 0;
    previousFrame
    = null;
    currentFrame
    = null;
    scale
    = 6d;
    alpha
    = 0.5d;
    backgroundMask
    = null;
    isStatDataReady
    = false;
    imagesHi
    = null;
    imagesLow
    = null;
    }

    /// <summary>
    /// 设置或者获取计算前景时所用的阀值
    /// </summary>
    public double Scale
    {
    get
    {
    return scale;
    }
    set
    {
    scale
    = value > 0 ? value : 6d;
    }
    }

    /// <summary>
    /// 设置或者获取计算均值漂移是使用的权值
    /// </summary>
    public double Alpha
    {
    get
    {
    return alpha;
    }
    set
    {
    alpha
    = value > 0 && value < 1 ? value : 0.5d;
    }
    }

    /// <summary>
    /// 更新背景模型
    /// </summary>
    /// <param name="image"></param>
    public void Update(Image<TColor, Byte> image)
    {
    imageTemp
    = image.ConvertScale<Single>(1d, 0d); //将图像转换成浮点型
    if (imageAcc == null)
    {
    imageAcc
    = imageTemp.Copy();
    }
    else
    imageAcc.RunningAvg(imageTemp, alpha);
    if (previousFrame != null)
    {
    if (imageAccDiff == null)
    imageAccDiff
    = imageTemp.AbsDiff(previousFrame);
    else
    imageAccDiff.RunningAvg(imageTemp.AbsDiff(previousFrame), alpha);
    }
    previousFrame
    = imageTemp.Copy();
    frameCount
    ++;
    }

    /// <summary>
    /// 获取或者设置当前帧
    /// </summary>
    public Image<TColor, Byte> CurrentFrame
    {
    get
    {
    return currentFrame;
    }
    set
    {
    currentFrame
    = value;
    CalcBackgroundMask();
    }
    }

    /// <summary>
    /// 计算统计数据
    /// </summary>
    public void CalcStatData()
    {
    //计算出最高及最低阀值图像
    Image<TColor, Single> imageHi = imageAcc.Add(imageAccDiff.ConvertScale<Single>(scale, 0d));
    Image
    <TColor, Single> imageLow = imageAcc.Sub(imageAccDiff.ConvertScale<Single>(scale, 0d));
    imagesHi
    = imageHi.Split();
    imagesLow
    = imageLow.Split();
    isStatDataReady
    = true;
    //释放资源
    imageHi.Dispose();
    imageLow.Dispose();
    }

    /// <summary>
    /// 计算背景
    /// </summary>
    private void CalcBackgroundMask()
    {
    if (imageAcc == null || imageAccDiff == null || imageAcc.Size != currentFrame.Size)
    throw new ArgumentException("在计算背景时发生参数错误。可能是:还没有建立背景模型;或者当前帧的尺寸与背景尺寸不一致。");
    if (!isStatDataReady)
    CalcStatData();
    imageTemp
    = currentFrame.ConvertScale<Single>(1d, 0d);
    Image
    <Gray, Single>[] images = imageTemp.Split();
    //计算背景图像
    if (backgroundMask == null)
    backgroundMask
    = new Image<Gray, byte>(currentFrame.Size);
    backgroundMask.SetZero();
    for (int i = 0; i < currentFrame.NumberOfChannels; i++)
    backgroundMask._Or(images[i].InRange(imagesLow[i], imagesHi[i]));
    //释放资源
    for (int i = 0; i < images.Length; i++)
    images[i].Dispose();
    }

    /// <summary>
    /// 获取背景
    /// </summary>
    public Image<Gray, Byte> BackgroundMask
    {
    get
    {
    return backgroundMask;
    }
    }

    /// <summary>
    /// 获取前景
    /// </summary>
    public Image<Gray, Byte> ForegroundMask
    {
    get
    {
    return backgroundMask.Not();
    }
    }

    /// <summary>
    /// 释放资源
    /// </summary>
    public void Dispose()
    {
    if (imageAcc != null)
    imageAcc.Dispose();
    if (imageAccDiff != null)
    imageAccDiff.Dispose();
    if (previousFrame != null)
    previousFrame.Dispose();
    if (currentFrame != null)
    currentFrame.Dispose();
    if (backgroundMask != null)
    backgroundMask.Dispose();
    if (isStatDataReady)
    {
    for (int i = 0; i < imagesHi.Length; i++)
    {
    imagesHi[i].Dispose();
    imagesLow[i].Dispose();
    }
    }
    }
    }

    /// <summary>
    /// 使用标准方差来建立背景模型
    /// </summary>
    /// <typeparam name="TColor"></typeparam>
    public class BackgroundStatModelSquareAcc<TColor> : IBackgroundStatModel<TColor>
    where TColor : struct, IColor
    {
    //成员
    private Image<TColor, Single> imageAccSum; //累计图像
    private Image<TColor, Single> imageAccSquare; //累计平方图像
    private int frameCount; //已经累计的背景帧数
    private Image<TColor, Single> previousFrame; //在背景建模时使用的前一帧图像
    private Image<TColor, Byte> currentFrame; //当前帧图像
    private double scale; //计算背景时所使用的缩放系数,大于平均值*scale倍数的像素认为是前景
    private Image<Gray, Byte> backgroundMask; //计算得到的背景图像
    private Image<TColor, Single> imageTemp; //临时图像
    private bool isStatDataReady; //是否已经准备好统计数据
    private Image<Gray, Single>[] imagesHi; //背景模型中各通道的最大值图像
    private Image<Gray, Single>[] imagesLow; //背景模型中各通道的最小值图像

    /// <summary>
    /// 构造函数
    /// </summary>
    public BackgroundStatModelSquareAcc()
    {
    imageAccSum
    = null;
    imageAccSquare
    = null;
    frameCount
    = 0;
    previousFrame
    = null;
    currentFrame
    = null;
    scale
    = 6d;
    backgroundMask
    = null;
    isStatDataReady
    = false;
    imagesHi
    = null;
    imagesLow
    = null;
    }

    /// <summary>
    /// 设置或者获取计算前景时所用的阀值
    /// </summary>
    public double Scale
    {
    get
    {
    return scale;
    }
    set
    {
    scale
    = value > 0 ? value : 6d;
    }
    }

    /// <summary>
    /// 更新背景模型
    /// </summary>
    /// <param name="image"></param>
    public void Update(Image<TColor, Byte> image)
    {
    if (frameCount == 0)
    {
    imageAccSum
    = new Image<TColor, Single>(image.Size);
    imageAccSum.SetZero();
    imageAccSquare
    = new Image<TColor, float>(image.Size);
    imageAccSquare.SetZero();
    }
    imageTemp
    = image.ConvertScale<Single>(1d, 0d); //将图像转换成浮点型
    imageAccSum.Acc(imageTemp);
    CvInvoke.cvSquareAcc(imageTemp.Ptr, imageAccSquare.Ptr, IntPtr.Zero);
    previousFrame
    = imageTemp.Copy();
    frameCount
    ++;
    }

    /// <summary>
    /// 获取或者设置当前帧
    /// </summary>
    public Image<TColor, Byte> CurrentFrame
    {
    get
    {
    return currentFrame;
    }
    set
    {
    currentFrame
    = value;
    CalcBackgroundMask();
    }
    }

    /// <summary>
    /// 计算统计数据
    /// </summary>
    public void CalcStatData()
    {
    //计算出标准差、最高及最低阀值图像
    Image<TColor, Single> imageAvg = imageAccSum.ConvertScale<Single>(1d / frameCount, 0d);
    Image
    <TColor, Single> imageSd = imageAccSquare.ConvertScale<Single>(1d / frameCount, 0d);
    imageSd.Sub(imageAvg.Pow(2d));
    imageSd
    = imageSd.Pow(0.5d);
    Image
    <TColor, Single> imageHi = imageAvg.Add(imageSd.ConvertScale<Single>(scale, 0d));
    Image
    <TColor, Single> imageLow = imageAvg.Sub(imageSd.ConvertScale<Single>(scale, 0d));
    imagesHi
    = imageHi.Split();
    imagesLow
    = imageLow.Split();
    isStatDataReady
    = true;
    //释放资源
    imageAvg.Dispose();
    imageSd.Dispose();
    imageHi.Dispose();
    imageLow.Dispose();
    }

    /// <summary>
    /// 计算背景
    /// </summary>
    private void CalcBackgroundMask()
    {
    if (imageAccSum == null || imageAccSquare == null || imageAccSum.Size != currentFrame.Size)
    throw new ArgumentException("在计算背景时发生参数错误。可能是:还没有建立背景模型;或者当前帧的尺寸与背景尺寸不一致。");
    if (!isStatDataReady)
    CalcStatData();
    imageTemp
    = currentFrame.ConvertScale<Single>(1d, 0d);
    Image
    <Gray, Single>[] images = imageTemp.Split();
    //计算背景图像
    if (backgroundMask == null)
    backgroundMask
    = new Image<Gray, byte>(currentFrame.Size);
    backgroundMask.SetZero();
    for (int i = 0; i < currentFrame.NumberOfChannels; i++)
    backgroundMask._Or(images[i].InRange(imagesLow[i], imagesHi[i]));
    //释放资源
    for (int i = 0; i < images.Length; i++)
    images[i].Dispose();
    }

    /// <summary>
    /// 获取背景
    /// </summary>
    public Image<Gray, Byte> BackgroundMask
    {
    get
    {
    return backgroundMask;
    }
    }

    /// <summary>
    /// 获取前景
    /// </summary>
    public Image<Gray, Byte> ForegroundMask
    {
    get
    {
    return backgroundMask.Not();
    }
    }

    /// <summary>
    /// 释放资源
    /// </summary>
    public void Dispose()
    {
    if (imageAccSum != null)
    imageAccSum.Dispose();
    if (imageAccSquare != null)
    imageAccSquare.Dispose();
    if (previousFrame != null)
    previousFrame.Dispose();
    if (currentFrame != null)
    currentFrame.Dispose();
    if (backgroundMask != null)
    backgroundMask.Dispose();
    if (isStatDataReady)
    {
    for (int i = 0; i < imagesHi.Length; i++)
    {
    imagesHi[i].Dispose();
    imagesLow[i].Dispose();
    }
    }
    }
    }

    /// <summary>
    /// 使用标准协方差来建立背景模型
    /// </summary>
    /// <typeparam name="TColor"></typeparam>
    public class BackgroundStatModelMultiplyAcc<TColor> : IBackgroundStatModel<TColor>
    where TColor : struct, IColor
    {
    //成员
    private Image<TColor, Single> imageAccSum; //累计图像
    private Image<TColor, Single> imageAccMultiply; //累计平方图像
    private int frameCount; //已经累计的背景帧数
    private Image<TColor, Single> previousFrame; //在背景建模时使用的前一帧图像
    private Image<TColor, Byte> currentFrame; //当前帧图像
    private double scale; //计算背景时所使用的缩放系数,大于平均值*scale倍数的像素认为是前景
    private Image<Gray, Byte> backgroundMask; //计算得到的背景图像
    private Image<TColor, Single> imageTemp; //临时图像
    private bool isStatDataReady; //是否已经准备好统计数据
    private Image<Gray, Single>[] imagesHi; //背景模型中各通道的最大值图像
    private Image<Gray, Single>[] imagesLow; //背景模型中各通道的最小值图像

    /// <summary>
    /// 构造函数
    /// </summary>
    public BackgroundStatModelMultiplyAcc()
    {
    imageAccSum
    = null;
    imageAccMultiply
    = null;
    frameCount
    = 0;
    previousFrame
    = null;
    currentFrame
    = null;
    scale
    = 6d;
    backgroundMask
    = null;
    isStatDataReady
    = false;
    imagesHi
    = null;
    imagesLow
    = null;
    }

    /// <summary>
    /// 设置或者获取计算前景时所用的阀值
    /// </summary>
    public double Scale
    {
    get
    {
    return scale;
    }
    set
    {
    scale
    = value > 0 ? value : 6d;
    }
    }

    /// <summary>
    /// 更新背景模型
    /// </summary>
    /// <param name="image"></param>
    public void Update(Image<TColor, Byte> image)
    {
    if (frameCount == 0)
    {
    imageAccSum
    = new Image<TColor, Single>(image.Size);
    imageAccSum.SetZero();
    imageAccMultiply
    = new Image<TColor, float>(image.Size);
    imageAccMultiply.SetZero();
    }
    imageTemp
    = image.ConvertScale<Single>(1d, 0d); //将图像转换成浮点型
    imageAccSum.Acc(imageTemp);
    if (previousFrame != null)
    CvInvoke.cvMultiplyAcc(previousFrame.Ptr, imageTemp.Ptr, imageAccMultiply.Ptr, IntPtr.Zero);
    previousFrame
    = imageTemp.Copy();
    frameCount
    ++;
    }

    /// <summary>
    /// 获取或者设置当前帧
    /// </summary>
    public Image<TColor, Byte> CurrentFrame
    {
    get
    {
    return currentFrame;
    }
    set
    {
    currentFrame
    = value;
    CalcBackgroundMask();
    }
    }

    /// <summary>
    /// 计算统计数据
    /// </summary>
    public void CalcStatData()
    {
    //计算出标准协方差、最高及最低阀值图像
    Image<TColor, Single> imageAvg = imageAccSum.ConvertScale<Single>(1d / frameCount, 0d);
    Image
    <TColor, Single> imageScov = imageAccMultiply.ConvertScale<Single>(1d / frameCount, 0d);
    imageScov.Sub(imageAvg.Pow(2d));
    imageScov
    = imageScov.Pow(0.5d);
    Image
    <TColor, Single> imageHi = imageAvg.Add(imageScov.ConvertScale<Single>(scale, 0d));
    Image
    <TColor, Single> imageLow = imageAvg.Sub(imageScov.ConvertScale<Single>(scale, 0d));
    imagesHi
    = imageHi.Split();
    imagesLow
    = imageLow.Split();
    isStatDataReady
    = true;
    //释放资源
    imageAvg.Dispose();
    imageScov.Dispose();
    imageHi.Dispose();
    imageLow.Dispose();
    }

    /// <summary>
    /// 计算背景
    /// </summary>
    private void CalcBackgroundMask()
    {
    if (imageAccSum == null || imageAccMultiply == null || imageAccSum.Size != currentFrame.Size)
    throw new ArgumentException("在计算背景时发生参数错误。可能是:还没有建立背景模型;或者当前帧的尺寸与背景尺寸不一致。");
    if (!isStatDataReady)
    CalcStatData();
    imageTemp
    = currentFrame.ConvertScale<Single>(1d, 0d);
    Image
    <Gray, Single>[] images = imageTemp.Split();
    //计算背景图像
    if (backgroundMask == null)
    backgroundMask
    = new Image<Gray, byte>(currentFrame.Size);
    backgroundMask.SetZero();
    for (int i = 0; i < currentFrame.NumberOfChannels; i++)
    backgroundMask._Or(images[i].InRange(imagesLow[i], imagesHi[i]));
    //释放资源
    for (int i = 0; i < images.Length; i++)
    images[i].Dispose();
    }

    /// <summary>
    /// 获取背景
    /// </summary>
    public Image<Gray, Byte> BackgroundMask
    {
    get
    {
    return backgroundMask;
    }
    }

    /// <summary>
    /// 获取前景
    /// </summary>
    public Image<Gray, Byte> ForegroundMask
    {
    get
    {
    return backgroundMask.Not();
    }
    }

    /// <summary>
    /// 释放资源
    /// </summary>
    public void Dispose()
    {
    if (imageAccSum != null)
    imageAccSum.Dispose();
    if (imageAccMultiply != null)
    imageAccMultiply.Dispose();
    if (previousFrame != null)
    previousFrame.Dispose();
    if (currentFrame != null)
    currentFrame.Dispose();
    if (backgroundMask != null)
    backgroundMask.Dispose();
    if (isStatDataReady)
    {
    for (int i = 0; i < imagesHi.Length; i++)
    {
    imagesHi[i].Dispose();
    imagesLow[i].Dispose();
    }
    }
    }
    }

    /// <summary>
    /// 背景统计模型
    /// </summary>
    public class BackgroundStatModelBase<TColor> : IBackgroundStatModel<TColor>
    where TColor : struct, IColor
    {
    //成员变量
    IBackgroundStatModel<TColor> bgModel;
    BackgroundStatModelType type;

    /// <summary>
    /// 构造函数
    /// </summary>
    /// <param name="type">背景模型类型</param>
    public BackgroundStatModelBase(BackgroundStatModelType type)
    {
    this.type = type;
    switch (type)
    {
    case BackgroundStatModelType.FrameDiff:
    bgModel
    = new BackgroundStatModelFrameDiff<TColor>();
    break;
    case BackgroundStatModelType.AccAvg:
    bgModel
    = new BackgroundStatModelAccAvg<TColor>();
    break;
    case BackgroundStatModelType.RunningAvg:
    bgModel
    = new BackgroundStatModelRunningAvg<TColor>();
    break;
    case BackgroundStatModelType.SquareAcc:
    bgModel
    = new BackgroundStatModelSquareAcc<TColor>();
    break;
    case BackgroundStatModelType.MultiplyAcc:
    bgModel
    = new BackgroundStatModelMultiplyAcc<TColor>();
    break;
    default:
    throw new ArgumentException("不存在的背景模型", "type");
    }
    }

    /// <summary>
    /// 获取背景模型类型
    /// </summary>
    public BackgroundStatModelType BackgroundStatModelType
    {
    get
    {
    return type;
    }
    }

    /// <summary>
    /// 更新背景模型
    /// </summary>
    /// <param name="image"></param>
    public void Update(Image<TColor, Byte> image)
    {
    bgModel.Update(image);
    }

    /// <summary>
    /// 计算统计数据
    /// </summary>
    public void CalcStatData()
    {
    bgModel.CalcStatData();
    }

    /// <summary>
    /// 设置或者获取当前帧
    /// </summary>
    public Image<TColor, Byte> CurrentFrame
    {
    get
    {
    return bgModel.CurrentFrame;
    }
    set
    {
    bgModel.CurrentFrame
    = value;
    }
    }

    /// <summary>
    /// 获取背景
    /// </summary>
    public Image<Gray, Byte> BackgroundMask
    {
    get
    {
    return bgModel.BackgroundMask;
    }
    }

    /// <summary>
    /// 获取前景
    /// </summary>
    public Image<Gray, Byte> ForegroundMask
    {
    get
    {
    return bgModel.ForegroundMask;
    }
    }

    /// <summary>
    /// 释放资源
    /// </summary>
    public void Dispose()
    {
    if (bgModel != null)
    bgModel.Dispose();
    }
    }

    /// <summary>
    /// 背景模型类型
    /// </summary>
    public enum BackgroundStatModelType
    {
    FrameDiff,
    //帧差
    AccAvg, //平均背景
    RunningAvg, //均值漂移
    MultiplyAcc, //计算协方差
    SquareAcc //计算方差
    }
    }
    复制代码

     

    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给搞忘记了 -_-
    下面的代码演示了如果使用编码本背景模型:

    编码本模型
    复制代码
    //(1)初始化对象
    if (rbCodeBook.Checked)
    {
    if (bgCodeBookModel != null)
    {
    bgCodeBookModel.Dispose();
    bgCodeBookModel
    = null;
    }
    bgCodeBookModel
    = new BGCodeBookModel<Bgr>();
    }
    //(2)背景建模或者前景检测
    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)
    {
    //背景建模
    if (isBgModeling)
    {
    bgCodeBookModel.Update(image);
    //背景建模一段时间之后,清理陈旧的条目 (因为清理操作不会重置t,所以这里用求余数的办法来决定清理的时机)
    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;
    }
    }
    }
    //更新视频图像
    pbVideo.Image = image.Bitmap;
    }
    //(3)释放对象
    if (bgCodeBookModel != null)
    {
    try
    {
    bgCodeBookModel.Dispose();
    }
    catch { }
    }
    复制代码

     

    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在实现这个模型的时候做得不太好,因为它将背景建模和前景检测糅合到一起了,无论你是否愿意,在建模的过程中也会检测前景,而只希望前景检测的时候,同时也会建模。我比较喜欢将背景建模和前景检测进行分离的设计。
    调用的过程很简单,代码如下:

    高级背景统计模型
    复制代码
    //(1)创建对象
    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);
    }
    //背景建模及前景检测
    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));
    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;
    }
    }
    }
    //更新视频图像
    pbVideo.Image = image.Bitmap;
    }
    //(3)释放资源
    if (fgDetector != null)
    {
    try
    {
    fgDetector.Dispose();
    }
    catch { }
    }
    复制代码

     

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

    本文的代码

    本文代码
    复制代码
    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类的实现代码了,这里补上。多谢指正。

    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》比较了各种背景建模方式的差异,我希望能够尝试翻译出来。

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

    分类: 图像处理
  • 相关阅读:
    Yaf 在 Nginx 中的配置
    关于 GPG 用这个 就够 了
    PHP 服务器端处理跨域问题
    Linux 终端代理方式
    【转载】Oracle数据字典详解
    【转载】Oracle之内存结构(SGA、PGA)
    【转载】 使用rman进行坏块修复(ORA-01578、ORA-01110)
    【转载】使用Exp和Expdp导出数据的性能对比与优化
    【转载】oracle dbms_metadata.get_ddl的使用方法总结
    ORACLE执行详解
  • 原文地址:https://www.cnblogs.com/fx2008/p/2724587.html
Copyright © 2020-2023  润新知