姓名:周君會? ? ? ? 學號:17011210526
轉載自:
https://www.zhihu.com/question/19808224
【嵌牛導讀】:計步器可以記錄我們的步數(shù)衅谷,督促我們的健康,對我們的生活帶來了極大地便利瑰妄。
【嵌牛鼻子】:運動手環(huán)也是極其常見账月,手機中自帶的也有計步器太雨。
【嵌牛提問】:但是手機中自帶的計步器是如何實現(xiàn)精準計步的呢租悄,它的相關原理是什么奕枝?
【嵌牛正文】:
Android_基于G-Sensor的計步算法
一朴沿、寫在分享之前
學習Android也有將近一年的時間了雌芽,一直在看大牛們分享的知識授艰,今天也想分享自己之前的一點研究,關于計步器算法的世落。目前在計步領域比較領先的有樂動力以及春雨計步器淮腾,在做算法的參數(shù)調試的時候也是一直拿這兩個應用做對比。樂動力當之無愧行業(yè)第一屉佳,不管是應用的體驗還是準確度都是非常棒谷朝,春雨計步器的亮點是輕量級,使用以及界面操作都很簡單武花。之前因為一些需求圆凰,需要做一個計步器,所以就開始自己研究算法了体箕,各種場景(走路拿在手上专钉,放在口袋,跑步)累铅,算法的準確度大概可以達到95.7%跃须,綜合起來覺得是比春雨略好,但是贏不了樂動力(可以達到97.7%)在體驗和大局觀為王的互聯(lián)網(wǎng)時代娃兽,我覺得技術上的差距會越來越小菇民,重要的是體驗還有對于產品的定位,所以決定將算法與大家分享,第一是希望可以幫到到家玉雾,第二也是希望大家提一些意見,讓這個算法可以得到改進轻要。
二复旬、計步器算法的總體思路以及輔助調試的工具
人在走路時大致分為下面幾種場景:
1、正常走路冲泥,手機拿在手上(邊走邊看驹碍、甩手、不甩手)
2凡恍、慢步走志秃,手機拿在手上(邊走邊看、甩手嚼酝、不甩手)
3浮还、快步走,手機拿在手上(甩手闽巩、不甩手钧舌、走的很快一般不會看手機吧)
4、手機放在褲袋里(慢走涎跨、快走洼冻、正常走)
5、手機放在上衣口袋里(慢走隅很、快走撞牢、正常走)
6、上下樓梯(上面五中場景可以在這個場景中再次適用一遍)
以上叔营,不管出于哪一種場景(其實對應手機不同的運動規(guī)律)屋彪,g-sensor的三軸數(shù)據(jù)都是有規(guī)律可以尋找的。
每一步都有特征點绒尊,找到這個特征點撼班,就是識別出來一步。
下面推薦一個工具垒酬,叫gsensor-debug砰嘁,可以觀察三軸的曲線,下面是手機上下擺動的曲線
這是很規(guī)律曲線只要檢測波峰就行了勘究,實際的走路曲線會有很多雜波矮湘,算法的作用就是濾除這些雜波(走路的波形可以用工具自己看,可以保存為文件口糕,用excel打開有數(shù)據(jù)缅阳,將數(shù)據(jù)轉換為波形就可以自己看)
三、算法的介紹(貼出核心代碼)1、變量的定義//存放三軸數(shù)據(jù)? float[] oriValues = new float[3];? final int valueNum = 4;? //用于存放計算閾值的波峰波谷差值? float[] tempValue = new float[valueNum];? int tempCount = 0;? //是否上升的標志位? boolean isDirectionUp = false;? //持續(xù)上升次數(shù)? int continueUpCount = 0;? //上一點的持續(xù)上升的次數(shù)十办,為了記錄波峰的上升次數(shù)? int continueUpFormerCount = 0;? //上一點的狀態(tài)秀撇,上升還是下降? boolean lastStatus = false;? //波峰值? float peakOfWave = 0;? //波谷值? float valleyOfWave = 0;? //此次波峰的時間? long timeOfThisPeak = 0;? //上次波峰的時間? long timeOfLastPeak = 0;? //當前的時間? long timeOfNow = 0;? //當前傳感器的值? float gravityNew = 0;? //上次傳感器的值? float gravityOld = 0;? //動態(tài)閾值需要動態(tài)的數(shù)據(jù),這個值用于這些動態(tài)數(shù)據(jù)的閾值? final float initialValue = (float) 1.3;? //初始閾值? float ThreadValue = (float) 2.0;? private StepListener mStepListeners;
2. 代碼向族,結合注釋看
檢測步子就是檢測波峰呵燕,但是要濾除無效的波峰,主要采用了如下三種措施
a件相、規(guī)定曲線連續(xù)上升的次數(shù)
b再扭、波峰波谷的差值需要大于閾值
c、閾值是動態(tài)改變的
另一個是一些參數(shù)的初始值夜矗,比如initialValue 以及ThreadValue 的初始值泛范,以及averageValue函數(shù)的梯度化范圍值
需要結合各種場景的波形圖來統(tǒng)計,還有幾十實際的測試來調試參數(shù)紊撕,這些參數(shù)大概前后調了兩個星期罢荡,其實總體思路不復雜。
下面貼出核心代碼以及一些注釋:
(因為一些原因对扶,整個工程我就不傳了柠傍,后面有時間我可以將app傳上來)
/*
* 注冊了G-Sensor后一只會調用這個函數(shù)
* 對三軸數(shù)據(jù)進行平方和開根號的處理
* 調用DetectorNewStep檢測步子
* */
@Override
public void onSensorChanged(SensorEvent event) {
for (int i = 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中的數(shù)據(jù)
* 2.如果檢測到了波峰辩稽,并且符合時間差以及閾值的條件惧笛,則判定為1步
* 3.符合時間差條件,波峰波谷差值大于initialValue逞泄,則將該差值納入閾值的計算中
* */
public void DetectorNewStep(float values) {
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.連續(xù)記錄10才開始計步
* 2.例如記錄的9步用戶停住超過3秒各谚,則前面的記錄失效,下次從頭開始
* 3.連續(xù)記錄了9步用戶還在運動到千,之前的數(shù)據(jù)才有效
* */
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.到波峰為止昌渤,持續(xù)上升大于等于2次
* 4.波峰值大于20
* 記錄波谷值? ? ? * 1.觀察波形圖,可以發(fā)現(xiàn)在出現(xiàn)步子的地方憔四,波谷的下一個就是波峰膀息,有比較明顯的特征以及差值? ? ? * 2.所以要記錄每次的波谷值,為了和下次的波峰做對比
* */? ? ? public boolean DetectorPeak(float newValue, float oldValue) {
lastStatus = isDirectionUp;
if (newValue >= oldValue) {? ? ? ? ? ? ? isDirectionUp = true;
continueUpCount++;
} else {
continueUpFormerCount = continueUpCount;
continueUpCount = 0;
isDirectionUp = false;
}
if (!isDirectionUp && lastStatus
&& (continueUpFormerCount >= 2 || oldValue >= 20)) {
peakOfWave = oldValue;
return true;
} else if (!lastStatus && isDirectionUp) {
valleyOfWave = oldValue;
return false;
} else {? ? ? ? ? ? ? return false;
}
}
/*
* 閾值的計算
* 1.通過波峰波谷的差值計算閾值
* 2.記錄4個值了赵,存入tempValue[]數(shù)組中
* 3.在將數(shù)組傳入函數(shù)averageValue中計算閾值
* */
public float Peak_Valley_Thread(float value) {
float tempThread = ThreadValue;
if (tempCount < valueNum) {
tempValue[tempCount] = value;
tempCount++;
} else {
tempThread = averageValue(tempValue, valueNum);
for (int i = 1; i < valueNum; i++) {
tempValue[i - 1] = tempValue[i];
}
tempValue[valueNum - 1] = value;
}
return tempThread;
}
/*
* 梯度化閾值
* 1.計算數(shù)組的均值
* 2.通過均值將閾值梯度化在一個范圍里
* */
public float averageValue(float value[], int n) {
float ave = 0;
for (int i = 0; i < n; i++) {
ave += value[i];
}
ave = ave / valueNum;
if (ave >= 8)
ave = (float) 4.3;
else if (ave >= 7 && ave < 8)
ave = (float) 3.3;
else if (ave >= 4 && ave < 7)
ave = (float) 2.3;
else if (ave >= 3 && ave < 4)
ave = (float) 2.0;
else {
ave = (float) 1.3;
}
return ave;
}