2012-12-03 1 views
22

ザ・センサーフュージョンビデオは偉大に見えますが、ないコードがありません: http://www.youtube.com/watch?v=C7JQ7Rpwn2k&feature=player_detailpage#t=1315s加速度計の代わりにAndroidジャイロスコープを使用しています。私は、こまごまとの多くを見つけることが、ない完全なコード

はここだけの加速度計とコンパスを使用しています私のコードです。私はまた、3方向の値にカルマンフィルタを使用していますが、それはここに表示するにはあまりにも多くのコードです。結局のところ、これは問題なく動作しますが、その結果は、結果が何であるか、フィルタリング係数をどれくらい低くするかによって、あまりにも不安定すぎるか遅すぎます。

/** Just accelerometer and magnetic sensors */ 
public abstract class SensorsListener2 
    implements 
     SensorEventListener 
{ 
    /** The lower this is, the greater the preference which is given to previous values. (slows change) */ 
    private static final float accelFilteringFactor = 0.1f; 
    private static final float magFilteringFactor = 0.01f; 

    public abstract boolean getIsLandscape(); 

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

     switch (type) { 
      case Sensor.TYPE_MAGNETIC_FIELD: 
       mags[0] = event.values[0] * magFilteringFactor + mags[0] * (1.0f - magFilteringFactor); 
       mags[1] = event.values[1] * magFilteringFactor + mags[1] * (1.0f - magFilteringFactor); 
       mags[2] = event.values[2] * magFilteringFactor + mags[2] * (1.0f - magFilteringFactor); 

       isReady = true; 
       break; 
      case Sensor.TYPE_ACCELEROMETER: 
       accels[0] = event.values[0] * accelFilteringFactor + accels[0] * (1.0f - accelFilteringFactor); 
       accels[1] = event.values[1] * accelFilteringFactor + accels[1] * (1.0f - accelFilteringFactor); 
       accels[2] = event.values[2] * accelFilteringFactor + accels[2] * (1.0f - accelFilteringFactor); 
       break; 

      default: 
       return; 
     } 




     if(mags != null && accels != null && isReady) { 
      isReady = false; 

      SensorManager.getRotationMatrix(rot, inclination, accels, mags); 

      boolean isLandscape = getIsLandscape(); 
      if(isLandscape) { 
       outR = rot; 
      } else { 
       // Remap the coordinates to work in portrait mode. 
       SensorManager.remapCoordinateSystem(rot, SensorManager.AXIS_X, SensorManager.AXIS_Z, outR); 
      } 

      SensorManager.getOrientation(outR, values); 

      double x180pi = 180.0/Math.PI; 
      float azimuth = (float)(values[0] * x180pi); 
      float pitch = (float)(values[1] * x180pi); 
      float roll = (float)(values[2] * x180pi); 

      // In landscape mode swap pitch and roll and invert the pitch. 
      if(isLandscape) { 
       float tmp = pitch; 
       pitch = -roll; 
       roll = -tmp; 
       azimuth = 180 - azimuth; 
      } else { 
       pitch = -pitch - 90; 
       azimuth = 90 - azimuth; 
      } 

      onOrientationChanged(azimuth,pitch,roll); 
     } 
    } 




    private float[] mags = new float[3]; 
    private float[] accels = new float[3]; 
    private boolean isReady; 

    private float[] rot = new float[9]; 
    private float[] outR = new float[9]; 
    private float[] inclination = new float[9]; 
    private float[] values = new float[3]; 



    /** 
    Azimuth: angle between the magnetic north direction and the Y axis, around the Z axis (0 to 359). 0=North, 90=East, 180=South, 270=West 
    Pitch: rotation around X axis (-180 to 180), with positive values when the z-axis moves toward the y-axis. 
    Roll: rotation around Y axis (-90 to 90), with positive values when the x-axis moves toward the z-axis. 
    */ 
    public abstract void onOrientationChanged(float azimuth, float pitch, float roll); 
} 

私はジャイロスコープのデータを追加する方法を見つけようとしましたが、私は正しいことをしていません。 http://developer.android.com/reference/android/hardware/SensorEvent.htmlのGoogleドキュメントには、ジャイロスコープのデータからデルタマトリックスを取得するためのコードがいくつか示されています。私が加速度センサーと磁気センサー用のフィルターをクランクダウンさせ、実際には安定しているように思えます。それは長期的な方向性を追跡するでしょう。

次に、ジャイロスコープの最新のNデルタマトリクスの履歴を保存します。私が新しいものを得るたびに、私は最も古いものを落として、それらをすべて一緒に掛けて、最終マトリックスを得ました。これは加速度計と磁気センサーによって返された安定したマトリックスに対して倍増するでしょう。

これは機能していないようです。少なくとも、私の実装はうまくいきません。結果は加速度計だけではなく、はるかにジッタがあります。ジャイロスコープ履歴のサイズを大きくするとジッタが大きくなり、ジャイロスコープから正しい値を計算していないと思うようになります。

public abstract class SensorsListener3 
    implements 
     SensorEventListener 
{ 
    /** The lower this is, the greater the preference which is given to previous values. (slows change) */ 
    private static final float kFilteringFactor = 0.001f; 
    private static final float magKFilteringFactor = 0.001f; 


    public abstract boolean getIsLandscape(); 

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

     switch (type) { 
      case Sensor.TYPE_MAGNETIC_FIELD: 
       mags[0] = event.values[0] * magKFilteringFactor + mags[0] * (1.0f - magKFilteringFactor); 
       mags[1] = event.values[1] * magKFilteringFactor + mags[1] * (1.0f - magKFilteringFactor); 
       mags[2] = event.values[2] * magKFilteringFactor + mags[2] * (1.0f - magKFilteringFactor); 

       isReady = true; 
       break; 
      case Sensor.TYPE_ACCELEROMETER: 
       accels[0] = event.values[0] * kFilteringFactor + accels[0] * (1.0f - kFilteringFactor); 
       accels[1] = event.values[1] * kFilteringFactor + accels[1] * (1.0f - kFilteringFactor); 
       accels[2] = event.values[2] * kFilteringFactor + accels[2] * (1.0f - kFilteringFactor); 
       break; 

      case Sensor.TYPE_GYROSCOPE: 
       gyroscopeSensorChanged(event); 
       break; 

      default: 
       return; 
     } 




     if(mags != null && accels != null && isReady) { 
      isReady = false; 

      SensorManager.getRotationMatrix(rot, inclination, accels, mags); 

      boolean isLandscape = getIsLandscape(); 
      if(isLandscape) { 
       outR = rot; 
      } else { 
       // Remap the coordinates to work in portrait mode. 
       SensorManager.remapCoordinateSystem(rot, SensorManager.AXIS_X, SensorManager.AXIS_Z, outR); 
      } 

      if(gyroUpdateTime!=0) { 
       matrixHistory.mult(matrixTmp,matrixResult); 
       outR = matrixResult; 
      } 

      SensorManager.getOrientation(outR, values); 

      double x180pi = 180.0/Math.PI; 
      float azimuth = (float)(values[0] * x180pi); 
      float pitch = (float)(values[1] * x180pi); 
      float roll = (float)(values[2] * x180pi); 

      // In landscape mode swap pitch and roll and invert the pitch. 
      if(isLandscape) { 
       float tmp = pitch; 
       pitch = -roll; 
       roll = -tmp; 
       azimuth = 180 - azimuth; 
      } else { 
       pitch = -pitch - 90; 
       azimuth = 90 - azimuth; 
      } 

      onOrientationChanged(azimuth,pitch,roll); 
     } 
    } 



    private void gyroscopeSensorChanged(SensorEvent event) { 
     // This timestep's delta rotation to be multiplied by the current rotation 
     // after computing it from the gyro sample data. 
     if(gyroUpdateTime != 0) { 
      final float dT = (event.timestamp - gyroUpdateTime) * NS2S; 
      // Axis of the rotation sample, not normalized yet. 
      float axisX = event.values[0]; 
      float axisY = event.values[1]; 
      float axisZ = event.values[2]; 

      // Calculate the angular speed of the sample 
      float omegaMagnitude = (float)Math.sqrt(axisX*axisX + axisY*axisY + axisZ*axisZ); 

      // Normalize the rotation vector if it's big enough to get the axis 
      if(omegaMagnitude > EPSILON) { 
       axisX /= omegaMagnitude; 
       axisY /= omegaMagnitude; 
       axisZ /= omegaMagnitude; 
      } 

      // Integrate around this axis with the angular speed by the timestep 
      // in order to get a delta rotation from this sample over the timestep 
      // We will convert this axis-angle representation of the delta rotation 
      // into a quaternion before turning it into the rotation matrix. 
      float thetaOverTwo = omegaMagnitude * dT/2.0f; 
      float sinThetaOverTwo = (float)Math.sin(thetaOverTwo); 
      float cosThetaOverTwo = (float)Math.cos(thetaOverTwo); 
      deltaRotationVector[0] = sinThetaOverTwo * axisX; 
      deltaRotationVector[1] = sinThetaOverTwo * axisY; 
      deltaRotationVector[2] = sinThetaOverTwo * axisZ; 
      deltaRotationVector[3] = cosThetaOverTwo; 
     } 
     gyroUpdateTime = event.timestamp; 
     SensorManager.getRotationMatrixFromVector(deltaRotationMatrix, deltaRotationVector); 
     // User code should concatenate the delta rotation we computed with the current rotation 
     // in order to get the updated rotation. 
     // rotationCurrent = rotationCurrent * deltaRotationMatrix; 
     matrixHistory.add(deltaRotationMatrix); 
    } 



    private float[] mags = new float[3]; 
    private float[] accels = new float[3]; 
    private boolean isReady; 

    private float[] rot = new float[9]; 
    private float[] outR = new float[9]; 
    private float[] inclination = new float[9]; 
    private float[] values = new float[3]; 

    // gyroscope stuff 
    private long gyroUpdateTime = 0; 
    private static final float NS2S = 1.0f/1000000000.0f; 
    private float[] deltaRotationMatrix = new float[9]; 
    private final float[] deltaRotationVector = new float[4]; 
//TODO: I have no idea how small this value should be. 
    private static final float EPSILON = 0.000001f; 
    private float[] matrixMult = new float[9]; 
    private MatrixHistory matrixHistory = new MatrixHistory(100); 
    private float[] matrixTmp = new float[9]; 
    private float[] matrixResult = new float[9]; 


    /** 
    Azimuth: angle between the magnetic north direction and the Y axis, around the Z axis (0 to 359). 0=North, 90=East, 180=South, 270=West 
    Pitch: rotation around X axis (-180 to 180), with positive values when the z-axis moves toward the y-axis. 
    Roll: rotation around Y axis (-90 to 90), with positive values when the x-axis moves toward the z-axis. 
    */ 
    public abstract void onOrientationChanged(float azimuth, float pitch, float roll); 
} 


public class MatrixHistory 
{ 
    public MatrixHistory(int size) { 
     vals = new float[size][]; 
    } 

    public void add(float[] val) { 
     synchronized(vals) { 
      vals[ix] = val; 
      ix = (ix + 1) % vals.length; 
      if(ix==0) 
       full = true; 
     } 
    } 

    public void mult(float[] tmp, float[] output) { 
     synchronized(vals) { 
      if(full) { 
       for(int i=0; i<vals.length; ++i) { 
        if(i==0) { 
         System.arraycopy(vals[i],0,output,0,vals[i].length); 
        } else { 
         MathUtils.multiplyMatrix3x3(output,vals[i],tmp); 
         System.arraycopy(tmp,0,output,0,tmp.length); 
        } 
       } 
      } else { 
       if(ix==0) 
        return; 
       for(int i=0; i<ix; ++i) { 
        if(i==0) { 
         System.arraycopy(vals[i],0,output,0,vals[i].length); 
        } else { 
         MathUtils.multiplyMatrix3x3(output,vals[i],tmp); 
         System.arraycopy(tmp,0,output,0,tmp.length); 
        } 
       } 
      } 
     } 
    } 


    private int ix = 0; 
    private boolean full = false; 
    private float[][] vals; 
} 

コードの2番目のブロックには、ジャイロスコープをミックスに追加するコードの最初のブロックからの変更が含まれています。

具体的には、accelのフィルタ係数を小さくする(値をより安定させる)。 MatrixHistoryクラスは、gyroscopeSensorChangedメソッドで計算された最後の100個のジャイロスコープdeltaRotationMatrix値を追跡します。

このトピックについては、このサイトで多くの質問がありました。彼らは私がこの時点まで手伝ってくれましたが、次に何をすべきかを理解することはできません。私は本当にセンサーフュージョンの男がちょうどどこかにいくつかのコードを投稿したことを望みます。彼は明らかにそれをすべてまとめました。

+2

"Professional Android Sensor Programming"の本によると、InvenSenseのSensor Fusionアルゴリズムは独自のものであるため、パブリックアクセスでソースコードを見つけることはほとんど不可能です。 SENSOR.TYPE_ROTATIONはすでにジャイロベースの短時間訂正に関する測定値を提供するため、ライブラリはシステムレベルの最新のデバイスに含まれています。私は、この問題の最も精巧な公的情報源は[this](http://www.thousand-thoughts.com/2012/03/android-sensor-fusion-tutorial/)だと思う。それが良い代替品かどうかはわかりません。 – Stan

+0

カルマンフィルタを用いたセンサ融合に関連する学術論文がいくつかあります。通常、ソースコードは含まれていませんが、必要な技術的および数学的な詳細が必要です。 [scholar.google.com](http://scholar.google.com/scholar?q=kalman+gyroscope) – zapl

+0

なぜ磁気値をローパスフィルタリングしていますか? –

答えて

47

カルマンフィルタが何であるか知っていても、あなたに+1してください。あなたが望むなら、私はこの投稿を編集し、あなたが何をしようとしているのかについて数年前に書いたコードを提供します。

しかし、まず、あなたがそれを必要としない理由を教えてください。

最新のAndroidセンサースタックの実装では、上記のStanとしてSensor Fusionを使用しています。これは、使用可能なすべてのデータ(accel、mag、gyro)が1つのアルゴリズムでまとめられていることを意味し、すべての出力がAndroidセンサーの形で読み出されます。

編集:この素晴らしいGoogle Tech Talk:Sensor Fusion on Android Devices: A Revolution in Motion Processingを見つけたばかりです。トピックに興味があるなら、それを見るのに45分の価値があります。

本質的に、Sensor Fusionはブラックボックスです。私はAndroid実装のソースコードを調べました。これはC++で書かれた大きなカルマンフィルタです。そこにはかなり良いコードがあり、私が書いたフィルターよりもはるかに洗練されていて、おそらくあなたが書いていることがもっと洗練されているでしょう。覚えておいて、これらの人は生計のためにこれをやっています。

また、少なくとも1つのチップセットメーカーが独自のセンサーフュージョンを実装していることも知っています。デバイスの製造元は、Androidとベンダー実装の間で、独自の基準に基づいて選択します。

最後に、上記のStanとして、Invensenseは、チップレベルで独自のセンサー融合実装を備えています。

とにかく、あなたのデバイスに内蔵されているセンサーフュージョンは、あなたや私が一緒に凝縮できるものよりも優れている可能性があります。だからあなたは本当ににアクセスしたいと思っています。

Androidには、物理​​センサーと仮想センサーの両方があります。仮想センサーは、利用可能な物理センサーから合成されたセンサーです。最もよく知られている例は、加速度計と磁力計を取り、ロール/ピッチ/ヘッディング出力を生成するTYPE_ORIENTATIONです。 (ところで、あなたはこのセンサーを使用してはならない。それはあまりにも多くの制限があります。)

しかし重要なことは、Androidの新しいバージョンは、これら2つの新しい仮想センサーを含むことである:

TYPE_GRAVITYが持つ加速度入力でありますフィルタリングされたモーションの影響 TYPE_LINEAR_ACCELERATIONは、重力コンポーネントが除外された加速度計です。

これらの2つの仮想センサは、加速度計入力とジャイロ入力の組み合わせによって合成されます。

もう1つ注目すべきセンサーは、加速度計、磁力計、ジャイロから合成されたクォータニオンであるTYPE_ROTATION_VECTORです。これは、線形加速度の影響を除外したデバイスの完全な3次元姿勢を表します。

しかし、クォータニオンはほとんどの人にとってはやや抽象的なものですが、とにかく3-D変換に取り組んでいる可能性が高いので、TYPE_GRAVITYとTYPE_MAGNETIC_FIELDをSensorManager.getRotationMatrix()で結合するのが最良の方法です。

もう1つポイント:古いバージョンのAndroid搭載デバイスを使用している場合は、TYPE_GRAVITYイベントを受信して​​いないことを検出し、代わりにTYPE_ACCELEROMETERを使用する必要があります。理論的には、これはあなた自身のカルマンフィルタを使う場所ですが、もしあなたのデバイスにセンサー融合が組み込まれていなければ、おそらくジャイロもありません。

とにかく、私のやり方を示すサンプルコードがあります。

// Requires 1.5 or above 

    class Foo extends Activity implements SensorEventListener { 

    SensorManager sensorManager; 
    float[] gData = new float[3];   // Gravity or accelerometer 
    float[] mData = new float[3];   // Magnetometer 
    float[] orientation = new float[3]; 
    float[] Rmat = new float[9]; 
    float[] R2 = new float[9]; 
    float[] Imat = new float[9]; 
    boolean haveGrav = false; 
    boolean haveAccel = false; 
    boolean haveMag = false; 

    onCreate() { 
     // Get the sensor manager from system services 
     sensorManager = 
      (SensorManager)getSystemService(Context.SENSOR_SERVICE); 
    } 

    onResume() { 
     super.onResume(); 
     // Register our listeners 
     Sensor gsensor = sensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY); 
     Sensor asensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); 
     Sensor msensor = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD); 
     sensorManager.registerListener(this, gsensor, SensorManager.SENSOR_DELAY_GAME); 
     sensorManager.registerListener(this, asensor, SensorManager.SENSOR_DELAY_GAME); 
     sensorManager.registerListener(this, msensor, SensorManager.SENSOR_DELAY_GAME); 
    } 

    public void onSensorChanged(SensorEvent event) { 
     float[] data; 
     switch(event.sensor.getType()) { 
      case Sensor.TYPE_GRAVITY: 
      gData[0] = event.values[0]; 
      gData[1] = event.values[1]; 
      gData[2] = event.values[2]; 
      haveGrav = true; 
      break; 
      case Sensor.TYPE_ACCELEROMETER: 
      if (haveGrav) break; // don't need it, we have better 
      gData[0] = event.values[0]; 
      gData[1] = event.values[1]; 
      gData[2] = event.values[2]; 
      haveAccel = true; 
      break; 
      case Sensor.TYPE_MAGNETIC_FIELD: 
      mData[0] = event.values[0]; 
      mData[1] = event.values[1]; 
      mData[2] = event.values[2]; 
      haveMag = true; 
      break; 
      default: 
      return; 
     } 

     if ((haveGrav || haveAccel) && haveMag) { 
      SensorManager.getRotationMatrix(Rmat, Imat, gData, mData); 
      SensorManager.remapCoordinateSystem(Rmat, 
        SensorManager.AXIS_Y, SensorManager.AXIS_MINUS_X, R2); 
      // Orientation isn't as useful as a rotation matrix, but 
      // we'll show it here anyway. 
      SensorManager.getOrientation(R2, orientation); 
      float incl = SensorManager.getInclination(Imat); 
      Log.d(TAG, "mh: " + (int)(orientation[0]*DEG)); 
      Log.d(TAG, "pitch: " + (int)(orientation[1]*DEG)); 
      Log.d(TAG, "roll: " + (int)(orientation[2]*DEG)); 
      Log.d(TAG, "yaw: " + (int)(orientation[0]*DEG)); 
      Log.d(TAG, "inclination: " + (int)(incl*DEG)); 
     } 
     } 
    } 

Hmmm;もしあなたがQuaternionライブラリを手に入れたら、TYPE_ROTATION_VECTORを受け取ってそれを配列に変換するほうが簡単かもしれません。

+0

ネイティブ側でこのデータにアクセスする可能性はありますか?ほとんどのものはsensor.hのNDKを経由して公開されていません – Max

+0

これはすべて正常に動作しましたか、私のところはまだまだ不安定で、ローパスフィルタがあります – Burf2000

+0

こんにちはエドワード、これは私が見つけようとしているしばらくの間。 'SensorManager.remapCoordinateSystem'を使用する必要がある理由を教えてください。ありがとう。 –

5

ここで完全なコードを見つけるには、Androidのゼリービーンのデフォルトの実装があります:https://android.googlesource.com/platform/frameworks/base/+/jb-release/services/sensorservice/ fusion.cpp/hを確認してください。 クォータニオンの代わりに修正ロドリゲスパラメータ(オイラー角に近い)を使用します。オリエンテーションに加えて、カルマンフィルタはジャイロのドリフトを推定します。測定値の更新では、磁力計を使用し、誤って少し加速(特定の力)を使用します。

コードを使用するには、ウィザードまたはINSとKFの基本を理解する必要があります。フィルターが機能するには、多くのパラメーターを微調整する必要があります。エドワードが適切に置くように、これらの人たちは生計のためにこれをやっています。

少なくともGoogleの銀河系のネクサスでは、このデフォルトの実装は未使用のままであり、Invense独自のシステムによってオーバーライドされています。

関連する問題