大家好,插播一下,最近花了点时间,基于g-sensor,在做一些姿势识别的事情,比如走路,跑步,骑车,起立,坐下,文章还在整理,欢迎关注。周末争取传个apk给大家体验一下。
基于g-sensor的起立坐下的的识别:
http://blog.csdn.net/finnfu/article/details/78543693
基于g-sensor的走路跑步骑车的识别:
http://blog.csdn.net/finnfu/article/details/78543622
开始:
一、写在分享之前
最新发现了很多文章将算法直接拿去用,上,github上,导致下面有很多疑问。
希望大家转载或者改造的时候,可以注明一下算法的原作者为 finnfu以及原文链接,谢谢。
很多人问源码地址,因为一些原因不能提供,写了个简单的算法demo,以及算法介绍文档。
https://github.com/finnfu/stepcount
如果觉得对你有帮助,请给个star吧!
下面是正文:
目前在计步领域比较领先的有乐动力以及春雨计步器,在做算法的参数调试的时候也是一直拿这两个应用做对比。乐动力当之无愧行业第一,不管是应用的体验还是准确度都是非常棒,春雨计步器的亮点是轻量级,使用以及界面操作都很简单。之前因为一些需求,需要做一个计步器,所以就开始自己研究算法了,各种场景(走路拿在手上,放在口袋,跑步),算法的准确度大概可以达到95.7%,综合起来觉得是比春雨略好,但是赢不了乐动力(可以达到97.7%)在体验和大局观为王的互联网时代,我觉得技术上的差距会越来越小,重要的是体验还有对于产品的定位,所以决定将算法与大家分享,第一是希望可以帮到到家,第二也是希望大家提一些意见,让这个算法可以得到改进。
计步器apk下载地址
http://download.csdn.net/detail/finnfu/9534158
二、计步器算法的总体思路以及辅助调试的工具
人在走路时大致分为下面几种场景:
1、正常走路,手机拿在手上(边走边看、甩手、不甩手)
2、慢步走,手机拿在手上(边走边看、甩手、不甩手)
3、快步走,手机拿在手上(甩手、不甩手、走的很快一般不会看手机吧)
4、手机放在裤袋里(慢走、快走、正常走)
5、手机放在上衣口袋里(慢走、快走、正常走)
6、上下楼梯(上面五中场景可以在这个场景中再次适用一遍)
以上,不管出于哪一种场景(其实对应手机不同的运动规律),g-sensor的三轴数据都是有规律可以寻找的。
每一步都有特征点,找到这个特征点,就是识别出来一步。
下面推荐一个工具,叫gsensor-debug,可以观察三轴的曲线,下面是手机上下摆动的曲线
这是很规律曲线只要检测波峰就行了,实际的走路曲线会有很多杂波,算法的作用就是滤除这些杂波(走路的波形可以用工具自己看,可以保存为文件,用excel打开有数据,将数据转换为波形就可以自己看)
三、算法的介绍(贴出核心代码)
1、变量的定义
[java]view plaincopy
//存放三轴数据
float[] oriValues =newfloat[3];
finalintvalueNum =4;
//用于存放计算阈值的波峰波谷差值
float[] tempValue =newfloat[valueNum];
inttempCount =0;
//是否上升的标志位
booleanisDirectionUp =false;
//持续上升次数
intcontinueUpCount =0;
//上一点的持续上升的次数,为了记录波峰的上升次数
intcontinueUpFormerCount =0;
//上一点的状态,上升还是下降
booleanlastStatus =false;
//波峰值
floatpeakOfWave =0;
//波谷值
floatvalleyOfWave =0;
//此次波峰的时间
longtimeOfThisPeak =0;
//上次波峰的时间
longtimeOfLastPeak =0;
//当前的时间
longtimeOfNow =0;
//当前传感器的值
floatgravityNew =0;
//上次传感器的值
floatgravityOld =0;
//动态阈值需要动态的数据,这个值用于这些动态数据的阈值
finalfloatinitialValue = (float)1.3;
//初始阈值
floatThreadValue = (float)2.0;
privateStepListener mStepListeners;
2. 代码,结合注释看
检测步子就是检测波峰,但是要滤除无效的波峰,主要采用了如下三种措施
a、规定曲线连续上升的次数
b、波峰波谷的差值需要大于阈值
c、阈值是动态改变的
另一个是一些参数的初始值,比如initialValue 以及ThreadValue 的初始值,以及averageValue函数的梯度化范围值
需要结合各种场景的波形图来统计,还有几十实际的测试来调试参数,这些参数大概前后调了两个星期,其实总体思路不复杂。
下面贴出核心代码以及一些注释:
(因为一些原因,整个工程我就不传了,后面有时间我可以将app传上来)
[java]view plaincopy
/*
* 注册了G-Sensor后一只会调用这个函数
* 对三轴数据进行平方和开根号的处理
* 调用DetectorNewStep检测步子
* */
@Override
publicvoidonSensorChanged(SensorEvent event) {
for(inti =0; i <3; i++) {
oriValues[i] = event.values[i];
}
gravityNew = (float) Math.sqrt(oriValues[0] * oriValues[0]
+ oriValues[1] * oriValues[1] + oriValues[2] * oriValues[2]);
DetectorNewStep(gravityNew);
}
/*
* 检测步子,并开始计步
* 1.传入sersor中的数据
* 2.如果检测到了波峰,并且符合时间差以及阈值的条件,则判定为1步
* 3.符合时间差条件,波峰波谷差值大于initialValue,则将该差值纳入阈值的计算中
* */
publicvoidDetectorNewStep(floatvalues) {
if(gravityOld ==0) {
gravityOld = values;
}else{
if(DetectorPeak(values, gravityOld)) {
timeOfLastPeak = timeOfThisPeak;
timeOfNow = System.currentTimeMillis();
if(timeOfNow - timeOfLastPeak >=250
&& (peakOfWave - valleyOfWave >= ThreadValue)) {
timeOfThisPeak = timeOfNow;
/*
* 更新界面的处理,不涉及到算法
* 一般在通知更新界面之前,增加下面处理,为了处理无效运动:
* 1.连续记录10才开始计步
* 2.例如记录的9步用户停住超过3秒,则前面的记录失效,下次从头开始
* 3.连续记录了9步用户还在运动,之前的数据才有效
* */
mStepListeners.onStep();
}
if(timeOfNow - timeOfLastPeak >=250
&& (peakOfWave - valleyOfWave >= initialValue)) {
timeOfThisPeak = timeOfNow;
ThreadValue = Peak_Valley_Thread(peakOfWave - valleyOfWave);
}
}
}
gravityOld = values;
}
/*
* 检测波峰
* 以下四个条件判断为波峰:
* 1.目前点为下降的趋势:isDirectionUp为false
* 2.之前的点为上升的趋势:lastStatus为true
* 3.到波峰为止,持续上升大于等于2次
* 4.波峰值大于20
* 记录波谷值
* 1.观察波形图,可以发现在出现步子的地方,波谷的下一个就是波峰,有比较明显的特征以及差值
* 2.所以要记录每次的波谷值,为了和下次的波峰做对比
* */
publicbooleanDetectorPeak(floatnewValue,floatoldValue) {
lastStatus = isDirectionUp;
if(newValue >= oldValue) {
isDirectionUp =true;
continueUpCount++;
}else{
continueUpFormerCount = continueUpCount;
continueUpCount =0;
isDirectionUp =false;
}
if(!isDirectionUp && lastStatus
&& (continueUpFormerCount >=2|| oldValue >=20)) {
peakOfWave = oldValue;
returntrue;
}elseif(!lastStatus && isDirectionUp) {
valleyOfWave = oldValue;
returnfalse;
}else{
returnfalse;
}
}
/*
* 阈值的计算
* 1.通过波峰波谷的差值计算阈值
* 2.记录4个值,存入tempValue[]数组中
* 3.在将数组传入函数averageValue中计算阈值
* */
publicfloatPeak_Valley_Thread(floatvalue) {
floattempThread = ThreadValue;
if(tempCount < valueNum) {
tempValue[tempCount] = value;
tempCount++;
}else{
tempThread = averageValue(tempValue, valueNum);
for(inti =1; i < valueNum; i++) {
tempValue[i -1] = tempValue[i];
}
tempValue[valueNum -1] = value;
}
returntempThread;
}
[java]view plaincopy
/*
* 梯度化阈值
* 1.计算数组的均值
* 2.通过均值将阈值梯度化在一个范围里
* 3.参数暂时不开放(a,b,c,d,e,f,g,h,i,i,k,l)
* */
publicfloataverageValue(floatvalue[],intn) {
floatave =0;
for(inti =0; i < n; i++) {
ave += value[i];
}
ave = ave / valueNum;
if(ave >= a)
ave = (float) b;
elseif(ave >= c && ave < d)
ave = (float) e;
elseif(ave >= f && ave < g)
ave = (float) h;
elseif(ave >= i && ave < j)
ave = (float) k;
else{
ave = (float) l;
}
returnave;
}