Плавные данные GPS

Я работаю с данными GPS, получая значения каждую секунду и отображая текущую позицию на карте. Проблема в том, что иногда (особенно когда точность низкая) значения сильно меняются, что делает текущую позицию "прыгать" между удаленными точками на карте.

Мне было интересно, какой простой способ избежать этого. В качестве первой идеи я подумал о том, чтобы отбросить значения с точностью до определенного порога, но я думаю, что есть и другие лучшие способы. Каким образом обычные программы выполняют это?

Ответ 1

То, что вы ищете, называется Kalman Filter. Он часто используется для плавных навигационных данных. Это не обязательно тривиально, и вы можете много настраивать, но это очень стандартный подход и работает хорошо. Существует библиотека KFilter, которая является реализацией на С++.

Мой следующий резерв будет наименьшие квадраты подходят. Фильтр Калмана сгладит данные, принимая во внимание скорости, тогда как подход с наименьшими квадратами подходит только для использования позиционной информации. Тем не менее, это определенно проще реализовать и понять. Похоже, что в Научной библиотеке GNU может быть реализована .

Ответ 2

Вот простой фильтр Калмана, который можно было использовать именно для этой ситуации. Это было связано с некоторыми работами, которые я делал на устройствах Android.

Общая теория фильтра Калмана касается оценок для векторов с точностью оценок, представленных ковариационными матрицами. Однако для оценки местоположения на устройствах Android общая теория сводится к очень простому случаю. Поставщики местоположения Android предоставляют местоположение в виде широты и долготы вместе с точностью, которая определяется как единое число, измеренное в метрах. Это означает, что вместо ковариационной матрицы точность фильтра Калмана может быть измерена одним числом, хотя местоположение в калмановском фильтре измеряется двумя числами. Также можно игнорировать тот факт, что широта, долгота и метры фактически все разные единицы могут быть проигнорированы, потому что если вы поместите коэффициенты масштабирования в фильтр Калмана, чтобы преобразовать их все в одни и те же единицы, то эти коэффициенты масштабирования в конечном итоге будут отменены при преобразовании результатов обратно в исходные блоки.

Код может быть улучшен, поскольку он предполагает, что наилучшая оценка текущего местоположения является последним известным местоположением, и если кто-то перемещается, у него должны быть возможности использовать датчики Android для получения более точной оценки. Код имеет единственный свободный параметр Q, выраженный в метрах в секунду, который описывает, как быстро точность уменьшается при отсутствии каких-либо новых оценок местоположения. Более высокий параметр Q означает, что точность уменьшается быстрее. Фильтры Kalman в целом работают лучше, когда точность затухает немного быстрее, чем можно было бы ожидать, поэтому для прогулки по телефону с 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;
        }
    }
}

Ответ 3

Это может немного запоздать...

Я написал этот KalmanLocationManager для Android, который обертывает двух наиболее распространенных поставщиков местоположения: сети и GPS, kalman-фильтрует данные и доставляет обновления для LocationListener (как и два "реальных" провайдера).

Я использую его в основном для "интерполяции" между показаниями - получать обновления (предсказания местоположения) каждые 100 миллисов (например, вместо максимальной скорости в секунду в секунду), что дает мне лучшую частоту кадров при анимации моей позиции.

На самом деле он использует три фильтра калмана, для каждого измерения: широту, долготу и высоту. Во всяком случае, они независимы.

Это значительно упрощает математическую матрицу: вместо использования одной матрицы перехода состояния 6x6 я использую 3 разные матрицы 2x2. Фактически в коде я вообще не использую матрицы. Решили все уравнения, и все значения являются примитивами (double).

Исходный код работает, и там есть демонстрационная активность. Извините за отсутствие javadoc в некоторых местах, я догоню.

Ответ 4

Вы не должны вычислять скорость смены позиции за раз. GPS может иметь неточные позиции, но имеет точную скорость (выше 5 км/ч). Поэтому используйте скорость от отметки местоположения GPS. И далее вы не должны делать это с курсом, хотя он работает большую часть времени.

Позиции GPS, которые поставляются, уже отфильтрованы Kalman, вы, вероятно, не можете улучшить, в постобработке обычно у вас нет той же информации, что и чип GPS.

Вы можете сгладить его, но это также приводит к ошибкам.

Просто убедитесь, что вы удаляете позиции, когда устройство стоит на месте, это удаляет позиции прыжка, что некоторые устройства/конфигурации не удаляются.

Ответ 5

Один метод, который использует меньше математики/теории, состоит в выборке 2, 5, 7 или 10 точек данных за раз и определяет те, которые являются выбросами. Менее точная мера выброса, чем фильтр Калмана, заключается в использовании следующего алгоритма, чтобы взять все пара-мудрые расстояния между точками и выбросить тот, который наиболее далек от других. Обычно эти значения заменяются значением, самым близким к исходному значению, которое вы заменяете

Например

Сглаживание в пяти точках выборки 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 является самым большим, вы замените точку B на D, если BD = min {AB, BC, BD, BE}

Это сглаживание определяет выбросы и может быть увеличено с использованием средней точки BD вместо точки D, чтобы сгладить позиционную линию. Ваш пробег может варьироваться, и существуют более математически строгие решения.

Ответ 6

Как и для наименьших квадратов, вот несколько других вещей, которые можно поэкспериментировать с:

  • Просто потому, что наименьшая квадратичная подгонка не означает, что она должна быть линейной. Вы можете наименьших квадратов - соответствовать квадратичной кривой для данных, тогда это соответствует сценарию, в котором пользователь ускоряется. (Обратите внимание, что по методу наименьших квадратов я подразумеваю использование координат в качестве зависимой переменной и времени как независимой переменной.)

  • Вы также можете попробовать взвешивать точки данных на основе сообщаемой точности. Когда точность мала, эти данные ниже.

  • Еще одна вещь, которую вы можете попробовать, а не отображать одну точку, если точность низкая, отображает круг или что-то, указывающее диапазон, в котором пользователь может основываться на заявленной точности. (Это то, что делает iPhone встроенное приложение Google Maps.)

Ответ 7

Я обычно использую акселерометры. Резкое изменение положения за короткий период подразумевает быстрое ускорение. Если это не отражается в телеметрии акселерометра, то это почти наверняка связано с изменением "лучших трех" спутников, используемых для вычисления положения (к которому я отношусь как телепортация GPS).

Когда актив находится в состоянии покоя и прыгает из-за телепортации GPS, если вы постепенно вычисляете центр тяжести, вы фактически пересекаете больший и больший набор оболочек, повышая точность.

Чтобы сделать это, когда актив не находится в состоянии покоя, вы должны оценить его вероятную следующую позицию и ориентацию на основе скорости, заголовка и линейного и вращательного (если у вас есть гироскопа) данные ускорения. Это более или менее то, что делает знаменитый фильтр K. Вы можете получить все это на аппаратных средствах около 150 долларов США на AHRS, где есть все, кроме модуля GPS, и с разъемом для подключения. Он имеет собственный процессор и фильтрацию Kalman на борту; результаты стабильны и неплохи. Инерционное наведение очень устойчиво к дрожанию, но со временем дрейфует. GPS склонен к дрожанию, но не дрейфует со временем, они были практически сделаны, чтобы компенсировать друг друга.

Ответ 8

Вы также можете использовать сплайн. Подавайте значения, которые у вас есть, и интерполируйте точки между вашими известными точками. Связывание этого с подгонкой наименьших квадратов, скользящим средним или калмановским фильтром (как упоминалось в других ответах) дает вам возможность вычислять точки между вашими "известными" точками.

Возможность интерполяции значений между вашими известными дает вам приятный плавный переход и/разумное/приближение того, какие данные будут присутствовать, если бы вы имели более высокую точность. http://en.wikipedia.org/wiki/Spline_interpolation

Различные сплайны имеют разные характеристики. Наиболее часто я встречаю Akima и кубические сплайны.

Еще один алгоритм, который следует учитывать, - это алгоритм упрощения линии Рамера-Дугласа-Пьюкера, который широко используется в упрощении GPS-данных. (http://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm)

Ответ 9

Возвращаясь к фильтрам Калмана... Я нашел реализацию C для фильтра Калмана для данных GPS здесь: http://github.com/lacker/ikalman Я haven Еще не пробовал, но кажется многообещающим.

Ответ 10

Отображается на CoffeeScript, если это интересно. ** edit → извините, используя магистраль тоже, но вы получите эту идею.

Изменено немного, чтобы принять маяк с атрибутами

{широта: item.lat, longitude: 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]

Ответ 11

У меня здесь есть реализация javascript, но она все еще нуждается в улучшении, потому что она дает мне массив с NAN lat en long;

    var MinAccuracy = 1;
    var Q_metres_per_second=16;
    var TimeStamp_milliseconds;
    var lat;
    var lng;
    var variance;
   test();
    function test(){

        var points = [{
            lat: 0.0850000,
            lng: 0.75000,
            time: '2016-07-11T17:48:42.489Z',
            tag: "A"
        }, {
            lat: 0.00000001,
            lng: 0.01,
            time: '2016-07-11T18:20:42.489Z',
            tag: "B"
        }, {
            lat: 0.01,
            lng: 0.0100001,
            time: '2016-07-11T18:21:02.489Z',
            tag: "C"
        }, {
            lat: 0.000001,
            lng: 0.011,
            time: '2016-07-11T18:21:22.489Z',
            tag: "D"
        }, {
            lat: 0.002,
            lng: 0.002,
            time: '2016-07-11T18:21:42.489Z',
            tag: "E"
        }, {
            lat: 0.0021,
            lng: 0.0021,
            time: '2016-07-11T18:22:02.489Z',
            tag: "F"
        }];
        var test;
        for(var i=0; i< points.length;i++){
            test = SetStat(points[i].lat,points[i].lng,5,points[i].time)

        }
      console.log(test);
    }
    function fcoords(lat, lng){
        return {
            lat:lat,
            lng:lng
        };
    }
    function SetStat(lat_measurement, lng_measurement, accuracy,  TimeStamp_milliseconds){

        var arrcor =[];

        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{

            var 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
            }


            var K = variance / (variance + accuracy * accuracy);

            lat += K * (lat_measurement - lat);
            lng += K * (lng_measurement - lng);
            arrcor.push(fcoords(lat,lng))


            // new Covarariance  matrix is (IdentityMatrix - K) * Covarariance
            variance = (1 - K) * variance;
        }

      return arrcor;
    }

Ответ 12

Я преобразовал код 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
    /// https://stackoverflow.com/info/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
        }
    }
}