简单的记步算法

      这篇文章是介绍算法原理的,对造轮子没兴趣的朋友直接可以直接点这里(Android计步器的实现)。

      最近遇到一个小项目需要研究下记步的算法,闲来无事,花了几天时间自己写了一套简单的记步算法,算法还是不很完善。经过测试除了下楼梯丢失步数较多外,正常行走,跑步,爬楼梯的准确度大致能达到98%以上。

      在处理数据的时候没有用到非常高级的处理技巧,用的是都是最简单的滤波器,不需要很高深的数学知识。

      学习这篇文章之前,你可能需要稍微了解一下有关Android系统传感器的基本知识,不过其实不了解也可以,毕竟算法其实也是思想,都是相通的。

      简单介绍下算法需要的两个重要数据。首先记步需要借助于加速度来计算,用过加速度传感器的同学应该知道加速度的三轴X、Y、Z(不知道查一下Android加速度传感器),由于手机在使用过程中的存在各种姿势和角度,想要分别通过三轴加速度来计算会比较麻烦,因此我这里直接取的是加速度的标量和:

Acc=Math.sqrt(X * X + Y * Y + Z * Z);

 提醒一下,我之后说的加速度全是三轴加速度的标量和。

       除了加速度外,还有个重要的评判数据,就是时间。这个时间是指获取到两个相邻加速度之间的时间差,由于我测试的几款手机的时间差都是一个稳定值(Android传感器中SENSOR_DELAY_GAME级别时,大部分的稳定值为20ms),建议最好还是设置个定时器来控制一下,以免这个时间差在软件运行中发生波动,具体的定时器的实现,相信也不需要我多讲。

 

简单介绍下算法流程:

1.首先把时间差T(单位毫秒)确定下来,以此作为时间度量来处理加速度数据,我这里选的是20ms

2.采集加速度数据,存在加速度数组中,数组长度定为为2000/T(2000ms=2s,2秒的依据在于正常人走一步在两秒以内)

3.对加速度数组进行处理,先做平滑(相邻数据求个均值),再对相邻数据做差分,通过差分值找出数据中的波峰波谷。

4.由于加速度一些误差和人体的自然抖动,找到的波峰波谷会非常多。所以我在相邻的几个波峰(或波谷)之间取了其中最大值(或最小值),这样处理之后使得每个波峰(波谷)相邻的只能是波谷(波峰)

5.找出最新出现的两个波谷和一个波峰,取两个波谷之间的距离D1,以及波峰与两波谷之间的平均高度差值D2,取合适的阙值来做计算,以此来判断是否走了一步。

我发现网上很多的教程都是没有介绍算法思路的,当然程序员之间直接用代码进行交流是常事了,但是对像我这样代码经验不多的入门菜鸟来说还是有点头疼。所以就简单写一下我的思路,能领会多少就看各位了,实在领会不了的,请看下面的代码吧。

我这里只给出算法部分的代码,萌新的代码,觉得写的不规范的看看就好。github工程地址

    //时间差
    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;
            }
        }
    }

 

  • 11
    点赞
  • 57
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值