当前位置: 首页 > 知识库问答 >
问题:

将磁场 X、Y、Z 值从设备转换为全局参考系

滕星纬
2023-03-14

使用TYPE_MAGNETOMETER传感器时,可以获得与设备方向相关的磁场强度的X,Y,Z值。我想得到的是将这些值转换为全局参考系,澄清:用户获取设备,测量这些值,而不是围绕任何轴旋转设备一定程度并获得~相同的值。请在下面找到类似的问题: 获取全局坐标中的磁场值 如何获得独立于设备旋转的磁场矢量?在这个答案中描述了html" target="_blank">示例解决方案(它是线性加速度,但我认为这没关系):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) {
    }
}

感谢每一个读了这篇文章并提前发表了一些关于这个问题的想法的人。

共有3个答案

云锦
2023-03-14

随机地把一个答案放在一起,我发现这个答案比这里发布的更好。

https://stackoverflow.com/a/16418016/4033525

似乎传感器管理器.get定向()未能正确转换为世界框架。

正确的代码应该是:

SensorManager.getRotationMatrix(gravityCompassRotationMatrix, inclinationValues, gravityValues, magnitudeValues);
SensorManager.remapCoordinateSystem(currentOrientationRotationMatrix.matrix, worldAxisX, worldAxisY, adjustedRotationMatrix);
float sin = adjustedRotationMatrix[1] - adjustedRotationMatrix[4];
float cos = adjustedRotationMatrix[0] + adjustedRotationMatrix[5];
float m_azimuth_radians = (float) (sin != 0 && cos != 0 ? Math.atan2(sin, cos) : 0);

羊舌富
2023-03-14

按照上面的解释,这样做

private static final int TEST_GRAV = Sensor.TYPE_ACCELEROMETER;
private static final int TEST_MAG = Sensor.TYPE_MAGNETIC_FIELD;
private final float alpha = (float) 0.8;
private float gravity[] = new float[3];
private float magnetic[] = new float[3];

public void onSensorChanged(SensorEvent event) {
    Sensor sensor = event.sensor;
    if (sensor.getType() == TEST_GRAV) {
            // Isolate the force of gravity with the low-pass filter.
              gravity[0] = alpha * gravity[0] + (1 - alpha) * event.values[0];
              gravity[1] = alpha * gravity[1] + (1 - alpha) * event.values[1];
              gravity[2] = alpha * gravity[2] + (1 - alpha) * event.values[2];
    } else if (sensor.getType() == TEST_MAG) {

            magnetic[0] = event.values[0];
            magnetic[1] = event.values[1];
            magnetic[2] = event.values[2];

            float[] R = new float[9];
            float[] I = new float[9];
            SensorManager.getRotationMatrix(R, I, gravity, magnetic);
            float [] A_D = event.values.clone();
            float [] A_W = new float[3];
            A_W[0] = R[0] * A_D[0] + R[1] * A_D[1] + R[2] * A_D[2];
            A_W[1] = R[3] * A_D[0] + R[4] * A_D[1] + R[5] * A_D[2];
            A_W[2] = R[6] * A_D[0] + R[7] * A_D[1] + R[8] * A_D[2];

            Log.d("Field","\nX :"+A_W[0]+"\nY :"+A_W[1]+"\nZ :"+A_W[2]);

        }
    }
王伯寅
2023-03-14

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

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

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

现在,3维空间中的向量v可以由相对于某些基(e_1,e_2,e_3)的3元组(v_1,v_2,v_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)。

在Android设备中,基础是(x,y,z),对于大多数手机来说,x是沿着较短的一侧指向右侧,y是沿着较长的一侧指向上方,z垂直于屏幕并指向外侧
现在,这个基础随着设备位置的变化而变化。人们可以把这些基看作时间的函数(x(t),y(t)和z(t)),用数学术语来说,它是一个移动坐标系。

因此即使m不变,但事件不变。传感器返回的值不同,因为基础不同(我稍后将讨论波动)。事实上,这是一个事件。值是无用的,因为它给出了坐标,但我们不知道基是什么,即关于我们知道的某个基。

现在的问题是:有没有可能找到a和m相对于固定世界基(w_1,w_2,w_3)的坐标,其中w_1指向东方,w_2指向磁北,w_3指向天空?

如果满足两个重要的假设,答案是肯定的。
有了这两个假设,很容易计算(只有几个叉积)基矩阵R从基(x,y,z)到基(w_1,w_2,w_3)的变化,在Android系统中称为旋转矩阵。然后通过将R乘以v相对于(x,y,z)的坐标,得到向量v相对于基(w_1,w_2,w_3)的坐标。因此,m相对于世界坐标系的坐标只是旋转矩阵和TYPE_MAGNETIC_FIELD传感器返回的event.values的乘积,类似地,对于a。

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

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

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

 类似资料:
  • 问题内容: 我将如何在SELECT查询中反转此路径: 为了 其中/是定界符,并且在一行中可以有许多定界符 问题答案: 最简单的方法可能是编写一个存储的pl / sql函数,但是可以单独使用SQL(Oracle)来完成。 这将分解子路径中的路径: 然后,我们使用来重构反向路径:

  • 我正在尝试用熊猫数据框创建一个3d volatlity曲面,我觉得我已经掌握了所有信息,但我不确定如何从中创建3d图形。我读过的每一本指南似乎都使用了3个独立的数组,但我觉得我拥有的数据应该是可绘制的。 我当前的数据帧如下所示: 我希望我的X值是我的索引,Y值是列名(月份),Z值是框架中包含的实际值(例如,第一行第一列中的第一个值...X=35,Y=9/20/2019,Z=0.0879441) 环

  • 我有以下功能: 此代码给出了

  • 问题内容: 我具有纽约市纽约市的纬度/经度值;40.7560540,-73.9869510和地球的平面图像,即1000px×446px。 我希望能够使用Javascript将纬度/经度转换为X,Y坐标,该点将反映该位置。 因此,图像左上角的X,Y坐标将是;289、111 注意事项: 不用担心要使用哪种投影的问题,可以自己做假设,也可以按照自己知道的可行的方法进行操作 X,Y可以形成图像的任意一角

  • 这部分是学术性的,就我的目的而言,我只需要四舍五入到小数点后两位;但我很想知道发生了什么会产生两种略有不同的结果。 这是我编写的测试,将其缩小到最简单的实现: 但它失败了,输出如下: 有谁能详细解释一下是什么原因导致 我在一个答案中寻找的一些要点是:精度损失在哪里?哪种方法是首选的,为什么?哪一个实际上是正确的?(在纯数学中,不可能两者都是对的。也许两者都是错的?)对于这些算术运算,有没有更好的解

  • 我正在编写一个安全系统,拒绝未经授权的用户访问。 它按预期授予授权用户访问权限,但也允许未经授权的用户进入! 为什么会发生这种情况?我已经明确声明,只有当等于Kevin、Jon或Inbar时,才允许访问。我也尝试过相反的逻辑,,但结果是一样的。 注意:这个问题旨在作为这个非常常见的问题的规范重复目标。还有另一个热门问题如何针对单个值测试多个变量?这有同样的基本问题,但比较目标是相反的。这个问题不应