Плавні дані GPS


145

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

Мені було цікаво про якийсь досить простий метод, щоб цього уникнути. В якості першої ідеї я думав про відкидання значень з точністю, що перевищує певний поріг, але, мабуть, є деякі інші кращі способи зробити. Яким звичайним чином це виконують програми?


Я відчуваю погані наслідки "шуму GPS", коли намагаюся обчислити пов'язані (похідні) значення, такі як швидкість і нахил, які дуже розривно розроблені спеціально для треків із високою швидкістю вибірки (оскільки час має цілу [одну секунду] роздільну здатність).
heltonbiker

4
(також, якщо ви рухаєтесь по головних дорогах, ви можете використовувати алгоритм "прив’язання до доріг" за умови, що у вас є хороший [правильний, точний] набір даних дорожньої карти. Подумайте)
heltonbiker

Я також стикаюся з цим питанням для найкращої точності.
ViruMax

Відповіді:


80

Ось простий фільтр Кальмана, який міг би бути використаний саме для цієї ситуації. Це виходило з певної роботи, яку я робив на пристроях Android.

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

Код можна вдосконалити, оскільки він передбачає, що найкраща оцінка поточного місця розташування - це останнє відоме місце розташування, і якщо хтось рухається, слід мати можливість використовувати сенсори Android для отримання кращої оцінки. Код має єдиний вільний параметр 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у розділі "Пов'язані процеси" у цій статті Вікіпедії. Q_metres_per_secondє стандартним відхиленням і вимірюється в метрах, тому метри, а не метри / секунди - це його одиниці. Це відповідає стандартному відхиленню розподілу через 1 секунду.
Стохастично

3
Я спробував цей підхід і код, але в кінці кінців занадто скоротив загальну відстань. Зробив це занадто неточно.
Андреас Рудольф

1
@ user2999943 так, використовуйте код для обробки координат, отриманих від onLocationChanged ().
Стохастично

2
@Koray, якщо у вас немає інформації про точність, ви не можете використовувати фільтр Kalman. Це абсолютно принципово для того, що намагається зробити фільтр Кальмана.
Стохастично

75

Те, що ви шукаєте, називається фільтром Кальмана . Він часто використовується для згладжування навігаційних даних . Це не обов'язково тривіально, і ви можете зробити багато налаштувань, але це дуже стандартний підхід і працює добре. Існує бібліотека KFilter, яка є реалізацією C ++.

Мій наступний запасний варіант буде підходити як мінімум до квадратів . Фільтр Калмана згладить дані з урахуванням швидкостей, тоді як підхід, що відповідає найменшому квадрату, просто використовуватиме позиційну інформацію. І все-таки це реально простіше втілити і зрозуміти. Схоже, в Науковій бібліотеці GNU це може бути реалізовано.


1
Дякую Крису. Так, я читав про Кальмана під час певного пошуку, але це, безумовно, трохи перевищує мої знання з математики. Чи знаєте ви, що будь-який зразок коду легко читати (і розуміти!), А ще краще, якийсь варіант доступний? (C / C ++ / Java)
Ал.

1
@Al На жаль, моя єдина експозиція з фільтрами Kalman - це робота, тому у мене є чудовий елегантний код, який я не можу вам показати.
Кріс Аргуїн

Немає проблем :-) Я намагався шукати, але чомусь здається, що ця річ Калмана - це чорна магія. Багато теоретичних сторінок, але мало-мало коду. Дякую, спробуємо інші методи.
Ал.

2
kalman.sourceforge.net/index.php ось C ++ реалізація фільтра Kalman.
Ростислав Дружченко

1
@ChrisArguin Вітаємо вас. Повідомте мене, якщо результат хороший, будь ласка.
Ростислав Дружченко

11

Це може прийти трохи пізно ...

Я написав цей KalmanLocationManager для Android, який охоплює двох найпоширеніших провайдерів локації, мережу та GPS, kalman фільтрує дані та доставляє оновлення для LocationListener(як два "справжніх" провайдера).

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

Насправді він використовує три кальманові фільтри для кожного виміру: широту, довготу та висоту. У будь-якому разі вони незалежні.

Це значно покращує математику матриці: замість того, щоб використовувати одну матрицю переходу стану 6x6, я використовую 3 різні матриці 2x2. Насправді в коді я взагалі не використовую матриць. Розв’язано всі рівняння і всі значення - примітиви (подвійні).

Вихідний код працює, а там демонстраційна діяльність. Вибачте за відсутність явадока в деяких місцях, я наздогнаю.


1
Я спробував використати ваш код lib, у мене з’явилися небажані результати, я не впевнений, чи роблю щось не так ... (Нижче URL-адреса зображення, синій відфільтрований шлях місць, оранжевий - це сирі місця) app.box. com / s / w3uvaz007glp2utvgznmh8vlggvaiifk
umesh

Шипи, які ви бачите "зростаючими" від середньої (помаранчевої лінії), виглядають як оновлення мережевого постачальника. Чи можете ви спробувати побудувати як необмежену мережу, так і GPS-оновлення? Можливо, вам буде краще без оновлень мережі, залежно від того, що ви намагаєтеся досягти. До речі, звідки ви отримуєте ці сирі помаранчеві оновлення?
villoren

1
помаранчеві точки від постачальника gps, а сині - від Kalman, я накреслив журнали на карті
umesh

Чи можете ви надіслати мені ці дані в якомусь текстовому форматі? Кожне оновлення місцеположення має поле Location.getProvider (). Лише один файл із усіма Location.toString ().
villoren

9

Не слід обчислювати швидкість зі зміни положення за раз. GPS може мати неточні позиції, але він має точну швидкість (вище 5 км / год). Тому використовуйте швидкість від штемпеля розташування GPS. І далі ви не повинні робити це з курсом, хоча це працює більшість разів.

Позиції GPS, що поставляються, вже відфільтровані Калманом, ви, мабуть, не вдається покращити, як правило, у післяобробці ви не маєте такої ж інформації, як чіп GPS.

Ви можете згладити це, але це також вносить помилки.

Просто переконайтесь, що ви зніміть положення, коли пристрій стоїть нерухомо, це видалить стрибкові позиції, що деякі пристрої / Конфігурації не видаляються.


5
Чи можете ви надати для цього кілька посилань?
ivyleavedtoadflax

1
У цих реченнях є багато інформації та багато професійного досвіду, на яке речення саме ви хочете посилання? для швидкості: пошук ефекту доплера та GPS. внутрішній Кальман? Це основні знання GPS, кожен документ або книга, що описують, як працює інтерфейс чіпа GPS. smootig-помилки: коли-небудь згладжування вводить помилки. стояти на місці? Спробуй.
AlexWien

2
"Стрибки навколо", коли стоять на місці, - не єдине джерело помилок. Існують також відбиття сигналів (наприклад, від гір), де положення стрибає. Мої мікросхеми GPS (наприклад, Garmin Dakota 20, SonyEricsson Neo) цього не відфільтрували ... І насправді жарт - це значення висоти сигналів GPS, якщо вони не поєднуються з барометричним тиском. Ці значення не фільтруються або я не хочу бачити нефільтровані значення.
hgoebl

1
@AlexWien GPS обчислює відстань від точки за часом до допуску, даючи вам сферу товщиною, оболонку, зосереджену навколо супутника. Ви знаходитесь десь у цій томі оболонки. Перетин трьох цих об'ємів оболонки дає об'єм позиції, центроїдом якого є ваше обчислене положення. Якщо у вас є набір повідомлених позицій і ви знаєте, що датчик знаходиться в спокої, обчислювальний центр ефективно перетинає набагато більше оболонок, підвищуючи точність. Помилка в цьому випадку зменшується .
Пітер Вун

6
"Позиції GPS, як уже доставлені, вже відфільтровані Кальманом, ви, ймовірно, не можете покращити". Якщо ви можете вказати на джерело, яке підтверджує це для сучасних смартфонів (наприклад), це було б дуже корисно. Я сам не бачу доказів цього. Навіть проста фільтрація Калмана в сирих місцях пристрою настійно говорить про те, що це неправда. Сирі місця танцюють навколо хаотично, тоді як відфільтровані місця найчастіше розташовані близько до реального (відомого) місця.
sobri

6

Зазвичай я використовую акселерометри. Раптова зміна положення за короткий період передбачає велике прискорення. Якщо це не відображається в телеметрії акселерометра, це майже напевно пояснюється зміною "найкращих трьох" супутників, що використовуються для обчислення позиції (яку я називаю GPS-телепортуванням).

Коли актив перебуває в спокої і скакає завдяки GPS-телепортуванню, якщо ви поступово обчислюєте центроїд, ви ефективно перетинаєте все більший і більший набір оболонок, підвищуючи точність.

Для цього, коли актив не знаходиться в спокої, ви повинні оцінити його ймовірне наступне положення та орієнтацію на основі даних про швидкість, напрямок та лінійні та обертальні (якщо у вас гіроскопи) дані прискорення. Це більш-менш те, що робить відомий фільтр K. Ви можете отримати всю техніку за обладнання приблизно за 150 доларів на AHRS, що містить усе, окрім модуля GPS, та з гніздом для підключення. У нього є власний процесор і фільтр Kalman на борту; результати стабільні і досить хороші. Інерційне наведення дуже стійке до тремтіння, але пливе з часом. GPS схильний до тремтіння, але не збивається з часом, вони були практично зроблені для компенсації один одному.


4

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

Наприклад

Згладжування в п’яти точках вибірки A, B, C, D, E

АТОТАЛЬНА = ПІД відстань AB AC AD AE

BTOTAL = Сума відстаней AB BC BD BE

CTOTAL = Сума відстаней AC BC CD CE CE

DTOTAL = Сума відстаней DA DB DC DE

ETOTAL = Сума відстаней EA EB EC DE

Якщо BTOTAL найбільший, ви заміните точку B на D, якщо BD = min {AB, BC, BD, BE}

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


Спасибі, я теж постріляю. Зауважте, що я хочу згладити поточну позицію, оскільки це відображається та використовується для отримання деяких даних. Мене не цікавлять минулі моменти. У моїй оригінальній ідеї було застосовано зважені засоби, але я все ще маю бачити, що найкраще.
Ал.

1
Аль, це здається формою зважених засобів. Вам потрібно буде використовувати "минулі" точки, якщо ви хочете зробити будь-яке згладжування, тому що системі потрібно мати більше, ніж поточне положення, щоб знати, де також згладити. Якщо ваш GPS приймає точки даних раз на секунду, а ваш користувач дивиться на екран раз на п’ять секунд, ви можете використовувати 5 точок даних, не помічаючи його! Ковзаюча середня сума також затримається на один dp.
Карл

4

Що стосується найменших розмірів квадратів, ось кілька експериментів:

  1. Тільки тому, що це найменше розміщення квадратів, не означає, що він повинен бути лінійним. Ви можете мінімум-квадратів підлаштувати квадратичну криву до даних, тоді це відповідатиме сценарію, при якому користувач прискорюється. (Зверніть увагу, що під найменшими квадратами я маю на увазі використання координат як залежної змінної, а час - як незалежної змінної.)

  2. Ви також можете спробувати зважування точок даних на основі повідомленої точності. Коли точність низька, ці дані нижче.

  3. Інша річ, яку ви можете спробувати, - це не відображати єдину точку, якщо низька точність відображення кола або щось, що вказує на діапазон, в якому користувач міг би базуватися на повідомленій точності. (Це робиться вбудованим додатком Google Maps для iPhone.)


3

Ви також можете використовувати сплайн. Введіть значення, які у вас є, і інтерполюйте бали між вашими відомими точками. Пов'язуючи це з фільтром з найменшою квадратикою, ковзною середньою або кальмановим фільтром (як зазначено в інших відповідях), ви зможете обчислювати бали між вашими "відомими" точками.

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

Різні сплайни мають різні характеристики. Я найчастіше бачив акіму та кубічні сплайни.

Ще один алгоритм, який слід врахувати, - це алгоритм спрощення лінії Рамер-Дуглас-Пюкер, він досить часто використовується для спрощення даних 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 на Котлін

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

Ось реалізація Javascript реалізації Java @ Stochastically для всіх, хто цього потребує:

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)
    }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.