摇动检测库 – Windows Phone 菜谱





5.00/5 (3投票s)
本文档介绍了一个辅助库,用于通过使用 Windows Phone 7 设备内置的加速度计来识别摇动(shake)手势。
注意:以下文章最初作为 Windows Phone 配方“Shake Gestures Library”的一部分发布,您可以在 这里 找到,该文章是我与 Yochay Kiriaty 一起为微软撰写的。
文档目的
本文档介绍了一个辅助库,用于通过使用 Windows Phone 7 设备内置的加速度计来识别摇动(shake)手势。它解释了如何使用该库,库内部的工作原理,以及如何配置库的参数以满足您的手势检测需求。
库功能
摇动手势库使用加速度计来检测三个方向的运动
- 左右(X 方向)
- 上下(Y 方向)
- 前后(Z 方向)
如果您正在寻找一个通用的摇动(shake)手势,并且不关心手势的方向,那么您可以使用一个或多个受支持手势的任意组合。然而,有时您需要更好的控制,并了解手势是否在“正确的”方向上。
如何使用摇动手势库
由于摇动手势库为您完成了所有繁重的工作,使用该库只需要您付出很少的努力。
步骤 1:添加对摇动手势库的引用:ShakeGestures.dll
步骤 2:在文件头添加一个 using
语句
using ShakeGestures;
步骤 3:注册 ShakeGesture
事件
// register shake event
ShakeGesturesHelper.Instance.ShakeGesture +=
new EventHandler<ShakeGestureEventArgs>(Instance_ShakeGesture);
// optional, set parameters
ShakeGesturesHelper.Instance.MinimumRequiredMovesForShake = 5;
步骤 4:实现步骤 3 中的 ShakeGesture
事件处理程序
private void Instance_ShakeGesture(object sender, ShakeGestureEventArgs e)
{
_lastUpdateTime.Dispatcher.BeginInvoke(
() =>
{
_lastUpdateTime.Text = DateTime.Now.ToString();
CurrentShakeType = e.ShakeType;
});
}
ShakeGestureEventArgs
包含一个 ShakeType
属性,用于标识摇动(shake)手势的方向。
最后,您需要激活该库,它会绑定到手机的加速度计并开始监听传入的传感器输入事件。
// start shake detection
ShakeGesturesHelper.Instance.Active = true;
注意:您可以继续直接使用手机的传感器。ShakeGesturesHelper 类不会阻止任何传感器事件——它只是监听它们。
注意:ShakeGesture 事件发生在与 UI 线程不同的线程上,因此如果您想从该事件更新 UI,应该将您的代码分派到 UI 线程上运行。这可以通过使用方法 myControl.Dispatcher.BeginInvoke() 来实现,其中 myControl 是您想要更新的控件。
摇动手势库是如何工作的?
我们需要弄清楚什么是摇动手势(shake gesture),以及如何在摇动发生时识别它。然后,我们需要根据手势方向对摇动进行分类。
Windows Phone 中的加速度计传感器测量施加在手机上的重力,大约每秒报告 50 次 AccelerometerReading
事件。当您在 3D 空间中上下移动手机时,您会获得大量读数(传感器相当嘈杂),并且您并不真正知道手机的方向或手机移动的距离,因为您只知道在给定时间段内手机上相对重力的变化。
考虑到这一点,我们着手创建了一个简单、高性能的摇动手势(shake gesture)库,并提出了以下摇动手势检测过程:
- 降噪
- 分割为“摇动”和“静止”信号段
- 摇动方向分类
我们将传感器读数报告为向量,因为我们获得的是 3 轴读数(X、Y 和 Z,每个都有一个关联的 double
值)。我们可以将初始点设置为 (0, 0, 0
),因此是一个向量。
降噪
在执行手势检测之前,必须从加速度计的输入向量中去除噪声。这是通过将原始输入向量通过低通滤波器来完成的。这会平滑信号,使其忽略小的变化,只考虑任何“足够大”的变化。我们正在使用 Dave Edson 的现有降噪实现。有关 Dave 算法的更多详细信息,请查看这篇博客文章。
正如您所见,降噪后的信号更加干净,也更容易处理。
分割为摇动和静止信号段
在清理完信号后,我们的下一步是区分摇动和静止信号段。基本上,我们希望在满足以下条件时识别摇动信号段:
- 一个或多个向量(X、Y 或 Z)的幅度超过某个阈值。
- 向量保持在该阈值之上足够长的时间,这样我们就不会将一次性的小波动误认为是摇动。
注意:我们不使用系统计时器来测量时间。相反,我们依赖于 Windows Phone 传感器每秒生成约 50 个事件这一事实,因此每个事件之间的时间大约为 20 毫秒。因此,五个事件总共大约是 100 毫秒。您会在代码中看到我们测量事件间隔而不是实际世界时钟时间。
暂时忽略我们如何计算向量幅度;我们将在下一节讨论这个问题。现在,看看图 3。您可以看到绿色线条的模式,它清楚地显示了手机在摇动信号段和静止信号段中何时处于何种状态。因此,我们的目标是能够理解何时一系列向量代表一个摇动信号段。一旦我们识别出一个摇动信号段,我们将对其进行进一步处理以提取摇动类型并触发摇动事件。
计算重力向量
首先,我们需要一个反映手机静止状态的参考点。静止信号段用于计算我们称之为最后已知的重力向量的东西。最后已知的重力向量将稍后用于在摇动类型分类时消除重力的影响。
注意:出于两个原因,我们不能假设重力向量始终具有值 (0, -1, 0):
- 地球重力并非在地球上处处恰好为1G。它随地点和海拔高度而略有变化。
- 重力向量的方向取决于您握持手机的方式。如果您旋转它,方向会发生变化。如果您在移动,您的手部方向也会发生变化。
为了计算重力向量,我们取静止信号中最近的向量(<a>MaximumStillVectorsNeededForAverage</a>
参数)并对幅度非常小的向量(<a>StillMagnitudeWithoutGravitationThreshold</a>
参数)进行平均。如果我们没有足够的静止向量来形成代表性样本(<a>MinimumStillVectorsNeededForAverage</a>
参数),我们将中止计算,因为我们无法获得有效的重力向量。
重力向量会为我们获得的每个新的静止信号段重新计算,以适应您握持手机方式的任何变化。请记住,我们正在寻找实时效果;因此,我们需要维护影响手机的当前重力的准确状态。
查找信号边界
现在,假设用户开始摇动他的手机。我们需要找到摇动信号的开始和结束点。为此,我们使用以下算法:
只有两种可能的状态:
- 摇动状态 – 表示我们当前处于摇动信号的中间
- 静止状态 – 表示我们当前处于静止信号的中间(即,不在摇动信号的中间)
基于当前状态和输入向量类型,我们决定下一步是什么。每次我们从加速度计获取一个新的输入向量(在降噪之后),我们都会检查向量幅度是否等于或大于某个摇动阈值<a>ShakeMagnitudeWithoutGravitationThreshold</a>
参数。如果它更大,那么该输入向量就被视为潜在的摇动向量。否则,它被视为静止向量。
对于每个输入向量和当前的“摇动”状态,我们使用以下状态机来确定下一个状态……
- 条件:手机的当前状态是静止,并且新向量具有静止的幅度值。
- 操作:将向量添加到静止信号数组(该数组是一个循环数组)。
- 条件:手机的当前状态是静止,并且新的输入向量的幅度高于最小摇动阈值。
- 操作:设置摇动状态,处理静止信号,将向量添加到摇动信号数组(这并不意味着我们已识别出摇动,我们只是将其添加到数组中以供稍后处理)。
- 条件:当前手机状态是摇动,并且新的输入向量具有摇动幅度。
- 操作:将向量添加到摇动信号,尝试处理摇动信号数组。在这里,我们实际上是在尝试识别摇动,因为我们已经在数组中获得了一些摇动向量。同样,我们需要等待达到最小摇动向量数量才能开始处理摇动信号。
- 条件:当前手机状态是摇动,并且新的输入向量具有静止幅度(低于最小摇动幅度)。
- 操作:将向量添加到摇动信号,除非我们已经收到了太多连续的静止向量(
StillCounterThreshold
参数),在这种情况下,将静止向量移至静止信号并将手机状态更改为静止状态。
摇动类型分类
现在是时候深入研究实际的向量处理了。以下描述了我们使用的算法,但出于性能原因,代码在收集摇动向量时会执行部分处理,但基本思想是相同的。
抵消重力影响(去除地球重力影响)
所有输入向量都包含地球的重力。由于我们不了解手机在 3D 空间中的位置,因此我们不知道 X、Y 或 Z 哪个向量受到地球重力的影响。因此,我们需要去除地球重力的影响。
为了消除重力的影响,我们首先从摇动信号中的所有向量中减去最后已知的重力向量。这样,当我们沿某个轴获得加速度时,我们可以确定用户沿着该轴移动了手机,而不是怀疑这只是重力在欺骗我们。因此,对于每个输入向量,我们都减去最后已知的重力向量。
查找摇动的主要方向
现在我们想找到摇动信号的主要方向,这可以是以下三个方向之一:
- X(左右)
- Y(上下)
- Z(前后)
为此,我们首先需要找到摇动信号中每个向量的主要方向。我们通过检查哪个向量分量具有最大的绝对值来做到这一点。为了更好地理解为什么我们将向量分解为分量,让我们回顾一个二维向量。正如您在图 4 中所见,红色向量分量被分解为 X 和 Y 值[Red value = sqrt(pow(x,2) + pow(y,2))]
。
很明显,红色向量应该被归类为 X 轴向量,因为其 X 分量远强于其 Y 分量。该库对 3D 向量也采用相同的处理方法。
在这一阶段,我们得到了一个不受地球重力影响的向量,我们将其分解为分量,并且我们知道向量的主要方向。
我们的下一步是创建一个向量分量的直方图,然后选择包含最多分量的方向。请记住,我们开始时得到了一个幅度足够强的向量,可以被视为一个摇动向量。我们消除了地球重力的影响,将其分解为不同的分量,并对其进行分类。例如,图 5 显示了一个摇动信号的直方图,该信号的大部分向量都指向 X 方向,因此该摇动信号的主要方向将是 X。
为了防止弱向量(<a>WeakMagnitudeWithoutGravitationThreshold</a>
参数)进入摇动信号而影响直方图结果,我们在计算直方图时只考虑高于某个阈值的向量。
此外,只有当主要方向的向量数量超过某个阈值(<a>MinimumShakeVectorsNeededForShake</a>
参数)时,我们才会认为直方图结果有效。这将使我们能够消除由于对少量数据进行直方图计算而导致的错误结果。
识别手势
到目前为止,我们已经识别出手机在 3D 空间中足够快(有力)的运动,可以被视为摇动。基本上,我们知道摇动信号的轴,但这还不够。为了确保我们检测到的运动确实是一个摇动手势,而不是一个单一的随机运动,我们需要计算几个运动区间,更重要的是,向量方向的变化。摇动被定义为在某个轴上来回移动手机几次。
为了检查摇动信号是否是一个摇动手势,我们将检查主要方向坐标的符号。例如,如果我们发现摇动信号的主要方向是 X,那么我们将检查信号中所有向量的 X 分量的符号。在真实摇动手势中,我们期望符号会改变几次,因为我们来回移动手机,从而不断改变力(加速度),这会转化为方向的变化。
例如,在下面的图中,我们看到了一个真实摇动手势的归一化(无重力)摇动信号。您可以清楚地看到主要方向是 Z,Z 值在正负之间来回变化。
为了识别手势,我们将计算 X 值从正到负再到正的变化次数。如果计数大于 <a>MinimumRequiredMovesForShake</a>
,我们将最终触发摇动手势事件!
配置参数
在库的编码和测试过程中,我们发现摇动是非常个体化的。它取决于应用程序的性质以及手机用户实际制作的摇动手势。有可用的自适应和学习算法。虽然这些可能更准确,但它们需要一个学习阶段,而我们不想强迫最终用户和构建应用程序的开发人员进行学习。因此,我们提供了大量的不同属性供您自行调整。
这些参数控制着手势检测算法的各个方面。通过更改这些参数,您可以改变您的应用程序对摇动和摇动持续时间的灵敏度。所有参数都有默认值,这些值保存在库中作为常量。
以下部分描述了可用的参数。每个参数都在上面算法的上下文中提及。
ShakeMagnitudeWithoutGravitationThreshold
描述:任何在(去除重力后)幅度大于此参数值的向量都被视为摇动向量。
默认值: 0.2
StillCounterThreshold
描述:此参数决定了停止摇动信号需要多少连续的静止向量。
默认值:20(约 400 毫秒)
StillMagnitudeWithoutGravitationThreshold
描述:此参数确定了作为静止向量考虑的(去除重力后)最大允许幅度。
默认值: 0.02
MaximumStillVectorsNeededForAverage
描述:创建静止向量平均值所需的静止向量的最大数量。我们不平均整个静止信号,而是仅查看最近的静止向量。这是作为运行时优化执行的。
默认值: 20
MinimumStillVectorsNeededForAverage
描述:创建静止向量平均值所需的静止向量的最小数量。没有足够的向量,平均值将不稳定,因此将被忽略。
默认值: 5
MinimumShakeVectorsNeededForShake
描述:确定识别摇动所需的摇动向量数量。
默认值: 10
WeakMagnitudeWithoutGravitationThreshold
描述:幅度低于此参数值的摇动向量将不被考虑用于手势分类。
默认值: 0.2
MinimumRequiredMovesForShake
描述:确定获得摇动信号所需的移动次数。
默认值: 3
摘要
在此文档中,我们展示了如何在您的应用程序中使用摇动手势库。我们深入探讨了用于识别摇动的算法。最后,我们浏览了您可以更改以更好地适应您应用程序需求的各种参数。
尽情摇动吧!
暂时就到这里,
Arik Poznanski。