将设备中的磁场 X、Y、Z 值转换为全局参考系 [英] Convert magnetic field X, Y, Z values from device into global reference frame

查看:28
本文介绍了将设备中的磁场 X、Y、Z 值转换为全局参考系的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

When you use TYPE_MAGNETOMETER sensor, you get X, Y, Z values of magnetic field strength in relation to the device orientation. What I want to get is to convert these values into global reference frame, clarifying: user takes the device, measure these values, than rotate the device for some degrees around any axis and gets ~the same values. Please, find similar questions below: Getting magnetic field values in global coordinates How can I get the magnetic field vector, independent of the device rotation? In this answer sample solution is described (it is for linear acceleration, but I think it doesn't matter): https://stackoverflow.com/a/11614404/2152255 I used it and I got 3 values, X is always very small (don't think that it is correct), Y and Z are OK, but they still changed a bit when I rotate the device. How could it be adjusted? And could it be solved all? I use simple Kalman filter to approximate measurement values, because w/o it I get quiet different values even if the device is not moving/rotating at all. Please, find my code below:

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) {
    }
}

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

Thanks everybody who read this post and who post some thoughts about the problem in advance.

推荐答案

在我对您上面提供的链接上的检查答案的评论中,我提到了我在 计算参考真北的加速度

In my comment on the checked answer on the link you provided above, I referred to my simple answer at calculate acceleration in reference to true north

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

Let me answer here again with more clarification. The answer is the product of the rotation matrix and the magnetic field values. If you read further on the "X is always very small" is the correct value.

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

The accelerometer and magnetic field sensors measure the acceleration of the device and the magnetic field of the earth at the device location respectively. They are vectors in 3 dimentional space, let call them a and m respectively.
If you stand still and rotate your device, theoretically m does not change assuming there are no magnetic interference from surrounding objects (actually m should change little, if you move around since the magnetic field of the earth should change little in a short distance). But a does change, even though it should not be drastic in most situation.

现在 3 维空间中的向量 v 可以由相对于某个基 (e_1, >e_2e_3),即 v = v_1 e_1 + v_2 e_2 + v_3 e_3.(v_1, v_2, v_3) 称为 v 相对于基 (e_1, e_2, e_3).

Now a vector v in 3 dimensional space can be represented by a 3-tuples (v_1, v_2, v_3) with respect to some basis (e_1, e_2, e_3), i.e v = v_1 e_1 + v_2 e_2 + v_3 e_3. (v_1, v_2, v_3) are called the coordinates of v with respect to the basis (e_1, e_2, e_3).

在 Android 设备中,基础是 (x, y, z) 其中,对于大多数手机,x 沿较短边并指向右侧,y 沿较长边并指向上方,z 与屏幕垂直并指向外.
现在这个基础随着设备位置的变化而变化.在数学中,可以将这些基数视为时间的函数(x(t)、y(t)、z(t))术语它是一个移动坐标系.

In Android devices, the basis is (x, y, z) where, for most phone, x is along the shorter side and pointing right, y is along the longer side and pointing up and z is perpendicular to the screen and pointing out.
Now this basis changes as the position of the device changes. One can think these bases as a function of time (x(t), y(t), z(t)), in mathematics term it is a moving coordinate system.

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

Thus even though m does not change, but the event.values returns by the sensors are different because the basis is different (I will talk about fluctuation later). As is, the event.values are useless because it gives us the coordinates but we do not know what the basis is, i.e with respect to some basis we know.

现在的问题是:是否可以找到 am 相对于固定世界基础(w_1w_2w_3),其中 w_1 指向东,w_2 指向磁北,w_3强>指向天空?

Now the question is: is it possible to find the coordinates of a and m with respect to the fixed world basis (w_1, w_2, w_3) where w_1 points toward East, w_2 points toward magnetic North and w_3 points up toward the sky?

答案是肯定的,前提是满足 2 个重要假设.
有了这 2 个假设,就可以简单地计算(只需几个叉积)基础矩阵 R 从基础 (x, y, z) 到基础(w_1w_2w_3),在 Android 中称为 >旋转矩阵.然后向量 v 相对于基 (w_1, w_2, w_3) 的坐标由下式获得R 乘以 v 相对于 (x, y, z>).因此,m 相对于世界坐标系的坐标只是 旋转矩阵 和 TYPE_MAGNETIC_FIELD 返回的 event.values 的乘积传感器和类似的a.

The answer is yes provided 2 important assumptions are satisfied.
With these 2 assumptions it is simple to calculate (just a few cross products) the change of basis matrix R from the basis (x, y, z) to the basis (w_1, w_2, w_3), which in Android is called the Rotation matrix. Then the coordinates of a vector v with respect to the basis (w_1, w_2, w_3) is obtained by multiply R with the coordinates of v with respect to (x, y, z). Thus the coordinates of m with respect to the world coordinates system is just the product of the rotation matrix and the event.values returned by the TYPE_MAGNETIC_FIELD sensor and similarly for a.

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

In android the rotation matrix is obtained by calling getRotationMatrix (float[] R, float[] I, float[] gravity, float[] geomagnetic) and we normally pass in the returned accelerometer values for the gravity parameter and the magnetic field values for the geomagnetic.

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

The 2 important assumptions are:
1- The gravity parameter represents a vector lying in w_3, more particular it is the minus of the vector influenced by gravity alone.
Thus if you pass in the accelerometer values without filtering, the rotation matrix will be slightly off. That is why you need to filter the accelerometer so that the filter values are approximately just the minus gravity vector. Since the gravitational acceleration is the dominant factor in the accelerometer vector, normally low pass filter is sufficient.
2- The geomagnetic parameter represents a vector lying in the plane spanned by the w_2 and the w_3 vectors. That is it lies in the North-Sky plane. Thus in term of the (w_1, w_2, w_3) basis, the first coordinate should be 0. Therefore, the "X is always very small" as you stated it above is the correct value, ideally it should be 0. Now the magnetic field values will fluctuate quite a bit. This is kind of expected, just as a regular compass needle will not stand still if you keep it in your hand and your hand shakes a little. Also, you may get interference from objects surround you and in this case the magnetic field values are unpredictable. I once test my compass app sitting near a "stone" table and my compass was off by more than 90 degrees, only by using a real compass that I found out that there is nothing wrong with my app and the "stone" table produces a real strong magnetic field.
With gravity as a dominant factor you can filter accelerometer values, but without any other knowledge, how do you fitler magnetic values? How do you know if there is or isn't any interference from surrounding objects?

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

You can do a lot more like complete knowledge of your device spatial position etc with the understanding of rotation matrix.

这篇关于将设备中的磁场 X、Y、Z 值转换为全局参考系的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆