大家好,插播一下椅文,最近花了點(diǎn)時(shí)間喂很,基于g-sensor,在做一些姿勢(shì)識(shí)別的事情皆刺,比如走路少辣,跑步,騎車羡蛾,起立漓帅,坐下,文章還在整理痴怨,歡迎關(guān)注忙干。周末爭(zhēng)取傳個(gè)apk給大家體驗(yàn)一下。
http://blog.csdn.net/finnfu/article/details/78543693
http://blog.csdn.net/finnfu/article/details/78543622
開始:
一浪藻、寫在分享之前
最新發(fā)現(xiàn)了很多文章將算法直接拿去用豪直,簡(jiǎn)書上,github上珠移,導(dǎo)致下面有很多疑問弓乙。
希望大家轉(zhuǎn)載或者改造的時(shí)候,可以注明一下算法的原作者為 finnfu以及原文鏈接钧惧,謝謝暇韧。
很多人問源碼地址,因?yàn)橐恍┰虿荒芴峁┡ǖ桑瑢懥藗€(gè)簡(jiǎn)單的算法demo懈玻,以及算法介紹文檔。
https://github.com/finnfu/stepcount
如果覺得對(duì)你有幫助乾颁,請(qǐng)給個(gè)star吧涂乌!
下面是正文:
目前在計(jì)步領(lǐng)域比較領(lǐng)先的有樂動(dòng)力以及春雨計(jì)步器,在做算法的參數(shù)調(diào)試的時(shí)候也是一直拿這兩個(gè)應(yīng)用做對(duì)比英岭。樂動(dòng)力當(dāng)之無愧行業(yè)第一湾盒,不管是應(yīng)用的體驗(yàn)還是準(zhǔn)確度都是非常棒,春雨計(jì)步器的亮點(diǎn)是輕量級(jí)诅妹,使用以及界面操作都很簡(jiǎn)單罚勾。之前因?yàn)橐恍┬枨笠闳耍枰鲆粋€(gè)計(jì)步器,所以就開始自己研究算法了尖殃,各種場(chǎng)景(走路拿在手上丈莺,放在口袋,跑步)送丰,算法的準(zhǔn)確度大概可以達(dá)到95.7%缔俄,綜合起來覺得是比春雨略好,但是贏不了樂動(dòng)力(可以達(dá)到97.7%)在體驗(yàn)和大局觀為王的互聯(lián)網(wǎng)時(shí)代器躏,我覺得技術(shù)上的差距會(huì)越來越小牵现,重要的是體驗(yàn)還有對(duì)于產(chǎn)品的定位,所以決定將算法與大家分享邀桑,第一是希望可以幫到到家瞎疼,第二也是希望大家提一些意見,讓這個(gè)算法可以得到改進(jìn)壁畸。
http://download.csdn.net/detail/finnfu/9534158
二贼急、計(jì)步器算法的總體思路以及輔助調(diào)試的工具
人在走路時(shí)大致分為下面幾種場(chǎng)景:
1、正常走路捏萍,手機(jī)拿在手上(邊走邊看太抓、甩手、不甩手)
2令杈、慢步走走敌,手機(jī)拿在手上(邊走邊看、甩手逗噩、不甩手)
3掉丽、快步走,手機(jī)拿在手上(甩手异雁、不甩手捶障、走的很快一般不會(huì)看手機(jī)吧)
4、手機(jī)放在褲袋里(慢走纲刀、快走项炼、正常走)
5、手機(jī)放在上衣口袋里(慢走示绊、快走锭部、正常走)
6、上下樓梯(上面五中場(chǎng)景可以在這個(gè)場(chǎng)景中再次適用一遍)
以上面褐,不管出于哪一種場(chǎng)景(其實(shí)對(duì)應(yīng)手機(jī)不同的運(yùn)動(dòng)規(guī)律)拌禾,g-sensor的三軸數(shù)據(jù)都是有規(guī)律可以尋找的。
每一步都有特征點(diǎn)盆耽,找到這個(gè)特征點(diǎn)蹋砚,就是識(shí)別出來一步扼菠。
下面推薦一個(gè)工具摄杂,叫g(shù)sensor-debug坝咐,可以觀察三軸的曲線,下面是手機(jī)上下擺動(dòng)的曲線
這是很規(guī)律曲線只要檢測(cè)波峰就行了析恢,實(shí)際的走路曲線會(huì)有很多雜波墨坚,算法的作用就是濾除這些雜波(走路的波形可以用工具自己看,可以保存為文件映挂,用excel打開有數(shù)據(jù)泽篮,將數(shù)據(jù)轉(zhuǎn)換為波形就可以自己看)
三、算法的介紹(貼出核心代碼)
[java]view plaincopy
//存放三軸數(shù)據(jù)
float[]?oriValues?=newfloat[3];
finalintvalueNum?=4;
//用于存放計(jì)算閾值的波峰波谷差值
float[]?tempValue?=newfloat[valueNum];
inttempCount?=0;
//是否上升的標(biāo)志位
booleanisDirectionUp?=false;
//持續(xù)上升次數(shù)
intcontinueUpCount?=0;
//上一點(diǎn)的持續(xù)上升的次數(shù)帽撑,為了記錄波峰的上升次數(shù)
intcontinueUpFormerCount?=0;
//上一點(diǎn)的狀態(tài),上升還是下降
booleanlastStatus?=false;
//波峰值
floatpeakOfWave?=0;
//波谷值
floatvalleyOfWave?=0;
//此次波峰的時(shí)間
longtimeOfThisPeak?=0;
//上次波峰的時(shí)間
longtimeOfLastPeak?=0;
//當(dāng)前的時(shí)間
longtimeOfNow?=0;
//當(dāng)前傳感器的值
floatgravityNew?=0;
//上次傳感器的值
floatgravityOld?=0;
//動(dòng)態(tài)閾值需要?jiǎng)討B(tài)的數(shù)據(jù)鞍时,這個(gè)值用于這些動(dòng)態(tài)數(shù)據(jù)的閾值
finalfloatinitialValue?=?(float)1.3;
//初始閾值
floatThreadValue?=?(float)2.0;
privateStepListener?mStepListeners;
檢測(cè)步子就是檢測(cè)波峰,但是要濾除無效的波峰逆巍,主要采用了如下三種措施
a及塘、規(guī)定曲線連續(xù)上升的次數(shù)
b、波峰波谷的差值需要大于閾值
c锐极、閾值是動(dòng)態(tài)改變的
另一個(gè)是一些參數(shù)的初始值笙僚,比如initialValue 以及ThreadValue 的初始值,以及averageValue函數(shù)的梯度化范圍值
需要結(jié)合各種場(chǎng)景的波形圖來統(tǒng)計(jì)灵再,還有幾十實(shí)際的測(cè)試來調(diào)試參數(shù)肋层,這些參數(shù)大概前后調(diào)了兩個(gè)星期,其實(shí)總體思路不復(fù)雜翎迁。
下面貼出核心代碼以及一些注釋:
(因?yàn)橐恍┰虿凼唬麄€(gè)工程我就不傳了,后面有時(shí)間我可以將app傳上來)
[java]view plaincopy
/*
*?注冊(cè)了G-Sensor后一只會(huì)調(diào)用這個(gè)函數(shù)
*?對(duì)三軸數(shù)據(jù)進(jìn)行平方和開根號(hào)的處理
*?調(diào)用DetectorNewStep檢測(cè)步子
*?*/
@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);
}
/*
*?檢測(cè)步子鸳兽,并開始計(jì)步
*?1.傳入sersor中的數(shù)據(jù)
*?2.如果檢測(cè)到了波峰掂铐,并且符合時(shí)間差以及閾值的條件,則判定為1步
*?3.符合時(shí)間差條件揍异,波峰波谷差值大于initialValue全陨,則將該差值納入閾值的計(jì)算中
*?*/
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;
/*
*?更新界面的處理,不涉及到算法
*?一般在通知更新界面之前衷掷,增加下面處理辱姨,為了處理無效運(yùn)動(dòng):
*?1.連續(xù)記錄10才開始計(jì)步
*?2.例如記錄的9步用戶停住超過3秒,則前面的記錄失效戚嗅,下次從頭開始
*?3.連續(xù)記錄了9步用戶還在運(yùn)動(dòng)雨涛,之前的數(shù)據(jù)才有效
*?*/
mStepListeners.onStep();
}
if(timeOfNow?-?timeOfLastPeak?>=250
&&?(peakOfWave?-?valleyOfWave?>=?initialValue))?{
timeOfThisPeak?=?timeOfNow;
ThreadValue?=?Peak_Valley_Thread(peakOfWave?-?valleyOfWave);
}
}
}
gravityOld?=?values;
}
/*
*?檢測(cè)波峰
*?以下四個(gè)條件判斷為波峰:
*?1.目前點(diǎn)為下降的趨勢(shì):isDirectionUp為false
*?2.之前的點(diǎn)為上升的趨勢(shì):lastStatus為true
*?3.到波峰為止枢舶,持續(xù)上升大于等于2次
*?4.波峰值大于20
*?記錄波谷值
*?1.觀察波形圖,可以發(fā)現(xiàn)在出現(xiàn)步子的地方替久,波谷的下一個(gè)就是波峰凉泄,有比較明顯的特征以及差值
*?2.所以要記錄每次的波谷值,為了和下次的波峰做對(duì)比
*?*/
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;
}
}
/*
*?閾值的計(jì)算
*?1.通過波峰波谷的差值計(jì)算閾值
*?2.記錄4個(gè)值蚯根,存入tempValue[]數(shù)組中
*?3.在將數(shù)組傳入函數(shù)averageValue中計(jì)算閾值
*?*/
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.計(jì)算數(shù)組的均值
*?2.通過均值將閾值梯度化在一個(gè)范圍里
*?3.參數(shù)暫時(shí)不開放(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;
}