スムーズなGPSデータ


145

私はGPSデータを処理しており、毎秒値を取得し、地図上に現在位置を表示しています。問題は、(特に精度が低い場合)値が大きく変動し、現在の位置がマップ内の離れたポイント間で「ジャンプ」することです。

これを回避するのに十分簡単な方法を考えていました。最初のアイデアとして、特定のしきい値を超えて正確に値を破棄することを考えましたが、他にもっと良い方法があると思います。プログラムがこれを実行する通常の方法は何ですか?


速度や勾配などの関連する(微分)値を計算しようとすると、「GPSノイズ」の悪影響が感じられます。これは、特にサンプルレートの高いトラックログでは非常に不連続です(時間には整数[1秒]の解像度があるため)。
heltonbiker 2012

4
(また、幹線道路をナビゲートしている場合は、適切な[正確で正確な]ロードマップデータセットがあれば、「道路までスナップ」アルゴリズムを使用できます。考えてみてください)
heltonbiker

精度を上げるためにもこの問題に直面しています。
ViruMax

回答:


80

これは、まさにこの状況に使用できる単純なカルマンフィルターです。これは、Androidデバイスで行ったいくつかの作業によるものです。

一般的なカルマンフィルター理論はすべてベクトルの推定に関するもので、推定の精度は共分散行列で表されます。ただし、Androidデバイスで位置を推定する場合、一般的な理論は非常に単純なケースに還元されます。Androidロケーションプロバイダーは、緯度と経度、およびメートル単位で測定される単一の数値として指定される精度で場所を提供します。つまり、カルマンフィルターの位置は2つの数値で測定されますが、共分散行列の代わりに、カルマンフィルターの精度を単一の数値で測定できます。また、緯度、経度、メートルが事実上すべての異なる単位であるという事実は無視できます。スケーリング係数をカルマンフィルターに入れてすべてを同じ単位に変換すると、

現在の場所の最良の推定は最後に既知の場所であると想定しているため、コードを改善できます。誰かが動いている場合は、Androidのセンサーを使用してより正確な推定を生成できるはずです。このコードには、1秒あたりのメートルで表される単一の自由パラメーターQがあり、新しい位置推定が存在しない場合に精度がどれだけ速く減衰するかを示します。Qパラメータが高いほど、精度が速く減衰することを意味します。カルマンフィルターは、通常、精度が予想よりも少し速く減衰する場合に良好に機能します。そのため、Androidフォンで歩き回る場合、通常はゆっくりと歩きますが、Q = 3メートル/秒でうまくいきます。ただし、高速の車で移動する場合は、明らかにより多くの数を使用する必要があります。

public class KalmanLatLong {
    private final float MinAccuracy = 1;

    private float Q_metres_per_second;    
    private long TimeStamp_milliseconds;
    private double lat;
    private double lng;
    private float variance; // P matrix.  Negative means object uninitialised.  NB: units irrelevant, as long as same units used throughout

    public KalmanLatLong(float Q_metres_per_second) { this.Q_metres_per_second = Q_metres_per_second; variance = -1; }

    public long get_TimeStamp() { return TimeStamp_milliseconds; }
    public double get_lat() { return lat; }
    public double get_lng() { return lng; }
    public float get_accuracy() { return (float)Math.sqrt(variance); }

    public void SetState(double lat, double lng, float accuracy, long TimeStamp_milliseconds) {
        this.lat=lat; this.lng=lng; variance = accuracy * accuracy; this.TimeStamp_milliseconds=TimeStamp_milliseconds;
    }

    /// <summary>
    /// Kalman filter processing for lattitude and longitude
    /// </summary>
    /// <param name="lat_measurement_degrees">new measurement of lattidude</param>
    /// <param name="lng_measurement">new measurement of longitude</param>
    /// <param name="accuracy">measurement of 1 standard deviation error in metres</param>
    /// <param name="TimeStamp_milliseconds">time of measurement</param>
    /// <returns>new state</returns>
    public void Process(double lat_measurement, double lng_measurement, float accuracy, long TimeStamp_milliseconds) {
        if (accuracy < MinAccuracy) accuracy = MinAccuracy;
        if (variance < 0) {
            // if variance < 0, object is unitialised, so initialise with current values
            this.TimeStamp_milliseconds = TimeStamp_milliseconds;
            lat=lat_measurement; lng = lng_measurement; variance = accuracy*accuracy; 
        } else {
            // else apply Kalman filter methodology

            long TimeInc_milliseconds = TimeStamp_milliseconds - this.TimeStamp_milliseconds;
            if (TimeInc_milliseconds > 0) {
                // time has moved on, so the uncertainty in the current position increases
                variance += TimeInc_milliseconds * Q_metres_per_second * Q_metres_per_second / 1000;
                this.TimeStamp_milliseconds = TimeStamp_milliseconds;
                // TO DO: USE VELOCITY INFORMATION HERE TO GET A BETTER ESTIMATE OF CURRENT POSITION
            }

            // Kalman gain matrix K = Covarariance * Inverse(Covariance + MeasurementVariance)
            // NB: because K is dimensionless, it doesn't matter that variance has different units to lat and lng
            float K = variance / (variance + accuracy * accuracy);
            // apply K
            lat += K * (lat_measurement - lat);
            lng += K * (lng_measurement - lng);
            // new Covarariance  matrix is (IdentityMatrix - K) * Covarariance 
            variance = (1 - K) * variance;
        }
    }
}

1
分散計算は次のようにするべきではありません:分散+ = TimeInc_milliseconds * TimeInc_milliseconds * Q_metres_per_second * Q_metres_per_second / 1000000
Horacio

4
@Horacio、私はあなたがなぜそう思うのか知っていますが、違います!数学的には、ここでの不確実性は、ウィーナープロセス(en.wikipedia.org/wiki/Wiener_processを参照)によってモデル化されており、ウィーナープロセスでは、分散は時間とともに直線的に増加します。Q_metres_per_second変数sigmaは、Wikipediaの記事の「関連プロセス」セクションの変数に対応しています。 Q_metres_per_secondは標準偏差であり、メートルで測定されるため、メートルと秒ではなくメートルがその単位です。1秒経過後の分布の標準偏差に相当します。
確率論的に2013

3
このアプローチとコードを試してみましたが、最終的に合計距離が短くなりすぎました。不正確にしすぎました。
Andreas Rudolph

1
@ user2999943はい、コードを使用して、onLocationChanged()から取得した座標を処理します。
確率的に

2
@Koray精度情報がない場合は、カルマンフィルターを使用できません。これは、カルマンフィルターが実行しようとしていることの完全に基本です。
確率的に

75

あなたが探しているものはカルマンフィルターと呼ばれていますナビゲーションデータスムーズにするためによく使用されます。それは必ずしも取るに足らないことではなく、あなたができる多くのチューニングがありますが、それは非常に標準的なアプローチであり、うまく機能します。C ++実装であるKFilterライブラリが利用可能です。

私の次のフォールバックは、最小二乗適合です。カルマンフィルターは速度を考慮に入れてデータを平滑化しますが、最小二乗近似アプローチは位置情報のみを使用します。それでも、実装と理解は間違いなく簡単です。GNU Scientific Libraryがこれを実装しているようです。


1
クリス、ありがとう。はい、検索中にカルマンについて読みましたが、確かに私の数学の知識を少し超えています。読みやすい(そして理解しやすい)サンプルコードを知っていますか?(C / C ++ / Java)
Al。

1
@Al残念ながら、カルマンフィルターでの私の唯一の露出は仕事を通してです、それで私はあなたに示すことができない素晴らしくエレガントなコードがあります。
Chris Arguin、

問題ありません:-)探してみましたが、何らかの理由でこのカルマンのものは黒魔術のようです。理論ページはたくさんありますが、コードはほとんどありません。おかげで、他の方法を試してみます。
アル。

2
kalman.sourceforge.net/index.phpは、カルマンフィルターのC ++実装です。
Rostyslav Druzhchenko 2014

1
@ChrisArguinどういたしまして 結果が良かったら教えてください。
Rostyslav Druzhchenko 2014

11

これは少し遅くなるかもしれません...

私はこのKalmanLocationManager for Androidを作成しました。これは、ネットワークとGPSの最も一般的な2つの位置プロバイダーをラップし、データをカルマンフィルターして、LocationListener(2つの「実際の」プロバイダーのように)更新を配信します。

私は主に、読み取り値の間を「補間」するために使用します。たとえば、1秒の最大gpsレートではなく、たとえば100ミリ秒ごとに更新(位置予測)を受信するためです。

実際には、緯度、経度、高度の各次元で3つのカルマンフィルターを使用します。とにかく、彼らは独立しています。

これにより、行列の計算がはるかに簡単になります。1つの6x6状態遷移行列を使用する代わりに、3つの異なる2x2行列を使用します。実際、コードでは、マトリックスをまったく使用していません。すべての方程式を解き、すべての値はプリミティブ(double)です。

ソースコードは機能しており、デモ活動があります。一部の場所にjavadocがないため申し訳ありませんが、追いつきます。


1
私はあなたのlibコードを使用しようとしました、私はいくつかの望ましくない結果を得ました、私が何か間違っているのかわかりません... com / s / w3uvaz007glp2utvgznmh8vlggvaiifk
umesh

平均値(オレンジ色の線)から「増加」しているスパイクは、ネットワークプロバイダーの更新のように見えます。生のネットワークとgpsの両方の更新をプロットしてみることはできますか?おそらく、達成しようとしていることに応じて、ネットワークを更新しない方がよいでしょう。ところで、どこから生のオレンジ色のアップデートを入手していますか?
villoren

1
オレンジ色のポイントはgpsプロバイダーから、青色はカルマンからのものです。地図にログをプロットしました
umesh

そのデータをテキスト形式で送っていただけませんか。場所の更新ごとに、Location.getProvider()フィールドが設定されます。すべてのLocation.toString()を含む1つのファイル。
villoren 2015

9

時間ごとの位置変化から速度を計算するべきではありません。GPSの位置は不正確ですが、速度は正確です(5 km / h以上)。したがって、GPSロケーションスタンプの速度を使用します。そして、それはほとんどの場合機能しますが、コースでそれを行うべきではありません。

配信されたGPS位置はすでにカルマンフィルター処理されているため、おそらく改善できません。通常、後処理ではGPSチップのような同じ情報はありません。

あなたはそれを滑らかにすることができますが、これはまたエラーを引き起こします。

デバイスが静止しているときに位置を削除することを確認してください。これにより、一部のデバイス/構成が削除されないジャンプ位置が削除されます。


5
これについていくつかの参考資料を提供していただけますか?
ivyleavedtoadflax 2015

1
その文章には多くの情報と専門的な経験があります。どの文章を参考にしたいですか?速度について:ドップラー効果とGPSを検索します。内部カルマン?これは、基本的なGPSの知識であり、GPSチップが内部でどのように機能するかを説明するすべての紙または本です。smootig-errors:常に平滑化するとエラーが発生します。じっとしてる?やってみよう。
AlexWien

2
エラーが発生するのは、静止したときの「飛び跳ねる」だけではありません。また、位置がジャンプする信号反射(山など)もあります。私のGPSチップ(Garmin Dakota 20、SonyEricsson Neoなど)はこれをフィルターにかけませんでした...そして、冗談は、気圧と組み合わされていないときのGPS信号の標高値です。これらの値はフィルタリングされていないか、フィルタリングされていない値を表示したくありません。
hgoebl 2015

1
@AlexWien GPSは、ある時点から許容範囲までの距離を計算して、衛星を中心とする厚さのある球体、シェルを提供します。あなたはこのシェルボリュームのどこかにいます。これらの3つのシェルボリュームの交点により、位置ボリュームが得られます。その重心は計算された位置です。レポートされた位置のセットがあり、センサーが静止していることがわかっている場合、重心の計算はより多くのシェルと効果的に交差し、精度が向上します。この場合のエラーは減少します。
Peter Wone 2017

6
「提供されたGPS位置はすでにカルマンフィルター処理されているため、おそらく改善できません」たとえば、最近のスマートフォンでこれを確認できるソースを指定できる場合、それは非常に便利です。私自身はその証拠を見ることができません。デバイスの生の場所の単純なカルマンフィルタリングでさえ、それが真実ではないことを強く示唆しています。生の場所は不規則に踊りますが、フィルタリングされた場所はほとんどの場合、実際の(既知の)場所に近くなります。
sobri

6

私は通常、加速度計を使用します。短期間の急激な位置の変化は、高い加速を意味します。これが加速度計テレメトリに反映されていない場合は、位置の計算に使用される「ベスト3」衛星の変更(これをGPSテレポーティングと呼びます)が原因であると考えられます。

資産が静止していて、GPSテレポートのために飛び回っている場合、重心を徐々に計算すると、ますます大きなシェルのセットと効果的に交差し、精度が向上します。

アセットが静止していないときにこれを行うには、速度、方向、線形および回転(ジャイロがある場合)の加速度データに基づいて、次の位置と方向を推定する必要があります。これは多かれ少なかれ、有名なKフィルターが行うことです。GPSモジュール以外のすべてを含み、1つを接続するためのジャックを備えたAHRSでは、約150ドルですべてをハードウェアで入手できます。独自のCPUとカルマンフィルターを搭載しています。結果は安定しており、非常に良好です。慣性ガイダンスはジッタに対して非常に耐性がありますが、時間とともにドリフトします。GPSはジッターを起こしやすいですが、時間とともにドリフトすることはなく、実際にはお互いを補償するために作成されました。


4

より少ない数学/理論を使用する1つの方法は、一度に2、5、7、または10のデータポイントをサンプリングし、外れ値であるデータポイントを決定することです。カルマンフィルターよりも正確でない外れ値の測定は、次のアルゴリズムを使用することです。をポイント間のすべてのペアワイズ距離を取得し、他から最も遠いものを捨てることです。通常、これらの値は、置換する範囲外の値に最も近い値に置き換えられます

例えば

5つのサンプルポイントA、B、C、D、Eでの平滑化

ATOTAL =距離の合計AB AC AD AE

BTOTAL =距離の合計AB BC BD BE

CTOTAL =距離の合計AC BC CD CE

DTOTAL =距離の合計DA DB DC DE

ETOTAL =距離の合計EA EB EC DE

BTOTALが最大の場合、BD = min {AB、BC、BD、BE}の場合、ポイントBをDに置き換えます。

この平滑化は外れ値を決定し、位置線を平滑化するために点Dの代わりにBDの中点を使用して拡張できます。あなたの走行距離は異なる場合があり、より数学的に厳密なソリューションが存在します。


ありがとう、私も試してみます。現在の位置は表示されているものであり、一部のデータを取得するために使用されるものであるため、現在の位置を滑らかにしたいことに注意してください。過去のポイントには興味がありません。私の最初のアイデアは加重平均を使用していましたが、それでも何が最良かを確認する必要があります。
アル。

1
Al、これは加重平均の形式のようです。平滑化を行う場合は、「過去の」ポイントを使用する必要があります。平滑化する場所もシステムが知るには、現在の位置よりも多くの位置が必要だからです。GPSが1秒に1回データポイントを取得していて、ユーザーが5秒に1回画面を見る場合、ユーザーが気付かないうちに5つのデータポイントを使用できます。移動平均も1 dpだけ遅れます。
Karl、

4

最小二乗法については、他にいくつか試してみる必要があります。

  1. 最小二乗適合であっても、線形である必要はありません。2次曲線をデータに最小二乗近似すると、ユーザーが加速しているシナリオに適合します。(最小二乗法とは、従属変数として座標を使用し、独立変数として時間を使用することを意味します。)

  2. レポートされた精度に基づいてデータポイントに重みを付けることもできます。精度が低い場合、これらのデータポイントは低くなります。

  3. 試してみたいもう1つのことは、単一の点を表示することではなく、精度が低い場合は、円またはユーザーが報告された精度に基づく可能性のある範囲を示す何かを表示します。(これは、iPhoneの組み込みGoogleマップアプリケーションが行うことです。)


3

スプラインを使用することもできます。持っている値を入力し、既知のポイント間のポイントを補間します。これを最小二乗フィット、移動平均、またはカルマンフィルター(他の回答で言及されている)とリンクすると、「既知の」ポイント間のポイントを計算できます。

既知の値の間で値を補間できることで、スムーズな移行と、より忠実度が高い場合にどのデータが存在するかについての/合理的な/近似が得られます。http://en.wikipedia.org/wiki/Spline_interpolation

スプラインごとに特性が異なります。最も一般的に使用されているのは、AkimaスプラインとCubicスプラインです。

考慮すべきもう1つのアルゴリズムは、Ramer-Douglas-Peuckerライン単純化アルゴリズムです。これは、GPSデータの単純化で非常に一般的に使用されています。(http://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm



0

興味のある方は、CoffeeScriptにマッピングしてください。**編集->バックボーンを使用して申し訳ありませんが、あなたはアイデアを得ます。

属性付きのビーコンを受け入れるように少し変更

{緯度:item.lat、経度:item.lng、日付:新しい日付(item.effective_at)、精度:item.gps_accuracy}

MIN_ACCURACY = 1

# mapped from http://stackoverflow.com/questions/1134579/smooth-gps-data

class v.Map.BeaconFilter

  constructor: ->
    _.extend(this, Backbone.Events)

  process: (decay,beacon) ->

    accuracy     = Math.max beacon.accuracy, MIN_ACCURACY

    unless @variance?
      # if variance nil, inititalise some values
      @variance     = accuracy * accuracy
      @timestamp_ms = beacon.date.getTime();
      @lat          = beacon.latitude
      @lng          = beacon.longitude

    else

      @timestamp_ms = beacon.date.getTime() - @timestamp_ms

      if @timestamp_ms > 0
        # time has moved on, so the uncertainty in the current position increases
        @variance += @timestamp_ms * decay * decay / 1000;
        @timestamp_ms = beacon.date.getTime();

      # Kalman gain matrix K = Covarariance * Inverse(Covariance + MeasurementVariance)
      # NB: because K is dimensionless, it doesn't matter that variance has different units to lat and lng
      _k  = @variance / (@variance + accuracy * accuracy)
      @lat = _k * (beacon.latitude  - @lat)
      @lng = _k * (beacon.longitude - @lng)

      @variance = (1 - _k) * @variance

    [@lat,@lng]

これを編集しようとしましたが、最後の行に@lat@lngが設定されているタイプミスがあります。ではないはず+=です=
jdixon04

0

Javaコードを@StochasticallyからKotlinに変換しました

class KalmanLatLong
{
    private val MinAccuracy: Float = 1f

    private var Q_metres_per_second: Float = 0f
    private var TimeStamp_milliseconds: Long = 0
    private var lat: Double = 0.toDouble()
    private var lng: Double = 0.toDouble()
    private var variance: Float =
        0.toFloat() // P matrix.  Negative means object uninitialised.  NB: units irrelevant, as long as same units used throughout

    fun KalmanLatLong(Q_metres_per_second: Float)
    {
        this.Q_metres_per_second = Q_metres_per_second
        variance = -1f
    }

    fun get_TimeStamp(): Long { return TimeStamp_milliseconds }
    fun get_lat(): Double { return lat }
    fun get_lng(): Double { return lng }
    fun get_accuracy(): Float { return Math.sqrt(variance.toDouble()).toFloat() }

    fun SetState(lat: Double, lng: Double, accuracy: Float, TimeStamp_milliseconds: Long)
    {
        this.lat = lat
        this.lng = lng
        variance = accuracy * accuracy
        this.TimeStamp_milliseconds = TimeStamp_milliseconds
    }

    /// <summary>
    /// Kalman filter processing for lattitude and longitude
    /// /programming/1134579/smooth-gps-data/15657798#15657798
    /// </summary>
    /// <param name="lat_measurement_degrees">new measurement of lattidude</param>
    /// <param name="lng_measurement">new measurement of longitude</param>
    /// <param name="accuracy">measurement of 1 standard deviation error in metres</param>
    /// <param name="TimeStamp_milliseconds">time of measurement</param>
    /// <returns>new state</returns>
    fun Process(lat_measurement: Double, lng_measurement: Double, accuracy: Float, TimeStamp_milliseconds: Long)
    {
        var accuracy = accuracy
        if (accuracy < MinAccuracy) accuracy = MinAccuracy

        if (variance < 0)
        {
            // if variance < 0, object is unitialised, so initialise with current values
            this.TimeStamp_milliseconds = TimeStamp_milliseconds
            lat = lat_measurement
            lng = lng_measurement
            variance = accuracy * accuracy
        }
        else
        {
            // else apply Kalman filter methodology

            val TimeInc_milliseconds = TimeStamp_milliseconds - this.TimeStamp_milliseconds

            if (TimeInc_milliseconds > 0)
            {
                // time has moved on, so the uncertainty in the current position increases
                variance += TimeInc_milliseconds.toFloat() * Q_metres_per_second * Q_metres_per_second / 1000
                this.TimeStamp_milliseconds = TimeStamp_milliseconds
                // TO DO: USE VELOCITY INFORMATION HERE TO GET A BETTER ESTIMATE OF CURRENT POSITION
            }

            // Kalman gain matrix K = Covarariance * Inverse(Covariance + MeasurementVariance)
            // NB: because K is dimensionless, it doesn't matter that variance has different units to lat and lng
            val K = variance / (variance + accuracy * accuracy)
            // apply K
            lat += K * (lat_measurement - lat)
            lng += K * (lng_measurement - lng)
            // new Covarariance  matrix is (IdentityMatrix - K) * Covarariance
            variance = (1 - K) * variance
        }
    }
}

0

@StochasticallyのJava実装のJavascript実装は、それを必要とするすべての人を対象としています。

class GPSKalmanFilter {
  constructor (decay = 3) {
    this.decay = decay
    this.variance = -1
    this.minAccuracy = 1
  }

  process (lat, lng, accuracy, timestampInMs) {
    if (accuracy < this.minAccuracy) accuracy = this.minAccuracy

    if (this.variance < 0) {
      this.timestampInMs = timestampInMs
      this.lat = lat
      this.lng = lng
      this.variance = accuracy * accuracy
    } else {
      const timeIncMs = timestampInMs - this.timestampInMs

      if (timeIncMs > 0) {
        this.variance += (timeIncMs * this.decay * this.decay) / 1000
        this.timestampInMs = timestampInMs
      }

      const _k = this.variance / (this.variance + (accuracy * accuracy))
      this.lat += _k * (lat - this.lat)
      this.lng += _k * (lng - this.lng)

      this.variance = (1 - _k) * this.variance
    }

    return [this.lng, this.lat]
  }
}

使用例:

   const kalmanFilter = new GPSKalmanFilter()
   const updatedCoords = []

    for (let index = 0; index < coords.length; index++) {
      const { lat, lng, accuracy, timestampInMs } = coords[index]
      updatedCoords[index] = kalmanFilter.process(lat, lng, accuracy, timestampInMs)
    }
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.