web-dev-qa-db-ja.com

デバイスからの磁場X、Y、Z値をグローバル参照フレームに変換します

TYPE_MAGNETOMETERセンサーを使用すると、デバイスの向きに関連する磁場強度のX、Y、Z値を取得します。私が取得したいのは、これらの値をグローバル参照フレームに変換して明確にすることです。ユーザーはデバイスを取得し、これらの値を測定します。次に、デバイスを任意の軸を中心にある程度回転させて、同じ値を取得します。以下の同様の質問を見つけてください。 グローバル座標での磁場値の取得デバイスの回転に関係なく磁場ベクトルを取得するにはどうすればよいですか? この回答では、サンプルソリューションについて説明します。 (線形加速用ですが、問題ではないと思います): 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) {
    }
}

この投稿を読んで、事前に問題についていくつかの考えを投稿してくれたすべての人に感謝します。

9
Yahor

あなたが上で提供したリンクのチェックされた答えについての私のコメントで、私は 真北を参照して加速度を計算する で私の簡単な答えを参照しました

ここでもう一度、より明確に答えさせてください。答えは、回転行列磁場値)の積です。さらに読むと、「Xは常に非常に小さい」が正しい値です。

加速度計と磁場センサーは、それぞれデバイスの加速度とデバイスの場所での地球の磁場を測定します。それらは3次元空間のベクトルであり、それぞれaおよびm)と呼びます。
静止してデバイスを回転させた場合、理論的にはmは、周囲の物体からの磁気干渉がないと仮定しても変化しません(実際には、mは、磁気から動き回った場合、ほとんど変化しないはずです地球の磁場は、短い距離ではほとんど変化しないはずです。しかし、aは、ほとんどの状況で劇的ではないはずですが、変化します。

これで、3次元空間のベクトルvは、いくつかの基底(e_1e_2e_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_1e_2e_3)。

Androidデバイスでは、基本は(xyz))です。ここで、ほとんどの電話では、xは短い方に沿っています側面と右向き、yは長辺に沿って上向き、zは画面に垂直で上向き)。
デバイスの位置が変わると、この基準も変わります。これらの基底は時間の関数として考えることができます(x(t)、y(t)、z(t))、数学用語では、それは移動座標系です。

したがって、mは変化しませんが、センサーによるevent.valuesリターンは、基底が異なるため異なります(変動については後で説明します)。そのまま、event.valuesは座標を与えるので役に立たないが、基礎が何であるか、つまり私たちが知っているいくつかの基礎に関してはわからない。

ここで問題は、固定世界ベース(w_1w_2w_3))に関してaおよびm)の座標を見つけることは可能ですか?ここで、w_1は東を指し、w_2磁北を指し、w_3は空を指す)?

2つの重要な仮定が満たされていれば、答えはイエスです。
これらの2つの仮定を使用すると、基底行列[〜#〜] r [〜#〜]から基底変換(x))の変化を計算するのは簡単です(いくつかの外積のみ)。 、yz)を基底に(w_1w_2w_3)、Android 回転行列。次に、基底変換に関するベクトルvの座標(w_1w_2w_3))は、[ 〜#〜] r [〜#〜]vに関して(xyz)。したがって、mワールド座標系に関しては、TYPE_MAGNETIC_FIELDセンサーによって返される回転行列event.values)の積であり、同様にa

In Android 回転行列は、getRotationMatrix(float [] R、float [] I、float [] gravity、float [] geomagnetic)を呼び出すことで取得されます。通常、重力パラメータの返された加速度計の値と地磁気の磁場の値を渡します。

2つの重要な仮定は次のとおりです。
1-重力パラメータw_3にあるベクトルを表します。より具体的には、重力のみの影響を受けるベクトルのマイナスです。
したがって、フィルタリングせずに加速度計の値を渡すと、回転行列がわずかにずれます。そのため、フィルタ値がほぼマイナスの重力ベクトルになるように加速度計をフィルタリングする必要があります。重力加速度が加速度計ベクトルの支配的な要因であるため、通常はローパスフィルターで十分です。
2-地磁気パラメータは、w_2およびw_3ベクトルがまたがる平面にあるベクトルを表します。つまり、北にあります-空の平面。したがって、(w_1w_2w_3)基準では、最初の座標は0である必要があります。したがって、「Xは常に非常に小さい」と述べたように)上記は正しい値であり、理想的には0である必要があります。これで、磁場の値がかなり変動します。これは、通常のコンパスニードルを手に持ったまま手で振ると静止しないのと同じように、一種の予想です。また、周囲の物体から干渉を受ける可能性があり、この場合、磁場の値は予測できません。「石」のテーブルの近くに座っているコンパスアプリをテストしたことがありますが、コンパスが90度以上ずれていました。実際のコンパスを使用すると、アプリに問題はなく、「石」のテーブルが実際の強い磁場を生成することがわかりました。
重力を支配的な要因として、加速度計の値をフィルタリングできますが、他の知識がなくても、磁気値をどのように適合させますか?周囲の物体からの干渉があるかどうかをどうやって知るのですか?

回転行列を理解することで、デバイスの空間位置などの完全な知識のように多くのことができます。

23
Hoan Nguyen

上記の説明に従って、これを行います

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]);

        }
    }
6
vineetv2821993