65.9K
CodeProject 正在变化。 阅读更多。
Home

EMGU 卡尔曼滤波器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.67/5 (10投票s)

2012 年 3 月 7 日

CPOL

10分钟阅读

viewsIcon

71366

使用 EMGU 图像处理库进行信号处理的中间示例

源代码 [SourceForge]

鼠标实时数据示例

计算数据示例

 

引言

以下教程实现了一个简单的卡尔曼滤波器。代码最初来源于 Roy 在 morethantechnical.com 上的一篇文章。Usman Ashraf 和 Kevin Chow 非常好心地将其翻译成了 C# EMGU 版本。

本文是关于 EMGU 图像处理包装器用法的后续文章。有关 EMGU 包装器的更多信息,请访问 EMGU 网站。如果您是这个包装器的新手,请参阅 创建您的第一个 EMGU 图像处理项目文章。您将从 3 个未找到引用的警告开始。展开解决方案资源管理器中的“引用”文件夹,删除带有黄色警告图标的 3 个引用,然后添加新的引用。

卡尔曼滤波器是一种递归算法,它对带噪声的输入数据流进行操作,以生成对底层系统状态的统计最优估计(原始论文)。该滤波器以 Rudolf (Rudy) E. Kálmán 的名字命名,他是其理论的主要开发者之一。更多信息可在 Wikipedia 上找到。卡尔曼滤波器是为了解决维纳滤波问题而推导出来的。维纳滤波问题的目标是通过与期望的无噪声信号的估计进行比较来减少信号中的噪声量。维纳工作的离散时间等效项由 Kolmogorov 独立推导并于 1941 年发表。因此,该理论通常被称为 维纳-柯尔莫哥洛夫滤波理论。

在本例中,信号过程是鼠标的移动。虽然这是一个简单的应用,但该算法可以有许多应用,包括图像平滑、边缘跟踪和光流等。本教程旨在对滤波器设计进行相当基础的介绍。左侧屏幕截图是基本的鼠标跟踪示例。右侧屏幕截图是数据处理示例。

 

假设知识

以下教程属于中级水平。假定您可以自行设置项目并相应地引用相关文件。如果您遇到错误或刚开始学习,可以从这里开始: 创建您的第一个 EMGU 图像处理项目文章

 

卡尔曼滤波器基础

此解释摘录自这个 视频。它以一种简单的方式解释了卡尔曼滤波器,以下部分将其转录为本特定应用。

以我们的鼠标指针为例。它有一个已知的当前位置 denoted by Χτ-1,并且它的位置将发生未知因子 µ 的变化。鼠标将移动到下一个状态 ̅Χτ,但我们会加入某种形式的跟踪信息。在这种情况下,我们假设鼠标认为它所在的位置实际上是不正确的。事实上,计算机认为它所在的位置是点 ΧComp。可以安全地假设鼠标位于 ̅ΧτΧComp 之间的某个矢量点。这就是我们称之为估计位置的点,也是我们感兴趣的点 ΧEST

这就是卡尔曼滤波器以一种非常有效的方式为我们计算出的。这其中有几个状态,第一个是状态预测。我们正在尝试计算鼠标的下一个状态 ̅Χτ,现在我们知道下一个状态将是前一个状态 (Χτ-1) 和移动 (µ) 的函数。卡尔曼滤波器的第一个假设是这种变化是线性的,生成下面的方程。

 

̅Χτ = ΑΧτ-1 + Βµ

我们还必须考虑状态误差估计,假设这个误差服从高斯分布 (ετ)。这些假设并非对所有系统都适用,但同样,它们对许多系统是适用的。因此,生成的方程为:

 

̅Χτ = ΑΧτ-1 + Βµ + ετ

第二个状态是传感器预测。既然我们对鼠标认为它将要移动到的位置有所预测,那么我们应该对计算机将要认为鼠标位置会是多少有所预测。这将是 ̅Ζτ。这将是我们状态预测的某种函数,并且同样会存在某种误差,再次假设服从高斯分布。

 

̅Ζτ = C̅Χτ +ετ

所以卡尔曼滤波器的整个思想归结为需要计算的估计状态 (ΧEST) 是预测状态 (̅Χτ) 的线性函数。此外,还有计算机声称鼠标指针所在位置的实际测量值 (ΧComp) 与计算机将要跟踪的鼠标位置的预测测量值 (̅Ζτ) 之间的差值,乘以一个称为卡尔曼增益 (κ) 的增益因子。这就得到了我们最后的方程,如下所示:

 

ΧEST = ̅Χτ + κ( ΧComp - ̅Ζτ)

那么这一切意味着什么呢?嗯,如果计算机预测的鼠标位置是准确的,那么 ΧComp - ̅Ζτ = 0,那么估计位置的预测就是理想的。但如果计算机预测的鼠标位置是错误的,可能发生了一个错误(例如,噪声)。那么估计位置的预测就会根据卡尔曼增益的一个因子进行校正,从而获得更准确的估计。因此,卡尔曼增益被认为是一个校正项。

 

代码

设置卡尔曼滤波器

在提供的源代码中,卡尔曼滤波器在调用 Form1 InitializeComponent() 方法后进行初始化。我们感兴趣的方法是 KalmanFilter()

public void KalmanFilter()
{
      mousePoints = new List<pointf>();
      kalmanPoints = new List<pointf>();
      kal = new Kalman(4, 2, 0);
      syntheticData = new SyntheticData();
      Matrix state = new Matrix<float>(new float[]
      {
            0.0f, 0.0f, 0.0f, 0.0f
      });
      kal.CorrectedState = state;
      kal.TransitionMatrix = syntheticData.transitionMatrix;
      kal.MeasurementNoiseCovariance = syntheticData.measurementNoise;
      kal.ProcessNoiseCovariance = syntheticData.processNoise;
      kal.ErrorCovariancePost = syntheticData.errorCovariancePost;
      kal.MeasurementMatrix = syntheticData.measurementMatrix;
}

上述方法只是为卡尔曼滤波器提供了它将要跟踪的变量数量的详细信息,而“kal = new Kalman(4, 2, 0);”虽然很重要,但它只是说明跟踪 4 个动态变量和 2 个测量变量。“syntheticData”变量是一系列用于计算数据中的噪声并从中滤除噪声的矩阵。SyntheticData 变量保存在 SyntheticData.cs 类中。正是在这个类中,卡尔曼滤波器使用 4 个动态参数(由“state”表示)和 2 个测量参数进行设置。所有合成数据的矩阵都传递给卡尔曼滤波器,以便它可以参考它们来预测下一个状态。 

要完全理解实现并调整卡尔曼滤波器,必须仔细研究 SyntheticData.cs。首先要检查的是 SyntheticData 的初始化,

public SyntheticData()
{
      state = new Matrix<float>(4, 1);
      state[0, 0] = 0f; // x-pos
      state[1, 0] = 0f; // y-pos
      state[2, 0] = 0f; // x-velocity
      state[3, 0] = 0f; // y-velocity
      transitionMatrix = new Matrix<float>(new float[,]
      {
            {1, 0, 1, 0},
            {0, 1, 0, 1},
            {0, 0, 1, 0},
            {0, 0, 0, 1}
      });
      measurementMatrix = new Matrix<float>(new float[,]
      {
            { 1, 0, 0, 0 },
            { 0, 1, 0, 0 }
      });
      measurementMatrix.SetIdentity();
      processNoise = new Matrix<float>(4, 4);
      processNoise.SetIdentity(new MCvScalar(1.0e-4));
      measurementNoise = new Matrix<float>(2, 2);
      measurementNoise.SetIdentity(new MCvScalar(1.0e-1));
      errorCovariancePost = new Matrix<float>(4, 4);
      errorCovariancePost.SetIdentity();
}

如您所见,还分配了一个状态变量,给出了要跟踪的四个维度。鼠标的 X 位置、鼠标的 Y 位置、X 轴上的速度变化以及 Y 轴上的速度变化。这四个变量通常足以满足大多数情况,但是跟踪的变量越多,抑制噪声的可能性就越大。

转换矩阵为卡尔曼滤波器提供了所有变量之间预期的变化组合。由于我们跟踪四个变量,所以它是一个 4x4 的矩阵。矩阵的每一行从左到右分别对应 X 位置、Y 位置、X 速度、Y 速度。在提供的设计中,位置和速度的变化期望是增加或减少 1。通过将转换矩阵替换为下面的矩阵,我们可以提高卡尔曼滤波器的噪声抑制能力,您会看到更高的抗噪声能力。

transitionMatrix = new Matrix<float>(new float[,]
{
      {0.1F, 0,    1, 0},  // x-pos, y-pos, x-velocity, y-velocity
      {0,    0.1F, 0, 1},
      {0,    0,    1, 0},
      {0,    0,    0, 1}
}); 

同样,您会开始注意到在跟踪鼠标时对速度变化的响应较低。进一步改变 X 和 Y 速度会加剧这种情况。使用更高的值会产生一个高度不稳定的系统,该系统无法预测正确的值或抑制噪声。

构造函数中设置的变量主要处理设计,只有在添加额外变量进行跟踪时才需要调整。一个非常有用的变量是噪声的单位值。

      processNoise.SetIdentity(new MCvScalar(1.0e-4));

调整这些值也会影响卡尔曼滤波器抑制噪声的能力。提供的较小值“processNoise.SetIdentity”表示卡尔曼滤波器随时间的推移对噪声的抵抗力越强。

将滤波器应用于数据

在此应用程序中,鼠标位置的 X 和 Y 数据通过调用“filterPoints”方法应用于卡尔曼滤波器。这从“KalmanFilterRunner”方法调用,该方法是其中一个计时器的滴答事件。“KalmanFilterRunner”方法负责计算卡尔曼滤波器输出并显示结果。“filterpoints”方法允许更快速地将应用程序移植到其他应用程序。主方法本质上很简单:

public PointF[] filterPoints(PointF pt)
{
      syntheticData.state[0, 0] = pt.X;
      syntheticData.state[1, 0] = pt.Y;
      Matrix<float> prediction = kal.Predict();
      PointF predictPoint = new PointF(prediction[0, 0], prediction[1, 0]);
      PointF measurePoint = new PointF(syntheticData.GetMeasurement()[0, 0],
      syntheticData.GetMeasurement()[1, 0]);
      Matrix<float> estimated = kal.Correct(syntheticData.GetMeasurement());
      PointF estimatedPoint = new PointF(estimated[0, 0], estimated[1, 0]);
      syntheticData.GoToNextState();
      PointF[] results = new PointF[2];
      results[0] = predictPoint;
      results[1] = estimatedPoint;
      px = predictPoint.X;
      py = predictPoint.Y;
      cx = estimatedPoint.X;
      cy = estimatedPoint.Y;
      return results;
}

syntheticData 的当前状态会更新为鼠标指针的记录位置。然后调用卡尔曼滤波器来预测鼠标预期处于的下一个状态并生成卡尔曼增益。根据滤波器的设置,合成数据矩阵已被引用。

然后,“predictedPoint”是计算机将要认为的鼠标位置的预测。“mesurePoint”用于生成一个关于鼠标认为它将要移动到的位置的预测。此时会加入少量噪声,因为我们期望鼠标的实际位置和预测位置并不相同。如果这是真的,使用滤波器就没有意义了,因为没有噪声需要消除。最后,卡尔曼滤波器用于校正点并生成准确的 ?EST 位置。

“syntheticData.GoToNextState()”用于为卡尔曼滤波器生成一个带有噪声的下一个预测状态,以便其工作并发展其噪声抑制增益和校正。这就是为什么卡尔曼滤波器在第一次测量后会得到改进。每一次迭代,卡尔曼滤波器都会对它正在处理的噪声有更多的了解。

获取鼠标坐标并设置数据

鼠标坐标是通过 Picturebox 对象可用的 MouseMove 方法获得的。只要鼠标坐标可用于方法调用,就可以使用其他对象。每次鼠标在 Picturebox 上移动时,全局变量“ax”和“ay”都会更新。这些是记录的实际 x 坐标 (ax) 和记录的实际 y 坐标 (ay)。在卡尔曼滤波器中,这相当于计算机认为鼠标将要移动到的位置。

变量“ax”和“ay”是全局变量,并且仅在调用计时器方法时才被馈送到卡尔曼滤波器。计时器对于在使用实时数据时查看不同的滤波器效果至关重要。

使用计时器

在此示例中,使用计时器来间隔记录鼠标位置和预测卡尔曼点。在分析预录数据时不需要它们,但为了评估不同的滤波器设计,它们是必不可少的。计时器设置已放入一个独立的方法中。

private void InitialiseTimers(int Timer_Interval = 1000)

默认时间为 1 秒,但可以通过修改调用计时器设置方法的 Start_BTN_Click 方法进行调整,下面是一个例子,将计时器减至每 100 微秒。

private void Start_BTN_Click(object sender, EventArgs e)
{
      if (Start_BTN.Text == "Start")
      {
            MouseTrackingArea.Refresh();
            InitialiseTimers(100);
            Start_BTN.Text  = "Stop";
      }
      else
      {
            StopTimers();
            Start_BTN.Text = "Start";
      }
}

鼠标的记录位置也仅通过计时器记录。如前所述,只要鼠标移动到 Paintbox 控件上,所有鼠标位置都存储在“ax”和“ay”全局变量中,但只有在调用“MousePositionRecord”计时器的滴答方法时才会被记录。这并非必需,它只是允许比较输入到卡尔曼滤波器中的位置和卡尔曼滤波器生成的预测。

显示结果

结果通过直接绘制到 Paintbox 控件上来显示。这意味着如果程序被最小化/最大化或失去焦点,这些点将会消失。如果需要,可以通过调用“public PointF[] filterPoints(PointF pt)”时提供的输出,将点直接绘制到 Image 上。这是 EMGU 中一个简单的编码实现,由于不必要,因此未实现。

 

将卡尔曼滤波器应用于其他应用程序

预先存在的数据和图形显示

 

 

历史

[1] 2012/03/05 完成初始文章,发布 V1.0 x86。

[2] 2012/03/07 发布 x64 版本,并包含与替代数据配合使用的示例。

 

 

© . All rights reserved.