背景建模与前景检测之三(Background Generation And Foreground Detection Phase 3)

http://www.cnblogs.com/xrwang/archive/2010/04/12/BackgroundGenerationAndForegroundDetectionPhase3.html

作者:王先荣

    在上一篇文章里,我尝试翻译了《Nonparametric Background Generation》,本文主要介绍以下内容:如何实现该论文的算法,如果利用该算法来进行背景建模及前景检测,最后谈谈我的一些体会。为了使描述更加简便,以下将该论文的算法及实现称为NBGModel。
1 使用示例
    NBGModel在使用上非常的简便,您可以仿照下面的代码来使用它:

复制代码
   
   
// 初始化NBGModel对象 NBGModel nbgModel = new NBGModel( 320 , 240 ); // 训练背景模型 nbgModel.TrainBackgroundModel(historyImages); // 前景检测 nbgModel.Update(currentFrame); // 利用结果 pbResult.Image = nbgModel.ForegroundMask.Bitmap; // 释放对象 nbgModel.Dispose();
复制代码

下面是更加完整的示例:

更加完整的示例

复制代码
   
   
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Drawing; using System.Drawing.Imaging; using System.Diagnostics; using System.Runtime.InteropServices; using Emgu.CV; using Emgu.CV.Structure; using Emgu.CV.CvEnum; namespace ImageProcessLearn { public class NBGModel : IDisposable { // 成员变量 private int width; // 图像的宽度 private int height; // 图像的高度 private NBGParameter param; // 非参数背景模型的参数 private List < Image < Ycc, Byte >> historyImages = null ; // 历史图像:列表个数为param.n,在更新时如果个数大于param.n,删除最早的历史图像,加入最新的历史图像 // 由于这里采用矩形窗口方式的MeanShift计算,因此不再需要分组图像的典型点。这跟论文不一样。 // private List<Image<Ycc,Byte>> convergenceImages = null; // 收敛图像:列表个数为param.m,仅在背景训练时使用,训练结束即被清空,因此这里不再声明 private Image < Gray, Byte > sampleImage = null ; // 样本图像:保存历史图像中每个像素在Y通道的值,用于MeanShift计算 private List < ClusterCenter < Ycc >> [,] clusterCenters = null ; // 聚集中心数据:将收敛点分类之后得到的聚集中心,数组大小为:height x width,列表元素个数不定q(q<=m)。 private Image < Ycc, Byte > mrbm = null ; // 最可靠背景模型 private Image < Gray, Byte > backgroundMask = null ; // 背景掩码图像 private double frameCount = 0 ; // 总帧数(不包括训练阶段的帧数n) // 属性 /// <summary> /// 图像的宽度 /// </summary> public int Width { get { return width; } } /// <summary> /// 图像的高度 /// </summary> public int Height { get { return height; } } /// <summary> /// 非参数背景模型的参数 /// </summary> public NBGParameter Param { get { return param; } } /// <summary> /// 最可靠背景模型 /// </summary> public Image < Ycc, Byte > Mrbm { get { return mrbm; } } /// <summary> /// 背景掩码 /// </summary> public Image < Gray, Byte > BackgroundMask { get { return backgroundMask; } } /// <summary> /// 前景掩码 /// </summary> public Image < Gray, Byte > ForegroundMask { get { return backgroundMask.Not(); } } /// <summary> /// 总帧数 /// </summary> public double FrameCount { get { return frameCount; } } /// <summary> /// 构造函数 /// </summary> /// <param name="width"> 图像宽度 </param> /// <param name="height"> 图像高度 </param> public NBGModel( int width, int height) : this (width, height, NBGParameter.GetDefaultNBGParameter()) { } /// <summary> /// 构造函数 /// </summary> /// <param name="width"> 图像宽度 </param> /// <param name="height"> 图像高度 </param> /// <param name="param"> 非参数背景模型的参数 </param> public NBGModel( int width, int height, NBGParameter param) { CheckParameters(width, height, param); this .width = width; this .height = height; this .param = param; historyImages = new List < Image < Ycc, byte >> (param.n); sampleImage = new Image < Gray, byte > (param.n, 1 ); clusterCenters = new List < ClusterCenter < Ycc >> [height, width]; for ( int row = 0 ; row < height; row ++ ) { for ( int col = 0 ; col < width; col ++ ) clusterCenters[row, col] = new List < ClusterCenter < Ycc >> (param.m); // 聚集中心列表不定长,因此将其初始化为m个元素的容量 } mrbm = new Image < Ycc, byte > (width, height); backgroundMask = new Image < Gray, byte > (width, height); frameCount = 0 ; } /// <summary> /// 检查参数是否正确,如果不正确,抛出异常 /// </summary> /// <param name="width"> 图像宽度 </param> /// <param name="height"> 图像高度 </param> /// <param name="param"> 非参数背景模型的参数 </param> private void CheckParameters( int width, int height, NBGParameter param) { if (width <= 0 ) throw new ArgumentOutOfRangeException( " width " , width, " 图像宽度必须大于零。 " ); if (height <= 0 ) throw new ArgumentOutOfRangeException( " height " , height, " 图像高度必须大于零。 " ); if (param.n <= 0 ) throw new ArgumentOutOfRangeException( " n " , param.n, " 样本数目必须大于零。 " ); if (param.m <= 0 ) throw new ArgumentOutOfRangeException( " m " , param.m, " 典型点数目必须大于零。 " ); if (param.n % param.m != 0 ) throw new ArgumentException( " 样本数目必须是典型点数目的整数倍。 " , " n,m " ); if (param.theta <= 0 || param.theta > 1 ) throw new ArgumentOutOfRangeException( " theta " , param.theta, " 权重系数必须大于零,并且小于1。 " ); if (param.t <= 0 ) throw new ArgumentOutOfRangeException( " t " , param.t, " 最小差值必须大于零。 " ); } /// <summary> /// 释放资源 /// </summary> public void Dispose() { if (historyImages != null && historyImages.Count > 0 ) { foreach (Image < Ycc, byte > historyImage in historyImages) { if (historyImage != null ) historyImage.Dispose(); } } if (sampleImage != null ) sampleImage.Dispose(); if (clusterCenters != null && clusterCenters.Length > 0 ) { foreach (List < ClusterCenter < Ycc >> clusterCentersElement in clusterCenters) clusterCentersElement.Clear(); } if (mrbm != null ) mrbm.Dispose(); if (backgroundMask != null ) backgroundMask.Dispose(); } /// <summary> /// 增加历史图像 /// </summary> /// <param name="historyImage"> 历史图像 </param> /// <returns> 是否增加成功 </returns> public bool AddHistoryImage(Image < Ycc, byte > historyImage) { bool success = false ; if (historyImage != null && historyImage.Width == width && historyImage.Height == height) { if (historyImages.Count >= param.n) { if (historyImages[ 0 ] != null ) historyImages[ 0 ].Dispose(); historyImages.RemoveAt( 0 ); } historyImages.Add(historyImage.Copy()); success = true ; } return success; } /// <summary> /// 增加历史图像 /// </summary> /// <param name="historyImages"> 历史图像数组 </param> /// <returns> 返回成功增加的历史图像数目 </returns> public int AddHistoryImage(Image < Ycc, byte > [] historyImages) { int added = 0 ; if (historyImages != null && historyImages.Length > 0 ) { foreach (Image < Ycc, byte > historyImage in historyImages) { if (AddHistoryImage(historyImage)) added ++ ; } } return added; } /// <summary> /// 增加历史图像 /// </summary> /// <param name="historyImages"> 历史图像列表 </param> /// <returns> 返回成功增加的历史图像数目 </returns> public int AddHistoryImage(List < Image < Ycc, byte >> historyImages) { return AddHistoryImage(historyImages.ToArray()); } /// <summary> /// 增加历史图像 /// </summary> /// <param name="historyImage"> 历史图像 </param> /// <returns> 是否增加成功 </returns> public bool AddHistoryImage(Image < Bgr, byte > historyImage) { Image < Ycc, byte > image = historyImage.Convert < Ycc, byte > (); bool success = AddHistoryImage(image); image.Dispose(); return success; } /// <summary> /// 增加历史图像 /// </summary> /// <param name="historyImages"> 历史图像数组 </param> /// <returns> 返回成功增加的历史图像数目 </returns> public int AddHistoryImage(Image < Bgr, byte > [] historyImages) { int added = 0 ; if (historyImages != null && historyImages.Length > 0 ) { foreach (Image < Bgr, byte > historyImage in historyImages) { if (AddHistoryImage(historyImage)) added ++ ; } } return added; } /// <summary> /// 增加历史图像 /// </summary> /// <param name="historyImages"> 历史图像列表 </param> /// <returns> 返回成功增加的历史图像数目 </returns> public int AddHistoryImage(List < Image < Bgr, byte >> historyImages) { return AddHistoryImage(historyImages.ToArray()); } /// <summary> /// 训练背景模型 /// </summary> /// <returns> 返回训练是否成功 </returns> unsafe public bool TrainBackgroundModel() { bool success = false ; if (historyImages.Count >= param.n) { // 0.初始化收敛图像,个数为param.m List < Image < Ycc, byte >> convergenceImages = new List < Image < Ycc, byte >> (param.m); for ( int i = 0 ; i < param.m; i ++ ) convergenceImages.Add( new Image < Ycc, byte > (width, height)); // 1.将历史图像分为m组,以每组的位置为矩形窗的起点,对通道Y在历史图像中进行MeanShift计算,结果窗的中点为收敛中心,该中心的值为收敛值,将收敛值加入到convergenceImageData中。 int numberPerGroup = param.n / param.m; // 每组图像的数目 MCvConnectedComp comp; // 保存Mean Shift计算结果的连接部件 int offsetHistoryImage; // 历史图像中某个像素相对图像数据起点的偏移量 int widthStepHistoryImage = historyImages[ 0 ].MIplImage.widthStep; // 历史图像的每行字节数 byte * [] ptrHistoryImages = new byte * [param.n]; // 历史图像的数据部分起点数组 byte * ptrSampleImage = ( byte * )sampleImage.MIplImage.imageData.ToPointer(); // 样本图像的数据部分起点 int offsetConvergenceImage; // 收敛图像中某个像素相对图像数据起点的偏移量 int widthStepConvergenceImage = convergenceImages[ 0 ].MIplImage.widthStep; // 收敛图像的每行字节数 byte * [] ptrConvergenceImages = new byte * [param.m]; // 收敛图像的数据部分起点数组 for ( int i = 0 ; i < param.n; i ++ ) ptrHistoryImages[i] = ( byte * )historyImages[i].MIplImage.imageData.ToPointer(); for ( int i = 0 ; i < param.m; i ++ ) ptrConvergenceImages[i] = ( byte * )convergenceImages[i].MIplImage.imageData.ToPointer(); // 遍历图像的每一行 for ( int row = 0 ; row < height; row ++ ) { // 遍历图像的每一列 for ( int col = 0 ; col < width; col ++ ) { offsetHistoryImage = row * widthStepHistoryImage + col * 3 ; offsetConvergenceImage = row * widthStepConvergenceImage + col * 3 ; // 用历史图像在该点的Y通道值组成一副用于Mean Shift计算的样本图像 for ( int sampleIdx = 0 ; sampleIdx < param.n; sampleIdx ++ ) * (ptrSampleImage + sampleIdx) = * (ptrHistoryImages[sampleIdx] + offsetHistoryImage); // 以每组的位置为矩形窗的起点,用MeanShift过程找到局部极值,由局部极值组成的图像是收敛图像 for ( int representativeIdx = 0 ; representativeIdx < param.m; representativeIdx ++ ) { Rectangle window = new Rectangle(representativeIdx * numberPerGroup, 0 , numberPerGroup, 1 ); CvInvoke.cvMeanShift(sampleImage.Ptr, window, param.criteria, out comp); int center = comp.rect.Left + comp.rect.Width / 2 ; // 收敛中心 * (ptrConvergenceImages[representativeIdx] + offsetConvergenceImage) = * (ptrHistoryImages[center] + offsetHistoryImage); * (ptrConvergenceImages[representativeIdx] + offsetConvergenceImage + 1 ) = * (ptrHistoryImages[center] + offsetHistoryImage + 1 ); * (ptrConvergenceImages[representativeIdx] + offsetConvergenceImage + 2 ) = * (ptrHistoryImages[center] + offsetHistoryImage + 2 ); } } } // 2.将近似的收敛点分类,以得到聚集中心 // (1)得到收敛中心的最小值Cmin;(2)将[0 , Cmin+t]区间中的收敛中心划为一类;(3)计算已分类点的平均值,作为聚集中心的值,并将聚集中心添加到clusterCenters;(4)删除已分类的收敛中心;(5)重复(0)~(4)直到收敛中心全部归类。 for ( int row = 0 ; row < height; row ++ ) { // 遍历图像的每一列 for ( int col = 0 ; col < width; col ++ ) { offsetConvergenceImage = row * widthStepConvergenceImage + col * 3 ; // 得到该像素的收敛中心列表 List < Ycc > convergenceCenters = new List < Ycc > (param.m); for ( int convergenceIdx = 0 ; convergenceIdx < param.m; convergenceIdx ++ ) convergenceCenters.Add( new Ycc( * (ptrConvergenceImages[convergenceIdx] + offsetConvergenceImage), * (ptrConvergenceImages[convergenceIdx] + offsetConvergenceImage + 1 ), * (ptrConvergenceImages[convergenceIdx] + offsetConvergenceImage + 2 ))); while (convergenceCenters.Count > 0 ) { Ycc Cmin = MinYcc(convergenceCenters); double regionHigh = Cmin.Y + param.t; Ycc sum = new Ycc(0d, 0d, 0d); double li = 0d; for ( int i = convergenceCenters.Count - 1 ; i >= 0 ; i -- ) { Ycc ci = convergenceCenters[i]; if (ci.Y <= regionHigh) { sum.Y += ci.Y; sum.Cr += ci.Cr; sum.Cb += ci.Cb; li ++ ; convergenceCenters.RemoveAt(i); } } Ycc avg = new Ycc(sum.Y / li, sum.Cr / li, sum.Cb / li); double wi = li / param.m; ClusterCenter < Ycc > clusterCenter = new ClusterCenter < Ycc > (avg, wi, li, 0d); clusterCenters[row, col].Add(clusterCenter); } } } // 3.得到最可靠背景模型 GetMrbm(); // 4.释放资源 for ( int i = 0 ; i < param.m; i ++ ) convergenceImages[i].Dispose(); convergenceImages.Clear(); success = true ; } return success; } /// <summary> /// 训练背景模型(没有使用指针运算进行优化,用于演示流程) /// </summary> /// <returns> 返回训练是否成功 </returns> private bool TrainBackgroundModel2() { bool success = false ; if (historyImages.Count >= param.n) { // 0.初始化收敛图像,个数为param.m List < Image < Ycc, byte >> convergenceImages = new List < Image < Ycc, byte >> (param.m); for ( int i = 0 ; i < param.m; i ++ ) convergenceImages.Add( new Image < Ycc, byte > (width, height)); // 1.将历史图像分为m组,以每组的位置为矩形窗的起点,对通道Y在历史图像中进行MeanShift计算,结果窗的中点为收敛中心,该中心的值为收敛值,将收敛值加入到convergenceImageData中。 int numberPerGroup = param.n / param.m; // 每组图像的数目 MCvConnectedComp comp; // 保存Mean Shift计算结果的连接部件 // 遍历图像的每一行 for ( int row = 0 ; row < height; row ++ ) { // 遍历图像的每一列 for ( int col = 0 ; col < width; col ++ ) { // 用历史图像在该点的Y通道值组成一副用于Mean Shift计算的样本图像 for ( int sampleIdx = 0 ; sampleIdx < param.n; sampleIdx ++ ) sampleImage[ 0 , sampleIdx] = new Gray(historyImages[sampleIdx][row, col].Y); // 这里可以用指针优化访问像素的速度 // 以每组的位置为矩形窗的起点,用MeanShift过程找到局部极值,由局部极值组成的图像是收敛图像 for ( int representativeIdx = 0 ; representativeIdx < param.m; representativeIdx ++ ) { Rectangle window = new Rectangle(representativeIdx * numberPerGroup, 0 , numberPerGroup, 1 ); CvInvoke.cvMeanShift(sampleImage.Ptr, window, param.criteria, out comp); int center = comp.rect.Left + comp.rect.Width / 2 ; // 收敛中心 Ycc ci = historyImages[center][row, col]; // 收敛中心对应的像素值 convergenceImages[representativeIdx][row, col] = ci; // 将收敛中心添加到收敛图像数据中去(这两句可以用指针优化访问像素的速度) } } } // 2.将近似的收敛点分类,以得到聚集中心 // (1)得到收敛中心的最小值Cmin;(2)将[0 , Cmin+t]区间中的收敛中心划为一类;(3)计算已分类点的平均值,作为聚集中心的值,并将聚集中心添加到clusterCenters;(4)删除已分类的收敛中心;(5)重复(0)~(4)直到收敛中心全部归类。 for ( int row = 0 ; row < height; row ++ ) { // 遍历图像的每一列 for ( int col = 0 ; col < width; col ++ ) { // 得到该像素的收敛中心列表 List < Ycc > convergenceCenters = new List < Ycc > (param.m); for ( int convergenceIdx = 0 ; convergenceIdx < param.m; convergenceIdx ++ ) convergenceCenters.Add(convergenceImages[convergenceIdx][row, col]); while (convergenceCenters.Count > 0 ) { Ycc Cmin = MinYcc(convergenceCenters); double regionHigh = Cmin.Y + param.t; Ycc sum = new Ycc(0d, 0d, 0d); double li = 0d; for ( int i = convergenceCenters.Count - 1 ; i >= 0 ; i -- ) { Ycc ci = convergenceCenters[i]; if (ci.Y <= regionHigh) { sum.Y += ci.Y; sum.Cr += ci.Cr; sum.Cb += ci.Cb; li ++ ; convergenceCenters.RemoveAt(i); } } Ycc avg = new Ycc(sum.Y / li, sum.Cr / li, sum.Cb / li); double wi = li / param.m; ClusterCenter < Ycc > clusterCenter = new ClusterCenter < Ycc > (avg, wi, li, 0d); clusterCenters[row, col].Add(clusterCenter); } } } // 3.得到最可靠背景模型 GetMrbm(); // 4.释放资源 for ( int i = 0 ; i < param.m; i ++ ) convergenceImages[i].Dispose(); convergenceImages.Clear(); success = true ; } return success; } /// <summary> /// 训练背景模型 /// </summary> /// <param name="historyImages"> 历史图像数组 </param> /// <returns> 返回训练是否成功 </returns> public bool TrainBackgroundModel(Image < Ycc, byte > [] historyImages) { AddHistoryImage(historyImages); return TrainBackgroundModel(); } /// <summary> /// 训练背景模型 /// </summary> /// <param name="historyImages"> 历史图像数组 </param> /// <returns> 返回训练是否成功 </returns> public bool TrainBackgroundModel(List < Image < Ycc, byte >> historyImages) { AddHistoryImage(historyImages); return TrainBackgroundModel(); } /// <summary> /// 训练背景模型 /// </summary> /// <param name="historyImages"> 历史图像数组 </param> /// <returns> 返回训练是否成功 </returns> public bool TrainBackgroundModel(Image < Bgr, byte > [] historyImages) { AddHistoryImage(historyImages); return TrainBackgroundModel(); } /// <summary> /// 训练背景模型 /// </summary> /// <param name="historyImages"> 历史图像数组 </param> /// <returns> 返回训练是否成功 </returns> public bool TrainBackgroundModel(List < Image < Bgr, byte >> historyImages) { AddHistoryImage(historyImages); return TrainBackgroundModel(); } /// <summary> /// 得到Ycc列表中的最小值(仅比较Y分量) /// </summary> /// <param name="yccList"></param> /// <returns></returns> private Ycc MinYcc(List < Ycc > yccList) { Ycc min = yccList[ 0 ]; foreach (Ycc ycc in yccList) { if (ycc.Y < min.Y) min = ycc; } return min; } /// <summary> /// 得到聚集中心列表中的最大值(比较wi) /// </summary> /// <param name="clusterCenters"></param> /// <returns></returns> private ClusterCenter < Ycc > MaxClusterCenter(List < ClusterCenter < Ycc >> clusterCenters) { ClusterCenter < Ycc > max = clusterCenters[ 0 ]; foreach (ClusterCenter < Ycc > center in clusterCenters) { if (center.wi > max.wi) max = center; } return max; } /// <summary> /// 得到最可靠背景模型MRBM:在聚集中心选择wi最大的值(没有使用指针运算进行优化,用于演示流程) /// </summary> private void GetMrbm2() { // 遍历图像的每一行 for ( int row = 0 ; row < height; row ++ ) { // 遍历图像的每一列 for ( int col = 0 ; col < width; col ++ ) mrbm[row, col] = MaxClusterCenter(clusterCenters[row, col]).ci; } } /// <summary> /// 得到最可靠背景模型MRBM:在聚集中心选择wi最大的值 /// </summary> unsafe private void GetMrbm() { int widthStepMrbm = mrbm.MIplImage.widthStep; byte * ptrMrbm = ( byte * )mrbm.MIplImage.imageData.ToPointer(); byte * ptrPixel; Ycc ci; // 遍历图像的每一行 for ( int row = 0 ; row < height; row ++ ) { // 遍历图像的每一列 for ( int col = 0 ; col < width; col ++ ) { ci = MaxClusterCenter(clusterCenters[row, col]).ci; ptrPixel = ptrMrbm + row * widthStepMrbm + col * 3 ; * ptrPixel = ( byte )ci.Y; * (ptrPixel + 1 ) = ( byte )ci.Cr; * (ptrPixel + 2 ) = ( byte )ci.Cb; } } } /// <summary> /// 更新背景模型,同时计算相应的前景和背景(没有使用指针运算进行优化,用于演示流程) /// </summary> /// <param name="currentFrame"> 当前帧图像 </param> private void Update2(Image < Ycc, byte > currentFrame) { // 1.将当前帧加入到历史图像的末尾 AddHistoryImage(currentFrame); frameCount ++ ; // 2.将背景掩码图像整个设置为白色,在检测时再将前景像素置零 backgroundMask.SetValue(255d); // 3.遍历图像的每个像素,确定前景或背景;同时进行背景维持操作。 int numberPerGroup = param.n / param.m; int lastIdx = param.n - 1 ; // 遍历图像的每一行 for ( int row = 0 ; row < height; row ++ ) { // 遍历图像的每一列 for ( int col = 0 ; col < width; col ++ ) { int nearestIndex; // 最近的聚集中心索引 double d = GetMinD(historyImages[lastIdx][row, col].Y, clusterCenters[row, col], out nearestIndex); // 得到最小差值d if (d > param.t) { // 该点为前景:以该点附近的矩形窗{n-numberPerGroup,0,numberPerGroup,1}开始进行MeanShift运算,并得到新的收敛中心Cnew(wi=1/m),将Cnew加入到聚集中心clusterCenters // 用历史图像在该点的Y通道值组成一副用于Mean Shift计算的样本图像 for ( int sampleIdx = 0 ; sampleIdx < param.n; sampleIdx ++ ) sampleImage[ 0 , sampleIdx] = new Gray(historyImages[sampleIdx][row, col].Y); // 这里可以用指针运算来提高速度 Rectangle window = new Rectangle(param.n - numberPerGroup, 0 , numberPerGroup, 1 ); MCvConnectedComp comp; CvInvoke.cvMeanShift(sampleImage.Ptr, window, param.criteria, out comp); int center = comp.rect.Left + comp.rect.Width / 2 ; // 收敛中心 ClusterCenter < Ycc > cnew = new ClusterCenter < Ycc > (historyImages[center][row, col], 1d / (param.m + frameCount), 1d, frameCount); // 将收敛中心作为新的聚集中心 clusterCenters[row, col].Add(cnew); // 设置背景掩码图像的前景点 backgroundMask[row, col] = new Gray(0d); } else { // 该点为背景:更新最近聚集中心的ci为(li+1)/m clusterCenters[row, col][nearestIndex].li ++ ; clusterCenters[row, col][nearestIndex].wi = clusterCenters[row, col][nearestIndex].li / (param.m + frameCount); clusterCenters[row, col][nearestIndex].updateFrameNo = frameCount; } } } // 4.生成最可靠背景模型 GetMrbm(); } /// <summary> /// 更新背景模型,同时计算相应的前景和背景 /// </summary> /// <param name="currentFrame"> 当前帧图像 </param> private void Update2(Image < Bgr, byte > currentFrame) { Image < Ycc, byte > image = currentFrame.Convert < Ycc, byte > (); Update(image); image.Dispose(); } /// <summary> /// 更新背景模型,同时计算相应的前景和背景 /// </summary> /// <param name="currentFrame"> 当前帧图像 </param> unsafe public void Update(Image < Ycc, byte > currentFrame) { // 1.将当前帧加入到历史图像的末尾 AddHistoryImage(currentFrame); frameCount ++ ; // 2.将背景掩码图像整个设置为白色,在检测时再将前景像素置零 backgroundMask.SetValue(255d); // 3.遍历图像的每个像素,确定前景或背景;同时进行背景维持操作。 int numberPerGroup = param.n / param.m; int lastIdx = param.n - 1 ; int offsetHistoryImage; // 历史图像中某个像素相对图像数据起点的偏移量 int widthStepHistoryImage = historyImages[ 0 ].MIplImage.widthStep; // 历史图像的每行字节数 byte * [] ptrHistoryImages = new byte * [param.n]; // 历史图像的数据部分起点数组 byte * ptrSampleImage = ( byte * )sampleImage.MIplImage.imageData.ToPointer(); // 样本图像的数据部分起点 int widthStepBackgroundMask = backgroundMask.MIplImage.widthStep; // 背景掩码图像的每行字节数 byte * ptrBackgroundMask = ( byte * )backgroundMask.MIplImage.imageData.ToPointer(); // 背景掩码图像的数据部分起点 byte f = 0 ; // 前景对应的颜色值 for ( int i = 0 ; i < param.n; i ++ ) ptrHistoryImages[i] = ( byte * )historyImages[i].MIplImage.imageData.ToPointer(); // 遍历图像的每一行 for ( int row = 0 ; row < height; row ++ ) { // 遍历图像的每一列 for ( int col = 0 ; col < width; col ++ ) { offsetHistoryImage = row * widthStepHistoryImage + col * 3 ; int nearestIndex; // 最近的聚集中心索引 double d = GetMinD(( double )( * (ptrHistoryImages[lastIdx] + offsetHistoryImage)), clusterCenters[row, col], out nearestIndex); // 得到最小差值d if (d > param.t) { // 该点为前景:以该点附近的矩形窗{n-numberPerGroup,0,numberPerGroup,1}开始进行MeanShift运算,并得到新的收敛中心Cnew(wi=1/m),将Cnew加入到聚集中心clusterCenters // 用历史图像在该点的Y通道值组成一副用于Mean Shift计算的样本图像 for ( int sampleIdx = 0 ; sampleIdx < param.n; sampleIdx ++ ) * (ptrSampleImage + sampleIdx) = * (ptrHistoryImages[sampleIdx] + offsetHistoryImage); Rectangle window = new Rectangle(param.n - numberPerGroup, 0 , numberPerGroup, 1 ); MCvConnectedComp comp; CvInvoke.cvMeanShift(sampleImage.Ptr, window, param.criteria, out comp); int center = comp.rect.Left + comp.rect.Width / 2 ; // 收敛中心 ClusterCenter < Ycc > cnew = new ClusterCenter < Ycc > (historyImages[center][row, col], 1d / (param.m + frameCount), 1d, frameCount); // 将收敛中心作为新的聚集中心 clusterCenters[row, col].Add(cnew); // 设置背景掩码图像的前景点 * (ptrBackgroundMask + row * widthStepBackgroundMask + col) = f; } else { // 该点为背景:更新最近聚集中心的ci为(li+1)/m clusterCenters[row, col][nearestIndex].li ++ ; clusterCenters[row, col][nearestIndex].wi = clusterCenters[row, col][nearestIndex].li / (param.m + frameCount); clusterCenters[row, col][nearestIndex].updateFrameNo = frameCount; } } } // 4.生成最可靠背景模型 GetMrbm(); } public void Update(Image < Bgr, byte > currentFrame) { Image < Ycc, byte > image = currentFrame.Convert < Ycc, byte > (); Update(image); image.Dispose(); } /// <summary> /// 用wi>=theta作为条件选择可能的背景模型Cb;对每个观测值x0,计算x0与Cb的最小差值d /// </summary> /// <param name="x0"> 观测值x0 </param> /// <param name="centerList"> 某像素对应的聚集中心列表 </param> /// <param name="nearestIndex"> 输出参数:最近的聚集中心索引 </param> /// <returns> 返回最小差值d </returns> private double GetMinD( double x0, List < ClusterCenter < Ycc >> centerList, out int nearestIndex) { double d = double .MaxValue; nearestIndex = 0 ; for ( int idx = 0 ; idx < centerList.Count; idx ++ ) { ClusterCenter < Ycc > center = centerList[idx]; if (center.wi >= param.theta) { double d0 = Math.Abs(center.ci.Y - x0); if (d0 < d) { d = d0; nearestIndex = idx; } } } return d; } /// <summary> /// 清除不活跃的聚集中心 /// </summary> /// <param name="staleThresh"> 不活跃阀值,不活跃帧数大于该值的聚集中心将被清除 </param> public void ClearStale( int staleThresh) { // 遍历每个像素的聚集中心 for ( int row = 0 ; row < height; row ++ ) { for ( int col = 0 ; col < width; col ++ ) { for ( int idx = clusterCenters[row, col].Count - 1 ; idx >= 0 ; idx -- ) { if (frameCount - clusterCenters[row, col][idx].updateFrameNo > staleThresh) clusterCenters[row, col].RemoveAt(idx); } } } } } /// <summary> /// 聚集中心 /// </summary> /// <typeparam name="TColor"> 聚集中心使用的色彩空间 </typeparam> public class ClusterCenter < TColor > where TColor : struct , IColor { public TColor ci; // 聚集中心的像素值 public double wi; // 聚集中心的权重 public double li; // 聚集中心包含的收敛点数目 public double updateFrameNo; // 更新该聚集中心时的帧数:用于消除消极的聚集中心 public ClusterCenter(TColor ci, double wi, double li, double updateFrameNo) { this .ci = ci; this .li = li; this .wi = wi; this .updateFrameNo = updateFrameNo; } } /// <summary> /// 非参数背景模型的参数 /// </summary> public struct NBGParameter { public int n; // 样本数目:需要被保留的历史图像数目 public int m; // 典型点数目:历史图像需要被分为多少组 public double theta; // 权重系数:权重大于该值的聚集中心为候选背景 public double t; // 最小差值:观测值与候选背景的最小差值大于该值时,为前景;否则为背景 public MCvTermCriteria criteria; // Mean Shift计算的终止条件:包括最大迭代次数和终止计算的精度 public NBGParameter( int n, int m, double theta, double t, MCvTermCriteria criteria) { this .n = n; this .m = m; this .theta = theta; this .t = t; this .criteria.type = criteria.type; this .criteria.max_iter = criteria.max_iter; this .criteria.epsilon = criteria.epsilon; } public static NBGParameter GetDefaultNBGParameter() { return new NBGParameter( 100 , 10 , 0.3d , 10d, new MCvTermCriteria( 100 , 1d)); } } }
 

3 NBGModel类介绍
    3.1 属性

Width——获取图像的宽度
Height——获取图像的高度
Param——获取参数设置
Mrbm——获取最可靠背景模型图像
BackgroundMask——获取背景掩码图像
ForegroundMask——获取前景掩码图像
FrameCount——获取已被检测的帧数

    3.2 构造函数
public NBGModel(int width, int height)——用默认的参数初始化NBGModel,等价于NBGModel(width, height, NBGParameter.GetDefaultNBGParameter())
public NBGModel(int width, int height, NBGParameter param)——用指定的参数初始化NBGModel

    3.3 方法
AddHistoryImage——添加一幅或者一组历史图像
TrainBackgroundModel——训练背景模型;如果传入了历史图像,则先添加历史图像,然后再训练背景模型
Update——更新背景模型,同时检测前景
ClearStale——清除消极的聚集中心
Dispose——释放资源

4 体会
    NBGModel的确非常有效,非常简洁,特别适用于伴随复杂运动对象的背景建模。我特意选取了PETS2009中的素材对其做了一些测试,结果也证明了 NBGModel的优越性。不过需要指出的是,它需要占用大量的内存(主要因为需要保存n幅历史图像);它的计算量比较大。
在使用的过程中,它始终需要在内存中缓存n幅历史图像,1幅最可靠背景模型图像,1幅背景掩码图像,近似m幅图像(聚集中心);而在训练阶段,更需要临时存储m幅收敛图像。
例如:样本数目为100,典型点数目为10,图像尺寸为768x576时,所用的内存接近300M,训练背景需要大约需要33秒,而对每幅图像进行前景检测大约需要600ms。虽然可以使用并行编程来提高性能,但是并不能从根本上解决问题。
(注:测试电脑的CPU为AMD闪龙3200+,内存1.5G。)
    看来,有必要研究一种新的方法,目标是检测效果更好,内存占用低,处理更快速。目前的想法是使用《Wallflower: Principles and Practice of Background Manitenance》中的3层架构(时间轴上的像素级处理,像素间的区域处理,帧间处理),但是对每层架构都选用目前流行的处理方式,并对处理方式进行优化。时间轴上的像素级处理打算使用CodeBook方法,但是增加本文的一些思想。像素间的区域处理打算参考《基于区域相关的核函数背景建模算法》中的方法。帧间处理预计会采用全局灰度统计值作为依据。

最后,按照惯例:感谢您耐心看完本文,希望对您有所帮助。
本文所述方法及代码仅用于学习研究,不得用于商业目的。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值