如何在android中获得平滑的方向数据

时间:2015-01-08 18:00:44

标签: android android-sensors lowpass-filter

我有一个使用方向数据的应用程序,使用预先使用Sensor.TYPE_ORIENTAITON的API-8方法可以很好地工作。平滑数据相对容易。

我正在尝试更新代码以避免使用这种弃用的方法。新的标准方法是用Sensor.TYPE_ORIENTATIONSensor.TYPE_ACCELEROMETER组合替换单Sensor.TYPE_MAGENTIC_FIELD。收到该数据后,会将其(通过SensorManager.getRotationMatrix())发送至SensorManager.getOrientation()。这(理论上)返回与Sensor.TYPE_ORIENTATION相同的信息(除了不同的单位和轴方向)。

然而,这种方法似乎产生的数据比不推荐的方法(仍然有效)更加紧张(即嘈杂)。因此,如果您在同一设备上比较相同的信息,则不推荐使用的方法提供的噪声数据比当前方法少得多。

如何获得弃用方法提供的实际相同(噪声较小)数据?

让我的问题更清楚:我已经阅读了关于这个主题的各种答案,我尝试了各种过滤器:简单的KF / IIR低通,如你所说;中值滤波器在5到19点之间,但到目前为止,我还没有接近通过TYPE_ORIENTATION提供的数据平滑度。

3 个答案:

答案 0 :(得分:7)

对传感器输出应用低通滤波器。

这是我的低通滤波器方法:

private static final float ALPHA = 0.5f;
//lower alpha should equal smoother movement
...
private float[] applyLowPassFilter(float[] input, float[] output) {
    if ( output == null ) return input;

    for ( int i=0; i<input.length; i++ ) {
        output[i] = output[i] + ALPHA * (input[i] - output[i]);
    }
    return output;
}

像这样应用:

float[] mGravity;
float[] mGeomagnetic;
@Override
public void onSensorChanged(SensorEvent event) {
    if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER)
        mGravity = applyLowPassFilter(event.values.clone(), mGravity);
    if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD)
        mGeomagnetic = applyLowPassFilter(event.values.clone(), mGeomagnetic);
    if (mGravity != null && mGeomagnetic != null) {
        float R[] = new float[9];
        float I[] = new float[9];

        boolean success = SensorManager.getRotationMatrix(R, I, mGravity, mGeomagnetic);
        if (success) {
            float orientation[] = new float[3];
            SensorManager.getOrientation(R, orientation);
            azimuth = -orientation[0];
            invalidate();
        }
    }
}

这显然是指南针的代码,删除你不需要的东西。

另外,请查看此SE问题How to implement low pass filter using java

答案 1 :(得分:1)

事实证明,还有另一种,没有特别记录的方法来获取方向数据。隐藏在传感器类型列表中的是TYPE_ROTATION_VECTOR。所以,设置一个:

Sensor mRotationVectorSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR);
sensorManager.registerListener(this, mRotationVectorSensor, SensorManager.SENSOR_DELAY_GAME);

然后:

@Override
public void onSensorChanged(SensorEvent event) {
    final int eventType = event.sensor.getType();

    if (eventType != Sensor.TYPE_ROTATION_VECTOR) return;

    long timeNow            = System.nanoTime();

    float mOrientationData[] = new float[3];
    calcOrientation(mOrientationData, event.values.clone());

    // Do what you want with mOrientationData
}

关键机制是通过旋转矩阵从输入的旋转数据到方向矢量。稍微令人沮丧的是,方向矢量首先来自四元数据,但我无法看到如何直接传递四元数。 (如果您想知道四元数与orientatin和旋转信息的关系以及它们的使用原因,请参阅here。)

private void calcOrientation(float[] orientation, float[] incomingValues) {
    // Get the quaternion
    float[] quatF = new float[4];
    SensorManager.getQuaternionFromVector(quatF, incomingValues);

    // Get the rotation matrix
    //
    // This is a variant on the code presented in
    // http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToMatrix/
    // which has been altered for scaling and (I think) a different axis arrangement. It
    // tells you the rotation required to get from the between the phone's axis
    // system and the earth's.
    //
    // Phone axis system:
    // https://developer.android.com/guide/topics/sensors/sensors_overview.html#sensors-coords
    //
    // Earth axis system:
    // https://developer.android.com/reference/android/hardware/SensorManager.html#getRotationMatrix(float[], float[], float[], float[])
    //
    // Background information:
    // https://en.wikipedia.org/wiki/Rotation_matrix
    //
    float[][] rotMatF = new float[3][3];
    rotMatF[0][0] = quatF[1]*quatF[1] + quatF[0]*quatF[0] - 0.5f;
    rotMatF[0][1] = quatF[1]*quatF[2] - quatF[3]*quatF[0];
    rotMatF[0][2] = quatF[1]*quatF[3] + quatF[2]*quatF[0];
    rotMatF[1][0] = quatF[1]*quatF[2] + quatF[3]*quatF[0];
    rotMatF[1][1] = quatF[2]*quatF[2] + quatF[0]*quatF[0] - 0.5f;
    rotMatF[1][2] = quatF[2]*quatF[3] - quatF[1]*quatF[0];
    rotMatF[2][0] = quatF[1]*quatF[3] - quatF[2]*quatF[0];
    rotMatF[2][1] = quatF[2]*quatF[3] + quatF[1]*quatF[0];
    rotMatF[2][2] = quatF[3]*quatF[3] + quatF[0]*quatF[0] - 0.5f;

    // Get the orientation of the phone from the rotation matrix
    //
    // There is some discussion of this at
    // http://stackoverflow.com/questions/30279065/how-to-get-the-euler-angles-from-the-rotation-vector-sensor-type-rotation-vecto
    // in particular equation 451.
    //
    final float rad2deg = (float)(180.0 / PI);
    orientation[0] = (float)Math.atan2(-rotMatF[1][0], rotMatF[0][0]) * rad2deg;
    orientation[1] = (float)Math.atan2(-rotMatF[2][1], rotMatF[2][2]) * rad2deg;
    orientation[2] = (float)Math.asin ( rotMatF[2][0])                * rad2deg;
    if (orientation[0] < 0) orientation[0] += 360;
}

这似乎使数据非常相似(我没有运行数字测试)到旧的TYPE_ORIENTATION数据:它可用于带边际过滤的设备的运动控制。

还提供了有用的信息here,以及可能的替代解决方案here

答案 2 :(得分:1)

以下是我使用 SensorManager.SENSOR_DELAY_GAME 进行快速更新的方法,即

@Override
protected void onResume() {
    super.onResume();
    sensor_manager.registerListener(this, sensor_manager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER), SensorManager.SENSOR_DELAY_GAME);
    sensor_manager.registerListener(this, sensor_manager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD), SensorManager.SENSOR_DELAY_GAME);
}


移动平均

(效率较低)

private float[] gravity;
private float[] geomagnetic;
private float azimuth;
private float pitch;
private float roll; 

@Override
public void onSensorChanged(SensorEvent event) {
    if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER)
        gravity = moving_average_gravity(event.values);
    if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD)
        geomagnetic = moving_average_geomagnetic(event.values);

    if (gravity != null && geomagnetic != null) {
        float R[] = new float[9];
        float I[] = new float[9];

        boolean success = SensorManager.getRotationMatrix(R, I, gravity, geomagnetic);
        if (success) {
            float orientation[] = new float[3];
            SensorManager.getOrientation(R, orientation);
            azimuth = (float) Math.toDegrees(orientation[0]);
            pitch = (float) Math.toDegrees(orientation[1]);
            roll = (float) Math.toDegrees(orientation[2]);
            //if(roll>-46F && roll<46F)view.setTranslationX((roll/45F)*max_translation); //tilt from -45° to 45° to x-translate a view positioned centrally in a layout, from 0 - max_translation
            Log.i("TAG","azimuth: "+azimuth+" | pitch: "+pitch+" | roll: "+roll);
        }
    }
}




private ArrayList<Float[]> moving_gravity;
private ArrayList<Float[]> moving_geomagnetic;
private static final float moving_average_size=12;//change

private float[] moving_average_gravity(float[] gravity) {
    if(moving_gravity ==null){
        moving_gravity =new ArrayList<>();
        for (int i = 0; i < moving_average_size; i++) {
            moving_gravity.add(new Float[]{0F,0F,0F});
        }return new float[]{0F,0F,0F};
    }

    moving_gravity.remove(0);
    moving_gravity.add(new Float[]{gravity[0],gravity[1],gravity[2]});
    return moving_average(moving_gravity);
}

private float[] moving_average_geomagnetic(float[] geomagnetic) {
    if(moving_geomagnetic ==null){
        this.moving_geomagnetic =new ArrayList<>();
        for (int i = 0; i < moving_average_size; i++) {
            moving_geomagnetic.add(new Float[]{0F,0F,0F});
        }return new float[]{0F,0F,0F};
    }

    moving_geomagnetic.remove(0);
    moving_geomagnetic.add(new Float[]{geomagnetic[0],geomagnetic[1],geomagnetic[2]});
    return moving_average(moving_geomagnetic);
}

private float[] moving_average(ArrayList<Float[]> moving_values){
    float[] moving_average =new float[]{0F,0F,0F};
    for (int i = 0; i < moving_average_size; i++) {
        moving_average[0]+= moving_values.get(i)[0];
        moving_average[1]+= moving_values.get(i)[1];
        moving_average[2]+= moving_values.get(i)[2];
    }
    moving_average[0]= moving_average[0]/moving_average_size;
    moving_average[1]= moving_average[1]/moving_average_size;
    moving_average[2]= moving_average[2]/moving_average_size;
    return moving_average;
}


低通滤波器

(更高效)

private float[] gravity;
private float[] geomagnetic;
private float azimuth;
private float pitch;
private float roll; 

@Override
public void onSensorChanged(SensorEvent event) {
    if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER)
        gravity = LPF(event.values.clone(), gravity);
    if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD)
        geomagnetic = LPF(event.values.clone(), geomagnetic);

    if (gravity != null && geomagnetic != null) {
        float R[] = new float[9];
        float I[] = new float[9];

        boolean success = SensorManager.getRotationMatrix(R, I, gravity, geomagnetic);
        if (success) {
            float orientation[] = new float[3];
            SensorManager.getOrientation(R, orientation);
            azimuth = (float) Math.toDegrees(orientation[0]);
            pitch = (float) Math.toDegrees(orientation[1]);
            roll = (float) Math.toDegrees(orientation[2]);
            //if(roll>-46F && roll<46F)view.setTranslationX((roll/45F)*max_translation); //tilt from -45° to 45° to x-translate a view positioned centrally in a layout, from 0 - max_translation
            Log.i("TAG","azimuth: "+azimuth+" | pitch: "+pitch+" | roll: "+roll);
        }
    }
}




private static final float ALPHA = 1/16F;//adjust sensitivity
private float[] LPF(float[] input, float[] output) {
    if ( output == null ) return input;
    for ( int i=0; i<input.length; i++ ) {
        output[i] = output[i] + ALPHA * (input[i] - output[i]);
    }return output;
}
<块引用>

注意
根据 here

移动平均 12 个值 ALPHA = 0.0625 的低通滤波器代替 here