android - 将设备中的磁场 X、Y、Z 值转换为全局引用系

标签 android sensors android-sensors magnetometer

当您使用 TYPE_MAGNETOMETER 传感器时,您将获得与设备方向相关的磁场强度的 X、Y、Z 值。我想要得到的是将这些值转换为全局引用系,澄清:用户拿起设备,测量这些值,然后围绕任何轴将设备旋转一些度数并获得相同的值。
请在下面找到类似的问题:
Getting magnetic field values in global coordinates
How can I get the magnetic field vector, independent of the device rotation?
在这个答案中描述了示例解决方案(它用于线性加速,但我认为这无关紧要):https://stackoverflow.com/a/11614404/2152255
我用了它,我得到了 3 个值,X 总是很小(不要认为它是正确的),Y 和 Z 还可以,但是当我旋转设备时它们仍然有点变化。怎么可能调整?而且能全部解决吗?我使用简单的卡尔曼滤波器来近似测量值,因为没有它,即使设备根本没有移动/旋转,我也会得到安静的不同值。请在下面找到我的代码:

import android.app.Activity;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.opengl.Matrix;
import android.os.Bundle;
import android.view.View;
import android.widget.CheckBox;
import android.widget.TextView;
import com.test.statistics.filter.kalman.KalmanState;
import com.example.R;

/**
 * Activity for gathering magnetic field statistics.
 */
public class MagneticFieldStatisticsGatheringActivity extends Activity implements SensorEventListener {

    public static final int KALMAN_STATE_MAX_SIZE = 80;
    public static final double MEASUREMENT_NOISE = 5;

    /** Sensor manager. */
    private SensorManager mSensorManager;
    /** Magnetometer spec. */
    private TextView vendor;
    private TextView resolution;
    private TextView maximumRange;

    /** Magnetic field coordinates measurements. */
    private TextView magneticXTextView;
    private TextView magneticYTextView;
    private TextView magneticZTextView;

    /** Sensors. */
    private Sensor mAccelerometer;
    private Sensor mGeomagnetic;
    private float[] accelerometerValues;
    private float[] geomagneticValues;

    /** Flags. */
    private boolean specDefined = false;
    private boolean kalmanFiletring = false;

    /** Rates. */
    private float nanoTtoGRate = 0.00001f;
    private final int gToCountRate = 1000000;

    /** Kalman vars. */
    private KalmanState previousKalmanStateX;
    private KalmanState previousKalmanStateY;
    private KalmanState previousKalmanStateZ;
    private int previousKalmanStateCounter = 0;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main2);
        mSensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);

        mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
        mGeomagnetic = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);

        vendor = (TextView) findViewById(R.id.vendor);
        resolution = (TextView) findViewById(R.id.resolution);
        maximumRange = (TextView) findViewById(R.id.maximumRange);

        magneticXTextView = (TextView) findViewById(R.id.magneticX);
        magneticYTextView = (TextView) findViewById(R.id.magneticY);
        magneticZTextView = (TextView) findViewById(R.id.magneticZ);

        mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_FASTEST);
        mSensorManager.registerListener(this, mGeomagnetic, SensorManager.SENSOR_DELAY_FASTEST);
    }

    /**
     * Refresh statistics.
     *
     * @param view - refresh button view.
     */
    public void onClickRefreshMagneticButton(View view) {
        resetKalmanFilter();
    }

    /**
     * Switch Kalman filtering on/off
     *
     * @param view - Klaman filetring switcher (checkbox)
     */
    public void onClickKalmanFilteringCheckBox(View view) {
        CheckBox kalmanFiltering = (CheckBox) view;
        this.kalmanFiletring = kalmanFiltering.isChecked();
    }

    @Override
    public void onSensorChanged(SensorEvent sensorEvent) {
        if (sensorEvent.accuracy == SensorManager.SENSOR_STATUS_UNRELIABLE) {
            return;
        }
        synchronized (this) {
            switch(sensorEvent.sensor.getType()){
                case Sensor.TYPE_ACCELEROMETER:
                    accelerometerValues = sensorEvent.values.clone();
                    break;
                case Sensor.TYPE_MAGNETIC_FIELD:
                    if (!specDefined) {
                        vendor.setText("Vendor: " + sensorEvent.sensor.getVendor() + " " + sensorEvent.sensor.getName());
                        float resolutionValue = sensorEvent.sensor.getResolution() * nanoTtoGRate;
                        resolution.setText("Resolution: " + resolutionValue);
                        float maximumRangeValue = sensorEvent.sensor.getMaximumRange() * nanoTtoGRate;
                        maximumRange.setText("Maximum range: " + maximumRangeValue);
                    }
                    geomagneticValues = sensorEvent.values.clone();
                    break;
            }
            if (accelerometerValues != null && geomagneticValues != null) {
                float[] Rs = new float[16];
                float[] I = new float[16];

                if (SensorManager.getRotationMatrix(Rs, I, accelerometerValues, geomagneticValues)) {

                    float[] RsInv = new float[16];
                    Matrix.invertM(RsInv, 0, Rs, 0);

                    float resultVec[] = new float[4];
                    float[] geomagneticValuesAdjusted = new float[4];
                    geomagneticValuesAdjusted[0] = geomagneticValues[0];
                    geomagneticValuesAdjusted[1] = geomagneticValues[1];
                    geomagneticValuesAdjusted[2] = geomagneticValues[2];
                    geomagneticValuesAdjusted[3] = 0;
                    Matrix.multiplyMV(resultVec, 0, RsInv, 0, geomagneticValuesAdjusted, 0);

                    for (int i = 0; i < resultVec.length; i++) {
                        resultVec[i] = resultVec[i] * nanoTtoGRate * gToCountRate;
                    }

                    if (kalmanFiletring) {

                        KalmanState currentKalmanStateX = new KalmanState(MEASUREMENT_NOISE, accelerometerValues[0], (double)resultVec[0], previousKalmanStateX);
                        previousKalmanStateX = currentKalmanStateX;

                        KalmanState currentKalmanStateY = new KalmanState(MEASUREMENT_NOISE, accelerometerValues[1], (double)resultVec[1], previousKalmanStateY);
                        previousKalmanStateY = currentKalmanStateY;

                        KalmanState currentKalmanStateZ = new KalmanState(MEASUREMENT_NOISE, accelerometerValues[2], (double)resultVec[2], previousKalmanStateZ);
                        previousKalmanStateZ = currentKalmanStateZ;

                        if (previousKalmanStateCounter == KALMAN_STATE_MAX_SIZE) {
                            magneticXTextView.setText("x: " + previousKalmanStateX.getX_estimate());
                            magneticYTextView.setText("y: " + previousKalmanStateY.getX_estimate());
                            magneticZTextView.setText("z: " + previousKalmanStateZ.getX_estimate());

                            resetKalmanFilter();
                        } else {
                            previousKalmanStateCounter++;
                        }

                    } else {
                        magneticXTextView.setText("x: " + resultVec[0]);
                        magneticYTextView.setText("y: " + resultVec[1]);
                        magneticZTextView.setText("z: " + resultVec[2]);
                    }
                }
            }
        }
    }

    private void resetKalmanFilter() {
        previousKalmanStateX = null;
        previousKalmanStateY = null;
        previousKalmanStateZ = null;
        previousKalmanStateCounter = 0;
    }

    @Override
    public void onAccuracyChanged(Sensor sensor, int i) {
    }
}

感谢所有阅读这篇文章并提前发布有关该问题的想法的人。

最佳答案

在我对上面提供的链接上的检查答案的评论中,我提到了我在 calculate acceleration in reference to true north 上的简单答案

让我在这里再次回答,并进行更多澄清。答案是 旋转矩阵 磁场值 的乘积。如果您进一步阅读“X 总是非常小”是正确的值。

加速度计和磁场传感器分别测量设备的加速度和设备所在位置的地球磁场。它们是 3 维空间中的向量,分别称为 a m
如果你站着不动并旋转你的设备,理论上 m 不会改变,假设没有来自周围物体的磁干扰(实际上 m 应该几乎没有变化,如果你四处走动,因为地球的磁场应该改变很小短距离)。但是 a 确实发生了变化,尽管在大多数情况下它不应该是剧烈的。

现在,在3维空间中的向量 v 可以由3元组(V_1,V_2,V_3)相对于被表示在一定的基础上( E_1 E_2 E_3 ),即 v = V_1 e_1 + v_2 e_2 + v_3 e_3 . (v_1, v_2, v_3) 被称为 v 相对于基 ( e_1 , _0x1045279167 _207 904 _107 904 _07 904 _107 904) 的坐标。

在Android设备的基础是( X ÿŽ),其中,对于大多数电话, X 是沿着短边和指向右侧,ÿ是沿着长边和指向上和 z 垂直于屏幕并指向。
现在这个基础随着设备位置的变化而变化。人们可以将这些基数视为时间的函数( x (t), y (t), z _0x1045),它是数学中的移动坐标系(数学中的一项)

因此即使 m 没有变化,但是传感器返回的 event.values 是不同的,因为基础不同(我将在后面讨论波动)。按原样, event.values 没有用,因为它给了我们坐标,但我们不知道基础是什么,即关于我们知道的一些基础。

现在的问题是:是否有可能找到一个的坐标相对于固定世界基础( W_1 W_2 w_3 ),其中 W_1 点向东, W_2 指向磁北而 w_3 指向天空?

答案是肯定的,前提是满足 2 个重要假设。
与这些2个假设它是简单的计算(只是几个横产品)基矩阵ř从基础( X ÿŽ)到基础的变化( W_1 W_2 , w_3 ),在 Android 中称为 旋转矩阵 。然后,向量 v 的相对于所述基础( W_1 W_2 w_3 )坐标是由乘法ř v 的坐标获得的关于( X y , z )。因此相对于世界坐标系中的坐标是只是产品的旋转矩阵 event.values 由TYPE_MAGNETIC_FIELD传感器同样地,对于一个返回

在android中旋转矩阵是通过调用 getRotationMatrix (float[] R, float[] I, float[] weight, float[] geomagnetic) 获得的,我们通常传入加速度计和返回的参数值地磁的磁场值。

2 个重要的假设 是:
1- 重力参数 表示位于 w_3 中的向量。仅受重力影响的负向量。
因此,如果您在没有过滤的情况下传入加速度计值, 旋转矩阵 将略微偏离。这就是为什么您需要过滤加速度计,以便过滤器值大约只是负重力矢量。由于重力加速度是加速度计矢量的主要因素,通常低通滤波器就足够了。
2- 地磁参数 表示位于由 w_2 _0x10456919 w_2 _0x10456919 w_2 _0x10456919 和 _0x10456919 组成的平面中的向量。那就是它位于北天平面。因此,就 ( w_1 , w_2 , w_3 , w_3 )而言,首先应该是正确的,因此,X 值应该是非常小的基础,因此首先应该是正确的,X 应该是正确的,因为它首先应该是正确的,“X为 0。现在磁场值会波动很大。这是意料之中的,就像普通的罗盘指针如果握在手中并且手稍微抖动一下就不会静止一样。此外,您可能会受到周围物体的干扰,在这种情况下,磁场值是不可预测的。我曾经测试我的指南针应用程序坐在“石头” table 附近,我的指南针偏离了 90 多度,只有使用真正的指南针,我发现我的应用程序没有任何问题,“石头” table 产生了一个真正的强磁场。
以重力为主导因素,您可以过滤加速度计值,但在没有任何其他知识的情况下,您如何过滤磁值?你怎么知道是否有来自周围物体的干扰?

通过了解 旋转矩阵 ,您可以做更多的事情,例如完全了解您的设备空间位置等。

关于android - 将设备中的磁场 X、Y、Z 值转换为全局引用系,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15315129/

相关文章:

android - 传感器融合 : Which sensors are required for finding Rotation Vector?

Android 测试 onTouch、onDraw 和加速度计,但有些图形不工作

android - android计步器延迟

android - Android(虚拟)传感器的用电量

android - 如何稳定指南针?

java - Android ImageDownloader类: sHardBitmapCache NOT static when it should be?可能存在BUG

android - Cardview 突然停止显示阴影

Android:使用 IntentService 获取快速数据

android - React Native 未找到链接包

android - RecyclerView LayoutManager 不同行上的不同跨度计数