Android計步器演算法實現(2)
阿新 • • 發佈:2019-01-07
前言
之前的那套演算法自己平時玩玩其實還是沒問題的,但是在大工程裡面運用遇到了一些不得不優化的問題:
- 定時器太耗資源
- 每次計算的時間間隔短(幾十毫秒),但計算量大
因此針對這兩個問題需要再一次修改演算法。新演算法相比過去優點在於:
- 大大降低計算量
- 不需要使用計時器,減小系統負擔
演算法實現的意義
現實原因
對於大部分的開發者來說,自己寫一套計步器演算法是非常沒有必要的,呼叫系統自帶的計步器即可。那麼我又為什麼要自己動手呢?實際是有幾個原因的:
- 自己實現的演算法有利於做步長估計
- 鑑於國內Android手機碎片化嚴重,不同廠商的手機計步表現差異(下面說明)很大,自己實現的演算法便於控制差異性
不同廠商的手機計步器主要差異在這幾個方面:
- 計步器的敏感度,較敏感的演算法會把手部的輕微抖動也算成走步,敏感度低的演算法則計步丟失會比較嚴重
- 計步器每次返回的步數,因為Android手機用的是系統回撥的方式而不是查詢的方式,因此有的手機是走一步就回調(大部分Android手機),而有的則是走四步回撥(蘋果手機和少部分Android手機),還有些非固定數值返回的手機就不提了
- 計步器判決人在行走的時間長短,所有系統級演算法都會等人走了一定步數後才能確定人確實在行走,然後才開始回撥(此時會把之前走的步數一起返回)。大部分手機演算法需要等十步左右才開始,華為手機一般要等二三十步
優缺點
自己實現演算法的意義就是希望避開上面說的差異性問題,實現演算法上的統一。個人認為優點在於:
- 多了步長估計
- 每走一步就提示步態變化
缺點在於:
- 精度略微降低(每百步差一步左右)
- 沒有步行的預判時間
演算法原理
運動狀態判斷
判斷人的運動狀態。簡單做,利用滑動窗求方差,方差超過一定門限就判斷在運動中。
計步原理
計步演算法原理實際上相當簡單。我們來看走路時候的波形(加速度資料):
這個是處理過的訊號,但是真實訊號實際上就是這麼簡單。我們只要把每個峰值從中抓出來然後加點判決門限就大功告成了。
步長計算原理
這裡用到了兩個引數H和T,見圖:
H對應峰谷值的差值,T對應兩個波谷之間的時間差。帶入公式得到步長:
公式還不是最優的,需要驗證改進。
Java實現
public class StepController {
//狀態:靜止
public static int STAY=0;
//狀態:運動
public static int MOVE=1;
//運動狀態判斷陣列
private float[] stateValue=new float[20];
//加速度陣列
private float[] Accs=new float[100];
//每次傳入加速度的時間,省去定時器
private long[] time=new long[100];
//陣列下標
private int count=0;
//行走步數
private int Step=0;
//步長
private float Length=0;
//行進距離
private float Distance=0;
//狀態
private int State=STAY;
//回撥
public interface StepCallback{
void refreshStep(int step,float stepLength,float distance);
}
private StepCallback callback;
public StepController(StepCallback callback){
this.callback=callback;
}
//傳入加速度和時間
public void refreshAcc(float[] values,long timestamp){
Accs[count]=(float) Math.sqrt(values[0]*values[0]+
values[1]*values[1]+
values[2]*values[2]);
time[count]=timestamp;
//檢查運動狀態
stateValue[count%20]=Accs[count];
checkState();
//設定檢測點,檢測點的作用是來減少計算量
final int ckpoint = Accs.length / 5;
//運動狀態判斷加上判斷檢測點是否有波谷存在
if (State==MOVE&&Accs[(count-ckpoint+Accs.length)%Accs.length]<Accs[(count-ckpoint+Accs.length+1)%Accs.length]
&&Accs[(count-ckpoint+Accs.length)%Accs.length]<Accs[(count-ckpoint+Accs.length-1)%Accs.length]) {
//求均值
float ave = Utils.ave(Accs);
for (int i = 0; i < Accs.length; ++i) {
ave += Accs[i];
}
ave /= Accs.length;
//調整陣列順序,把新資料放在前面,同時把資料減去均值
float[] data = new float[Accs.length];
for (int i = 0, j = count; i < data.length; ++i, --j) {
if (j < 0) j += data.length;
data[i] = Accs[j] - ave;
}
//尋找波峰波谷
float[] sign = new float[Accs.length];
for (int i = 1; i < Accs.length - 1; ++i) {
if (Math.abs(data[i]) > 0.8 && Math.abs(2 * data[i]) > Math.abs((data[i - 1] + data[i + 1]))) {
if (data[i] > 0) {
sign[i] = 1;
} else {
sign[i] = -1;
}
}
}
//取相鄰波峰中的最大值和相鄰波谷中的最小值
for (int i = 1; i < sign.length - 1; ) {
int index = i;
while (++i < sign.length - 1 && (sign[i] == 0 || sign[i] == sign[index])) {
if (sign[i] != 0 && Math.abs(data[i]) > Math.abs(data[index])) {
sign[index] = 0;
index = i;
} else {
sign[i] = 0;
}
}
}
//再次判斷檢測點是否是波谷
if (sign[ckpoint] < 0) {
int index = ckpoint;
//尋找下個波峰
while (++index < sign.length && sign[index] == 0) ;
if (index < sign.length && sign[index] > 0) {
int peak = index;
//尋找下個波谷
while (++index < sign.length && sign[index] == 0) ;
if (index < sign.length && sign[index] < 0) {
int valley = index;
//計算H和T
float H = data[peak] - 0.5f * data[ckpoint] - 0.5f * data[valley];
long T = time[(count - ckpoint + time.length) % time.length] - time[(count - valley + time.length) % time.length];
//門限判決
if (H > 3 && T > 300 && T < 1400) {
//步長計算
DetectStepLength((int) T, H);
++Step;
callback.refreshStep(Step,Length,Distance);
}
}
}
}
}
if (++count==Accs.length) count=0;
}
//運動狀態判斷
private void checkState(){
float ave=0;
float var=0;
//求均值
for (int i=0;i<stateValue.length;++i){
ave+=stateValue[i];
}
ave/=stateValue.length;
//求方差
for (int i=0;i<stateValue.length;++i){
var+=(stateValue[i]-ave)*(stateValue[i]-ave);
}
var/=stateValue.length;
//狀態判決
State=var>0.5?MOVE:STAY;
}
//步長計算,該公式利用最小二乘法推匯出,有一定可信性
private void DetectStepLength(int time,float f){
float steplength=0.35f-0.000155f*time+0.1638f*(float) Math.sqrt(f);
this.Length=(this.Length+steplength)/2;
Distance+=steplength;
}
}