手势库中有多种手势变化是否能提高识别率?

14

我正在实现应用程序中的手势识别,使用 Gestures Builder 创建手势库。 我想知道一个手势有多个不同的变化是否会帮助或阻碍识别(或性能)。 例如,我想要识别一个圆形手势。 我将至少有两种变化 - 顺时针和逆时针的圆形手势,具有相同的语义含义,以便用户不必考虑。 然而,我想知道是否保存每个方向的几个手势是可取的,例如,具有不同半径或具有“足够接近”的不同形状的手势,如椭圆等,包括每个形状的不同角度旋转。 有人有这方面的经验吗?

1个回答

19

经过一些实验和阅读Android源代码,我学到了一些东西......首先,似乎我不必担心在我的手势库中创建不同的手势来覆盖不同的角度旋转或方向(顺时针/逆时针)的圆形手势。默认情况下,GestureStore使用SEQUENCE_SENSITIVE序列类型(表示起始点和结束点很重要),以及ORIENTATION_SENSITIVE方向类型(表示旋转角度很重要)。然而,这些默认值可以通过“setOrientationStyle(ORIENTATION_INVARIANT)”和“setSequenceType(SEQUENCE_INVARIANT)”进行覆盖。

此外,引用源代码中的注释...“当使用SEQUENCE_SENSITIVE时,只允许单笔画手势”,“ORIENTATION_SENSITIVE和ORIENTATION_INVARIANT仅适用于SEQUENCE_SENSITIVE手势”。

有趣的是,ORIENTATION_SENSITIVE似乎意味着不仅仅是“方向敏感”。它的值为2,并且与它相关的注释和一些相关的(未记录的)常量暗示您可以请求不同级别的灵敏度。

// at most 2 directions can be recognized
public static final int ORIENTATION_SENSITIVE = 2;
// at most 4 directions can be recognized
static final int ORIENTATION_SENSITIVE_4 = 4;
// at most 8 directions can be recognized
static final int ORIENTATION_SENSITIVE_8 = 8;
在调用GestureLibrary.recognize()时,方向类型值(1、2、4或8)会作为参数numOrientations传递给GestureUtils.minimumCosineDistance(),然后执行一些超出我的能力范围的计算(见下文)。如果有人能解释一下,我很感兴趣。我知道它正在计算两个手势之间的角度差异,但我不理解它如何使用numOrientations参数。我的期望是,如果我指定一个值为2,它会找到手势A和手势B的两个变化之间的最小距离——一个变化是“正常B”,另一个是将B旋转180度。因此,我希望一个值为8的情况考虑B的8个变化,每个变化相隔45度。然而,尽管我没有完全理解下面的数学,但在我看来,numOrientations值为4或8并没有直接用于任何计算中,尽管大于2的值确实会导致不同的代码路径。也许这就是为什么这些其他值未经记录的原因。
/**
 * Calculates the "minimum" cosine distance between two instances.
 * 
 * @param vector1
 * @param vector2
 * @param numOrientations the maximum number of orientation allowed
 * @return the distance between the two instances (between 0 and Math.PI)
 */
static float minimumCosineDistance(float[] vector1, float[] vector2, int numOrientations) {
    final int len = vector1.length;
    float a = 0;
    float b = 0;
    for (int i = 0; i < len; i += 2) {
        a += vector1[i] * vector2[i] + vector1[i + 1] * vector2[i + 1];
        b += vector1[i] * vector2[i + 1] - vector1[i + 1] * vector2[i];
    }
    if (a != 0) {
        final float tan = b/a;
        final double angle = Math.atan(tan);
        if (numOrientations > 2 && Math.abs(angle) >= Math.PI / numOrientations) {
            return (float) Math.acos(a);
        } else {
            final double cosine = Math.cos(angle);
            final double sine = cosine * tan; 
            return (float) Math.acos(a * cosine + b * sine);
        }
    } else {
        return (float) Math.PI / 2;
    }
}

从我的阅读推测,最简单和最好的方法是拥有一个存储的循环手势,将序列类型和方向设置为不变。这样,任何循环的东西都应该相当匹配,无论方向或方位如何。所以我尝试了这个方法,对于几乎任何类似圆圈的东西都返回高分(在25到70的范围内)。然而,它还会为根本不像圆形的手势(水平线、V形等)返回约20分的分数。因此,我对应该匹配什么和不应该匹配什么之间的区分感到不满意。目前看起来效果最好的方法是有两个存储的手势,一个向每个方向,并与ORIENTATION_INVARIANT一起使用SEQUENCE_SENSITIVE。这为任何模糊的圆形形状都给出2.5分或更高的分数,但对于不是圆形的手势则得分低于1(或根本没有匹配)。


2
此外,在我的实验中,我发现无需将不同半径的圆形手势存储到手势库中,即可获得与绘制的不同半径手势的良好匹配 - 我在屏幕上绘制的不同半径的圆形手势与相同存储手势的得分相同。关于性能,我还没有进行计时,但基于对代码的查看,我怀疑性能与库中手势数量大致成线性相关。 - Andy Dennie
2
这里有一个提示:如果你调用 setSequenceType(GestureStore.SEQUENCE_INVARIANT),请确保在调用 load() 之前完成,否则你会发现没有任何匹配。 - Andy Dennie
谢谢您。使用额外的方向灵敏度级别解决了我在手势识别中遇到的问题,这些手势具有相似的笔画但不同的旋转。在使用此设置之前,加号和等于号等手势会发生碰撞。 - Ehz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接