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));
}
}
}