是这学期专业创新实践的任务,做一个利用手机划出的动作来点名的web应用,当初只是一拍脑袋想出的点子,做起来却不是预想的这么简单。

做动作时每个人操作手机的姿势朝向不同导致加速度计的读取数据区别。首先需要统一坐标系,用陀螺仪数据进行校正。

手机的加速度计数据为当前手机加速度顶部方向xx、右侧方向yy、屏幕向外zz三个方向的加速度分量

将其作为手机本身坐标系的一个向量,向世界坐标系转换

手机上获得的姿态角信息为手机各个轴向与标准姿态的偏差,我们可以将其组合为一个偏移矩阵MM
而为了矫正手机的加速度信息到一个标准坐标系,则可以使用M1M^{-1},将数据“转回来”

旋转的数学工具可以参考图形学中的旋转矩阵
三维坐标系中绕三轴逆时针旋转的矩阵如下:

Mα={1000cosαsinα0sinαcosα}M_\alpha=\begin{Bmatrix}1 & 0 & 0 \\0 & cos\alpha & sin\alpha \\0 & -sin\alpha & cos\alpha \end{Bmatrix}

Mβ={cosβ0sinβ010sinβ0cosβ}M_\beta=\begin{Bmatrix}cos\beta & 0 & -sin\beta \\0 & 1 & 0 \\sin\beta & 0 & cos\beta \end{Bmatrix}

Mγ={cosγsinγ0sinγcosγ0001}M_\gamma=\begin{Bmatrix}cos\gamma & sin\gamma & 0 \\ -sin\gamma & cos\gamma & 0 \\0 & 0 & 1 \end{Bmatrix}

若传感器数据依次为α\alpha,β\beta,γ\gamma
MT=MγMβMαM^T=M_\gamma*M_\beta*M_\alpha
计算顺序与手机给出的旋转顺序相反


应用搭建在web平台上,用js编写,js可以直接提供经过积分处理的陀螺仪数据,即返回手机的实时姿态角,以及手机的实时三轴加速度,方法如下:

1
2
window.addEventListener("deviceorientation", fun(event1){...},true);
window.addEventListener("devicemotion",fun(event2){});

这两个方法分别为浏览器添加了手机姿态角变化以及加速度变化的监听器。
event2中包含有event2.xevent2.yevent2.z三个手机坐标系下的加速度,一目了然。

此处详细讲姿态角获取与处理
方法会添加一个姿态角变化的监听器,每次出现角度变化便会调用fun处理
fun带有一个参数event,其中包含三个成员:
event1.alpha
event1.beta
event1.gamma
分别代表手机的三个角度值,这三个角度值的坐标系与之前提到的略有不同。

alpha表示设备沿zz轴上的旋转角度,范围为0~360,这里的zz轴永远垂直于地面向上

beta 表示设备在xx轴上的旋转角度,范围为-180~180。它描述的是设备由前向后旋转的情况,这里的xx轴方向也是永远垂直于zz轴向右,并与alpha相关。

gamma 表示设备在yy轴上的旋转角度,范围为-90~90。它描述的是设备由左向右旋转的情况,同样yy永远垂直于xx轴向前。

这是标准的x-y-z转动,我们可以看出通过-gamma->-beta->-alpha可以方便地将手机坐标系转换为世界坐标系。
要注意的是,这里并不是按照xxyyzz轴顺序转动的,旋转需要保持与手机坐标系的一致性。
以下是转换函数的一个示例,使用Math.JS库来计算矩阵,代码中使用齐次矩阵:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function rolling(m0){
var may=[
[Math.cos(gamma),0,Math.sin(gamma),0],
[0,1,0,0],
[Math.sin(gamma)*-1,0,Math.cos(gamma),0],
[0,0,0,1]
];
var mbx=[
[1,0,0,0],
[0,Math.cos(beta),-1*Math.sin(beta),0],
[0,Math.sin(beta),Math.cos(beta),0],
[0,0,0,1]
];
var mcz=[
[Math.cos(alpha),-1*Math.sin(alpha),0,0],
[Math.sin(alpha),Math.cos(alpha),0,0],
[0,0,1,0],
[0,0,0,1]
];
m0=math.multiply(m0,may);
m0=math.multiply(m0,mbx);
m0=math.multiply(m0,mcz);
return m0;
}

至此,我们已经可以利用获取的加速度通过转换和模拟积分来粗略的获取手机的运动轨迹。
然而手机的加速度计不甚准确,不同机型的传感器也有好坏之分,获取数据区别很大,我们需要使用滤波器来减小误差。

在这里我们初步尝试使用中值滤波,中值滤波可以很好地去除硬件抖动并平滑曲线。
对于实现签到功能,我们可以直接提取一些特征点,即检测轨迹中的速度方向突变来匹配轨迹图形中的角度一致性。

用一个Array保存手机各个时间点下的加速度值以及采样之间的时间差,将之滤波后模拟积分。
可以得到手机的速度变化序列和空间位置序列。

计算两速度的cos可以简单判断是否产生方向突变,记录突变点的位置数据。
最终的轨迹就被简化成了经过几个突变点位置的折线,计算折线间的夹角即为轨迹的特征点。

以下给出提取特征点的代码,arrA为加速度数据,arrV为速度数据,arrS为位置数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
function touchend(evt){
acac=false;
arrA=midfliter(arrA);

var vxx,vyy,vzz,xx,yy,zz;
xx=yy=zz=vyy=vzz=vxx=0;


var deltaV=7;
var lastI=0;
Sps.push([0,0,0]);

arrA.forEach(function (element, index, array) {
var tdt=element[3];
vxx += element[0] * tdt;
vyy += element[1] * tdt;
vzz += element[2] * tdt;
arrV.push([vxx, vyy, vzz]);

xx += vxx * tdt;
yy += vyy * tdt;
zz += vzz * tdt;
arrS.push([xx, yy, zz]);



if(index>deltaV&&index>lastI){
var tcos=getcos(arrV[index-deltaV],arrV[index]);

if(tcos<0.2){
var p=Math.floor(index-deltaV/2);
Sps.push(arrS[p]);
lastI=index+deltaV;
}
}
});

var ac3=document.getElementById("ac3");
Sps.forEach(function (element,index,array) {
if(index>1){
var a=array[index-2];
var b=array[index-1];
var c=array[index];
console.log(Math.acos(
getcos([b[0]-a[0],b[1]-a[1],b[2]-a[2]],
[c[0]-b[0],c[1]-b[1],c[2]-b[2]])
)/3.14*180)
}
});


}

至此已经实现了手势签到的要求,如果要求精确匹配,可以参考图像匹配算法如ICP等吗,或是引入深度学习。