我正在我的应用程序中实现手势识别,使用Gestures Builder创建手势库。我想知道手势的多种变化是否有助于或阻碍识别(或表现)。例如,我想要识别圆形手势。我将至少有两个变体 - 一个用于顺时针圆圈,一个用于逆时针方向,具有相同的语义含义,因此用户无需考虑它。但是,我想知道是否需要为每个方向保存几个手势,例如,各种半径,或者具有“足够接近”的不同形状 - 如蛋形,椭圆等,包括不同的角度旋转每个。有人有这方面的经验吗?
答案 0 :(得分: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;
在调用GestureLibary.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分。所以,我觉得应该匹配什么不应该匹配的东西并不是很好。看起来效果最好的是拥有两个存储的手势,每个方向一个,并将SEQUENCE_SENSITIVE与ORIENTATION_INVARIANT结合使用。对于任何非圆形的东西,这给我的分数为2.5或更高,但是对于非圆形的姿势,分数低于1(或根本没有匹配)。