写在前面:最开始仅仅使用加速度传感器开发,效果一般,也不错。后来搜集到陀螺仪传感器通过角速度对大腿运动的模拟判断步行更为科学,进行了改进。最后发现安卓4.4以上的版本自带步行加速度传感器(adb shell pm list features后需要含有 feature:android.hardware.sensor.stepcounter和feature:android.hardware.sensor.stepdetector支持,但是华为手机只有前者没有后者)。
简易版的计步器最重要的部分就是传感器的应用:
本项目用到了加速度传感器和陀螺仪传感器。陀螺仪传感器的最主要的作用在于可以测量角速度。
首先通过加速度传感器进行简易开发:
加速度传感器又叫G-sensor,返回x、y、z三轴的加速度数值。
该数值包含地心引力的影响,单位是m/s^2。
将手机平放在桌面上,x轴默认为0,y轴默认0,z轴默认9.81(由于地球的固有重力)。
将手机朝下放在桌面上,z轴为-9.81。
将手机向左倾斜,x轴为正值。
将手机向右倾斜,x轴为负值。
将手机向上倾斜,y轴为负值。
将手机向下倾斜,y轴为正值。
很简单。第一步,声明加速度传感器,注册监听。
sensorManager=(SensorManager) getSystemService(SENSOR_SERVICE);
sensor=sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); //获取传感器,在计步器中需要使用的是加速度传感器
sensorManager.registerListener(this,sensor,sensorManager.SENSOR_DELAY_UI);
第二步,写一个xml文件。
xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="5dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="30dp"
android:text="简易计步器"
android:textSize="25sp" />
<TextView
android:id="@+id/tv_step"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="5dp"
android:text="0"
android:textColor="#DE5347"
android:textSize="100sp"
android:textStyle="bold" />
<Button
android:id="@+id/btn_start"
android:layout_width="match_parent"
android:layout_height="64dp"
android:text="开始"
android:textSize="25sp" />
LinearLayout>
androidx.constraintlayout.widget.ConstraintLayout>
第三步,规定参数,写步数增加的判断逻辑。
分为向上加速度和向下加速度两部分。精度范围控制是否为有效步。Value数组接受传感器的实时数值,然后current_value记录当前加速度的模,last_value记录上一次加速度记录值的模。
这里涉及到一些物理知识,我们综合考虑大腿向前运行的过程,重点考虑竖直方向上向上向下模型,每一次走路都会有一个类似简谐运动的波形,首先会从平衡点向上运动,然后运动到顶点会再向下运动,直到运动到向下的顶点。
由于是加速度传感器,所以必须考虑加速度变化情况。分成从平衡点向上,向下,以及从上顶点回到平衡点和从下顶点回到平衡点四个部分,加速度方向分别向下、向上、向下、向上。于是传感器检测算法执行的时候,要时时刻刻判断顶点位置,如果当前位于顶点,且加速度向下,表示位于最高点;如果当前位于顶点,且加速度向上,表示位于最低点。我们规定,一次从最高点到最高点的运动过程为走一步的过程。所以如果当前位于最高点,且精度大于所给值,就步数++,更改lastvalue值,否则只更改lastvalue值。
最后就是不断更改数值,测试range的值,范围精度过小或过大都会影响传感器正确检测。我测试了很多值,发现range=3或5效果为佳。
private int step;
private double original_value;
private double last_value;
private double current_value;
private boolean motionState=true; //是否处于运动状态
private boolean processState=false; //是否已经开始计步
double range=5; //设置一个精度范围
float[] value=event.values;
current_value =magnitude(value[0],value[1],value[2]); //计算当前的模
//向上加速的状态
if(motionState==true){
if (current_value >= last_value)
last_value = current_value;
else {
//检测到一次峰值
if(Math.abs(current_value-last_value)>range){
original_value=current_value;
motionState=false;
}
}
}
//向下加速的状态
if(motionState==false){
if (current_value <= last_value)
last_value = current_value;
else {
//检测到一次峰值
if(Math.abs(current_value-last_value)>range){
original_value=current_value;
if (processState==true){
step++; //检测到开始记录,步数加1
if(processState==true){
textView_step.setText(step+""); //更新读数
}
}
motionState=true;
}
}
}
这种方法还欠缺,可以改进!改进方法就是用陀螺仪传感器更好地模拟运动过程。
陀螺仪最早是法国科学家在1850年在研究地球自转中获得灵感而发明的,如上图所示,将一个高速旋转的陀螺放到一个万向支架上,靠陀螺的方向来计算角速度,其简易图如下所示。
中间金色的转子即为陀螺,它因为惯性作用是不会受到影响的,周边的三个“钢圈”则会因为设备的改变姿态而跟着改变,通过这样来检测设备当前的状态,而这三个“钢圈”所在的轴,也就是三轴陀螺仪里面的“三轴”,即X轴、y轴、Z轴,三个轴围成的立体空间联合检测手机的各种动作,陀螺仪的最主要的作用在于可以测量角速度。
陀螺仪又叫角速度传感器,一般用来检测手机姿态的,好像手机中的陀螺仪传感器一般都是三轴的!
体感游戏用得最多,手机拍照防抖,GPS惯性导航,飞行姿态判断,还有为APP添加一些动作感应(比如轻轻晃动手机关闭来电铃声)等等
陀螺仪传感器的单位:角速度(弧度/秒)radians/second
获得传感器用的是:Sensor.TYPE_GYROSCOPE
他的三个值依次是沿着X轴,Y轴,Z轴旋转的角速度,手机逆时针旋转,角速度值为正,顺时针则为负值!
首先完成demo测试陀螺仪传感器数值,并摇晃手机观察幅度变化。
这一数据可以为后续步数传感器开发做基础。代码如下:
package com.example.pedometer2;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.util.Log;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE);
mSensorManager.registerListener(mSensorEventListener, mSensor, SensorManager.SENSOR_DELAY_GAME);
}
private SensorManager mSensorManager;
private Sensor mSensor;
@Override
protected void onResume() {
super.onResume();
mSensorManager.registerListener(mSensorEventListener, mSensor, SensorManager.SENSOR_DELAY_GAME);
}
@Override
protected void onPause() {
super.onPause();
mSensorManager.unregisterListener(mSensorEventListener);
}
private float timestamp = 0;
private float angle[] = new float[3];
private static final float NS2S = 1.0f / 1000000000.0f;
private float gx = 0,gy = 0,gz = 0;
private SensorEventListener mSensorEventListener = new SensorEventListener() {
@Override
public void onSensorChanged(SensorEvent sensorEvent) {
if (sensorEvent.accuracy != 0) {
int type = sensorEvent.sensor.getType();
switch (type) {
case Sensor.TYPE_GYROSCOPE:
if (timestamp != 0) {
final float dT = (sensorEvent.timestamp - timestamp) * NS2S;
angle[0] += sensorEvent.values[0] * dT;
angle[1] += sensorEvent.values[1] * dT;
angle[2] += sensorEvent.values[2] * dT;
float anglex = (float) Math.toDegrees(angle[0]);
float angley = (float) Math.toDegrees(angle[1]);
float anglez = (float) Math.toDegrees(angle[2]);
if(gx != 0){
float c = gx - anglex;
if(Math.abs(c) >= 0.5 ){
Log.d("================", "anglex------------>" + (gx - anglex));
gx = anglex;
}
}else{
gx = anglex;
}
if(gy != 0){
float c = gy - angley;
if(Math.abs(c) >= 0.5 ){
Log.d("================", "angley------------>" + (gy - angley));
gy = angley;
}
}else{
gy = angley;
}
if(gz != 0){
float c = gz - anglez;
if(Math.abs(c) >= 0.5 ){
Log.d("================", "anglez------------>" + (gz - anglez));
gz = anglez;
}
}else{
gz = anglez;
}
}
timestamp = sensorEvent.timestamp;
break;
}
}
}
@Override
public void onAccuracyChanged(Sensor sensor, int i) {
}
};
}
最后,基于陀螺仪传感器开发计步器。
程序框架和1一样,关键代码改变如下:
public void onSensorChanged(SensorEvent sensorEvent) {
if (timestamp != 0) {
final float dT = (sensorEvent.timestamp - timestamp) * NS2S;
angle[0] += sensorEvent.values[0] * dT;
angle[1] += sensorEvent.values[1] * dT;
angle[2] += sensorEvent.values[2] * dT;
float anglex = (float) Math.toDegrees(angle[0]);
float angley = (float) Math.toDegrees(angle[1]);
float anglez = (float) Math.toDegrees(angle[2]);
if(magnitude(gx-anglex,gy-angley,gz-anglez)>15 && Math.abs(gz-anglez)>5 && Math.abs(gx-anglex)>5 && Math.abs(gy-angley)>5 ){
if (processState==true){
step++; //检测到开始记录,步数加1
textView_step.setText(step+""); //更新读数
gx = anglex;
gy = angley;
gz = anglez;
}
motionState=true;
}
}
timestamp = sensorEvent.timestamp;
}
经过对比,陀螺仪传感器比加速度传感器对于步伐的检测更为准确。还可以改进的地方是,可以整合加速度传感器和陀螺仪传感器!但笔者觉得陀螺仪传感器已经很棒了!
此外,如果手机自带如下
这两个硬件支持,就可以利用自带的传感器进行步数统计。很可惜华为手机没有。
写在最后:
推荐官方学习文档,我们在进行传感器开发的时候,尽量使用真机测试,同时对于一个期望目标,给出多种传感器和方案,对于每一种都试一试,一定能找出最合适的。
运动传感器 | Android 开发者 | Android Developers (google.cn)
推荐几篇博客:
讲陀螺仪传感器的博客:
陀螺仪传感器的简单了解_谁的锅的博客-CSDN博客_陀螺仪传感器(讲解了历史、工作原理和应用)
讲手机自带记步传感器使用的博客:
Android简单的计步器应用实现_椒盐虾呀的博客-CSDN博客_android计步器实现
实测App运行效果:
加速度传感器还可用于摇一摇等开发,和具体应用功能相结合。
注:第一版是用加速度传感器开发的,第二版是陀螺仪传感器数据查看,第三版是基于陀螺仪传感器开发的。生成的Apk只是第三版,但是源码包含了三版。