作者是新人,请多关照,理工男一枚,说的不清楚的地方,自己琢磨吧哈哈。
最近遇到一个小项目需要研究下记步的算法,闲来无事,花了几天时间自己写了一套简单的记步算法,算法还是不很完善,但亲测准确度大致能接近98%,写出来供大家参考参考。
简单介绍下算法需要的两个重要数据。首先记步需要借助于加速度来计算,用过加速度传感器的同学应该知道加速度的三轴X、Y、Z(不知道查一下Android加速度传感器),由于手机在使用过程中的存在各种姿势和角度,通过三轴加速度来计算会比较麻烦,因此我这里直接取的是加速度的标量和:
Acc=Math.sqrt(X * X + Y * Y + Z * Z);
提醒一下,我之后说的加速度全是三轴加速度的标量和。
除了加速度外,还有个重要的评判数据,就是时间。这个时间是指获取到两个相邻加速度之间的时间差,由于我测试的几款手机的时间差都是一个稳定值(SENSOR_DELAY_GAME级别时,大部分的稳定值为20ms),作者比较懒,就直接以稳定值作为时间度量了,建议最好还是设置个定时器来控制一下,以免这个时间差在软件运行中发生波动。
简单介绍下算法流程:
1.首先把时间差T(单位毫秒)确定下来,以此作为时间度量来处理加速度数据
2.采集加速度数据,存在加速度数组中,数组长度定为为2000/T(2000ms=2s,2秒的依据在于正常人走一步在两秒以内)
3.对加速度数组进行处理,先做平滑(相邻数据求个均值),再对相邻数据求斜率,通过斜率找出数据中的波峰波谷。
4.由于加速度一些误差和人体的自然抖动,找到的波峰波谷会非常多。所以我在相邻的几个波峰(或波谷)之间取了其中最大值(或最小值),这样处理之后使得每个波峰相邻的只能是波谷
5.找出最新出现的两个波谷和一个波峰,取两个波谷之间的距离D1,以及波峰与两波谷之间的平均差值D2,取合适的阙值来做计算,以此来判断是否走了一步。
我发现网上很多的教程都是没有介绍算法思路的,当然程序员之间直接用代码进行交流是常事了,但是对像我这样代码经验不多的入门菜鸟来说还是有点头疼。所以就简单写一下我的思路,能领会多少就看各位了,实在领会不了的,请看下面的代码吧。
我这里只给出算法部分的代码,萌新的代码,觉得写的不规范的看看就好。
//时间差
private int Time_sigle=0;
//统计时间差记录的数量
private int Time_count=0;
//加速度数组长度
private int Acc_num=0;
//加速度数组
private float[] Accs;
//最新的加速度数据在数组的位置
private int Acc_count=0;
//步数
private int step=0;
//三轴加速度标量和acc,时间差time=(this_acc_time-last_acc_time)
void DetectStep(float acc,int time){
//采集前51~100次加速度的时间差,最开始时间会有些大的波动,所以直接去除
//求均值来作为时间度量Time_single
//利用时间度量计算出加速度数组长度
//假如用定时器来传数据将不会有下面这些问题哈,毕竟时间差是定值
if(++Time_count>50&&Time_count<=100){
Time_sigle+=time;
if(Time_count==100){
Time_sigle/=50;
//2000ms为填满整个加速度数组所需时间
Acc_num=2000/Time_sigle;
//定义数组长度Acc_num
Accs=new float[Acc_num];
}
}
if(Acc_num>0){
Accs[Acc_count]=acc;
float[] data1=new float[Acc_num];
float[] data2=new float[Acc_num];
float[] data3=new float[Acc_num];
float[] data4=new float[Acc_num];
//将最新的数据放在数组最前面,以此类推
for(int i=0,j=Acc_count;i<Acc_num;i++,j--){
if(j<0){
j+= Acc_num;
}
data1[i]=(Accs[j]);
}
//对数据进行平滑,采用均值滤波器
data2[0]=(data1[0]+data1[1])/2;
data2[Acc_num-1]=(data1[Acc_num-1]+data1[Acc_num-2])/2;
for(int i=1;i<Acc_num-1;i++){
data2[i]=data1[i-1]+data1[i]+data1[i+1];
data2[i]/=3;
}
//求均值
float ave=0f;
//均值的偏移量(用来滤掉一些较低的峰值,自己可以调节建议0.5~1之间)
float ave_offset=0.8f;
for(int i=0;i<Acc_num;i++){
ave+=data2[i];
}
ave/=Acc_num;
//求斜率
for (int i=1;i<Acc_num-1;i++){
data3[i]=(data2[i]-data2[i+1])/Time_sigle;
}
//利用斜率找峰谷值,用均值和偏移量滤掉部分较低的峰谷值
for(int i=1;i<Acc_num;i++){
if(data3[i-1]*data3[i]<0){
if(data3[i]>0&&data2[i]>(ave+ave_offset)){
data4[i]=1;
}else if(data3[i]<0&&data2[i]<(ave-ave_offset)){
data4[i]=-1;
}
}else if(data3[i]==0&&i<(Acc_num-1)){
if(data3[i-1]*data3[i+1]<0){
if(data2[i]>(ave+ave_offset)){
data4[i]=1;
}else if(data2[i]<(ave-ave_offset)){
data4[i]=-1;
}
}
}
}
//去掉连续的波峰值或者波谷值,使波峰波谷值交替出现
for(int i=0,j=0,sign=0;i<Acc_num;i++){
if(data4[i]!=0){
if (sign==1&&data4[i]==1){
if(data2[i]>data2[j]){
data4[j]=0;
j=i;
} else{
data4[i]=0;
}
}else if(sign==-1&&data4[i]==-1){
if(data2[i]<data2[j]){
data4[j]=0;
j=i;
} else{
data4[i]=0;
}
}else{
sign=(int)data4[i];
j=i;
}
}
}
//index做为出现波谷值的出现界限,当此处出现波谷值时,就去找下一个波峰值和波谷值
// 为什么index=Acc_num/5呢?因为此时能够确保获取的波谷值不会因为最新出现的加速度数据而发生变化
int index=Acc_num/5;
if(data4[index]<0){
//up为下一个峰值,down为下一个波谷值
int up=index;
int down=index;
//找到下一个波峰
for(int i=index+1;i<Acc_num;i++){
if(data4[i]>0){
up=i;
break;
}
}
//找到下一个波谷
for(int i=up+1;i<Acc_num;i++){
if(data4[i]<0){
down=i;
break;
}
}
//以波谷之间的距离、波峰与两个波谷的平均值为判断依据,选择合适的阙值来判断,我给出的不一定是最佳的,可以自行探索
if(down-index>Acc_num/7&&down-index<Acc_num/1.5&&(2*data2[up]-data2[index]-data2[down])>6){
int sum=0;
for(int i=index+1;i<down;i++){
if(data2[i]>ave){
sum++;
}
}
if(sum>(down-index)/4.5) {
step++;
}
}
}
if(++Acc_count==Acc_num){
Acc_count=0;
}
}
}