web-dev-qa-db-fra.com

Données GPS lisses

Je travaille avec des données GPS, obtenant des valeurs toutes les secondes et affichant la position actuelle sur une carte. Le problème est que parfois (surtout lorsque la précision est faible) les valeurs varient beaucoup, ce qui oblige la position actuelle à "sauter" entre des points distants de la carte.

Je m'interrogeais sur une méthode assez simple pour éviter cela. Comme première idée, j'ai envisagé de supprimer les valeurs avec une précision supérieure à un certain seuil, mais je suppose qu'il existe d'autres méthodes plus efficaces. Quelle est la méthode habituelle utilisée par les programmes?

138
Al.

Ce que vous recherchez s'appelle un filtre de Kalman . Il est fréquemment utilisé pour données de navigation lisses . Ce n’est pas nécessairement trivial et vous pouvez faire beaucoup de réglages, mais c’est une approche très standard qui fonctionne bien. Il existe une bibliothèque KFilter disponible, qui est une implémentation C++.

Ma prochaine solution serait ajustement des moindres carrés . Un filtre de Kalman lissera les données en tenant compte des vitesses, alors qu'une approche par les moindres carrés utilisera simplement des informations de position. Pourtant, il est nettement plus simple à mettre en œuvre et à comprendre. Il semble que la GNU bibliothèque scientifique peut avoir une implémentation de cela.

74
Chris Arguin

Voici un simple filtre de Kalman qui pourrait être utilisé exactement dans cette situation. Il provient de travaux que j'ai réalisés sur des appareils Android.

La théorie du filtre de Kalman en général concerne les estimations pour les vecteurs, avec la précision des estimations représentées par les matrices de covariance. Cependant, pour estimer l'emplacement sur Android périphériques, la théorie générale se réduit à un cas très simple. Android les fournisseurs de lieu donnent l'emplacement, sous forme de latitude et de longitude, avec une précision exprimée sous la forme d'un nombre unique mesuré en mètres, ce qui signifie qu'au lieu d'une matrice de covariance, la précision du filtre de Kalman peut être mesurée à l'aide d'un nombre unique, même si l'emplacement dans le filtre de Kalman est une mesure mesurée par deux chiffres Il est également possible d’ignorer le fait que la latitude, la longitude et les mètres sont bien des unités différentes, car si vous intégrez des facteurs de mise à l’échelle dans le filtre de Kalman pour les convertir en unités identiques, ces facteurs s’annulent alors lors de la conversion du filtre. résultats dans les unités d'origine.

Le code pourrait être amélioré, car il suppose que la meilleure estimation de l'emplacement actuel est le dernier emplacement connu, et si quelqu'un se déplace, il devrait être possible d'utiliser les capteurs d'Android pour obtenir une meilleure estimation. Le code a un seul paramètre libre Q, exprimé en mètres par seconde, qui décrit la rapidité avec laquelle la précision diminue en l'absence de toute nouvelle estimation d'emplacement. Un paramètre Q plus élevé signifie que la précision diminue plus rapidement. Les filtres de Kalman fonctionnent généralement mieux lorsque la précision décroît un peu plus vite que prévu, donc pour me déplacer avec un téléphone Android), je trouve que Q = 3 mètres par seconde fonctionne bien, même si généralement je marcher plus lentement que cela. Mais si vous voyagez dans une voiture rapide, un nombre beaucoup plus important doit évidemment être utilisé.

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;
        }
    }
}
72
Stochastically

Cela pourrait arriver un peu en retard ...

J'ai écrit ceci KalmanLocationManager pour Android, qui englobe les deux fournisseurs d'emplacement les plus courants, Réseau et GPS, kalman filtre les données et fournit des mises à jour vers un LocationListener (comme les véritables ' fournisseurs).

Je l'utilise principalement pour "interpoler" entre les lectures - pour recevoir des mises à jour (prédictions de position) toutes les 100 millis par exemple (au lieu du débit gps maximal d'une seconde), ce qui me donne une meilleure fréquence d'images lors de l'animation de ma position.

En fait, il utilise trois filtres de kalman, activés pour chaque dimension: latitude, longitude et altitude. Ils sont indépendants, de toute façon.

Cela rend les calculs matriciels beaucoup plus faciles: au lieu d'utiliser une matrice de transition d'état 6x6, j'utilise 3 matrices 2x2 différentes. En fait, dans le code, je n’utilise pas du tout de matrices. Résolu toutes les équations et toutes les valeurs sont des primitives (double).

Le code source fonctionne et il y a une activité de démonstration. Désolé pour le manque de javadoc dans certains endroits, je vais rattraper.

11
villoren

Vous ne devriez pas calculer la vitesse du changement de position par heure. Le GPS peut avoir des positions inexactes, mais sa vitesse est précise (supérieure à 5 km/h). Donc, utilisez la vitesse du timbre de localisation GPS. Et de plus, vous ne devriez pas faire cela avec cours, bien que cela fonctionne la plupart du temps.

Les positions GPS, telles que livrées, sont déjà filtrées par Kalman, vous ne pourrez probablement pas améliorer, en post-traitement, vous n’avez généralement pas les mêmes informations que la puce GPS.

Vous pouvez y remédier, mais cela introduit également des erreurs.

Assurez-vous simplement que vous supprimez les positions lorsque le périphérique est arrêté, cela supprime les positions de saut, que certains périphériques/configurations ne suppriment pas.

8
AlexWien

J'utilise habituellement les accéléromètres. Un changement de position soudain sur une courte période implique une forte accélération. Si cela ne se reflète pas dans la télémétrie par accéléromètre, cela est probablement dû au changement des "trois meilleurs" satellites utilisés pour calculer la position (que je qualifierais de téléportage GPS).

Lorsqu'un actif est au repos et bouge à cause de la téléportation GPS, si vous calculez progressivement le centroïde, vous croisez effectivement un ensemble de coquilles de plus en plus grand, ce qui améliore la précision.

Pour ce faire, lorsque l'actif n'est pas au repos, vous devez estimer sa prochaine position et son orientation probables en fonction de la vitesse, du cap et des données d'accélération linéaire et rotationnelle (si vous avez des gyroscopes). C'est plus ou moins ce que fait le fameux filtre K. Vous pouvez obtenir le tout en matériel pour environ 150 $ sur un AHRS contenant tout sauf le module GPS et avec une prise pour en connecter un. Il possède son propre processeur et le filtrage de Kalman à bord; les résultats sont stables et assez bons. Le guidage inertiel est très résistant à la gigue mais dérive avec le temps. Le GPS est sujet à la gigue mais ne dérive pas avec le temps, ils ont été pratiquement conçus pour se compenser.

5
Peter Wone

Une méthode qui utilise moins de maths/théorie consiste à échantillonner 2, 5, 7 ou 10 points de données à la fois et à déterminer ceux qui sont aberrants. Une mesure moins précise d’une valeur aberrante qu’un filtre de Kalman consiste à utiliser l’algorithme suivant pour prendre toutes les distances en paires entre points et éliminer celle qui est le plus éloigné des autres. Généralement, ces valeurs sont remplacées par la valeur la plus proche de la valeur périphérique que vous remplacez.

Par exemple

Lissage à cinq points d'échantillonnage A, B, C, D, E

ATOTAL = SOMME des distances AB AC AD AE

BTOTAL = SOMME des distances AB BC BD BE

CTOTAL = SOMME des distances AC BC CD CE

DTOTAL = SOMME des distances DA DB DC DE

ETOTAL = SOMME des distances EA EB EC DE

Si BTOTAL est le plus grand, vous remplacerez le point B par D si BD = min {AB, BC, BD, BE}

Ce lissage détermine les valeurs aberrantes et peut être augmenté en utilisant le point milieu de BD au lieu du point D pour lisser la ligne de position. Votre kilométrage peut varier et des solutions plus rigoureuses sur le plan mathématique existent.

4
ojblass

En ce qui concerne les moindres carrés, voici quelques autres choses à expérimenter:

  1. Ce n’est pas parce qu’il a la même taille que les carrés que l’ajustement est linéaire. Vous pouvez ajuster au moins une courbe quadratique aux données, ce qui conviendrait alors à un scénario dans lequel l'utilisateur accélère. (Notez que par moindres carrés, je veux dire en utilisant les coordonnées comme variable dépendante et le temps comme variable indépendante.)

  2. Vous pouvez également essayer de pondérer les points de données en fonction de la précision rapportée. Lorsque la précision est faible, ces points de données sont plus faibles.

  3. Une autre chose que vous voudrez peut-être essayer est plutôt que d'afficher un seul point, si la précision est faible, affichez un cercle ou quelque chose indiquant la plage dans laquelle l'utilisateur pourrait se baser sur la précision rapportée. (C’est ce que fait l’application Google Maps intégrée de l’iPhone.)

4
Alex319

Revenons aux filtres de Kalman ... J'ai trouvé une implémentation en C pour un filtre de Kalman pour les données GPS ici: http://github.com/lacker/ikalman Je ne l'ai pas encore essayé, mais cela semble prometteur.

3
KarateSnowMachine

Vous pouvez également utiliser une spline. Introduisez les valeurs que vous avez et interpolez les points entre vos points connus. En reliant cela à un ajustement des moindres carrés, une moyenne mobile ou un filtre de Kalman (comme indiqué dans d'autres réponses), vous permet de calculer les points entre vos points "connus".

Le fait d’être capable d’interpoler les valeurs entre vos valeurs connues vous donne une transition en douceur de Nice et une approximation/raisonnable/des données qui seraient présentes si vous aviez une fidélité supérieure. http://en.wikipedia.org/wiki/interpolation_pline

Différentes splines ont des caractéristiques différentes. Les splines Akima et Cubic sont les plus utilisées.

Un autre algorithme à considérer est l'algorithme de simplification de la ligne Ramer-Douglas-Peucker, il est assez couramment utilisé dans la simplification des données GPS. ( http://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm )

3
Aidos

Mappé sur CoffeeScript si cela intéresse quelqu'un. ** edit -> désolé d'utiliser backbone aussi, mais vous voyez l'idée.

Modifié légèrement pour accepter une balise avec attributs

{latitude: item.lat, longitude: item.lng, date: new Date (item.effective_at), exactitude: 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]
0
lucygenik

J'ai transformé le code Java de @Stochastically en 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
    /// https://stackoverflow.com/questions/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
inspire_coding