Как использовать фильтр Калмана в Python для данных о местоположении?

[РЕДАКТИРОВАТЬ] Ответ @Claudio дает мне действительно хороший совет о том, как отфильтровать выбросы. Тем не менее, я хочу начать использовать фильтр Калмана для своих данных. Итак, я изменил приведенный ниже пример данных, чтобы он имел легкий вариационный шум, который не был таким экстремальным (который я тоже часто вижу). Если бы кто-нибудь еще мог дать мне какое-то направление, как использовать PyKalman с моими данными, это было бы здорово. [/РЕДАКТИРОВАТЬ]

Для проекта робототехники я пытаюсь отследить воздушный змей в воздухе с помощью камеры. Я программирую на Python, и я вставил несколько шумных результатов определения местоположения ниже (каждый элемент также имеет объект datetime, но я оставил их для ясности).

[           # X     Y 
    {'loc': (399, 293)},
    {'loc': (403, 299)},
    {'loc': (409, 308)},
    {'loc': (416, 315)},
    {'loc': (418, 318)},
    {'loc': (420, 323)},
    {'loc': (429, 326)},  # <== Noise in X
    {'loc': (423, 328)},
    {'loc': (429, 334)},
    {'loc': (431, 337)},
    {'loc': (433, 342)},
    {'loc': (434, 352)},  # <== Noise in Y
    {'loc': (434, 349)},
    {'loc': (433, 350)},
    {'loc': (431, 350)},
    {'loc': (430, 349)},
    {'loc': (428, 347)},
    {'loc': (427, 345)},
    {'loc': (425, 341)},
    {'loc': (429, 338)},  # <== Noise in X
    {'loc': (431, 328)},  # <== Noise in X
    {'loc': (410, 313)},
    {'loc': (406, 306)},
    {'loc': (402, 299)},
    {'loc': (397, 291)},
    {'loc': (391, 294)},  # <== Noise in Y
    {'loc': (376, 270)},
    {'loc': (372, 272)},
    {'loc': (351, 248)},
    {'loc': (336, 244)},
    {'loc': (327, 236)},
    {'loc': (307, 220)}
]

Сначала я подумал о том, чтобы вручную вычислить выбросы, а затем просто удалить их из данных в реальном времени. Затем я прочитал о фильтрах Калмана и о том, как они специально предназначены для сглаживания зашумленных данных. Итак, после некоторого поиска я нашел библиотеку PyKalman, которая кажется идеальной для этого. Поскольку я как бы потерялся во всей терминологии фильтров Калмана, я прочитал вики и некоторые другие страницы, посвященные фильтрам Калмана. Я получил общее представление о фильтре Калмана, но совершенно не понимал, как применить его к моему коду.

В документации PyKalman я нашел следующий пример:

>>> from pykalman import KalmanFilter
>>> import numpy as np
>>> kf = KalmanFilter(transition_matrices = [[1, 1], [0, 1]], observation_matrices = [[0.1, 0.5], [-0.3, 0.0]])
>>> measurements = np.asarray([[1,0], [0,0], [0,1]])  # 3 observations
>>> kf = kf.em(measurements, n_iter=5)
>>> (filtered_state_means, filtered_state_covariances) = kf.filter(measurements)
>>> (smoothed_state_means, smoothed_state_covariances) = kf.smooth(measurements)

Я просто заменил свои наблюдения своими наблюдениями следующим образом:

from pykalman import KalmanFilter
import numpy as np
kf = KalmanFilter(transition_matrices = [[1, 1], [0, 1]], observation_matrices = [[0.1, 0.5], [-0.3, 0.0]])
measurements = np.asarray([(399,293),(403,299),(409,308),(416,315),(418,318),(420,323),(429,326),(423,328),(429,334),(431,337),(433,342),(434,352),(434,349),(433,350),(431,350),(430,349),(428,347),(427,345),(425,341),(429,338),(431,328),(410,313),(406,306),(402,299),(397,291),(391,294),(376,270),(372,272),(351,248),(336,244),(327,236),(307,220)])
kf = kf.em(measurements, n_iter=5)
(filtered_state_means, filtered_state_covariances) = kf.filter(measurements)
(smoothed_state_means, smoothed_state_covariances) = kf.smooth(measurements)

но это не дает мне никаких значимых данных. Например, smoothed_state_means становится следующим:

>>> smoothed_state_means
array([[-235.47463353,   36.95271449],
       [-354.8712597 ,   27.70011485],
       [-402.19985301,   21.75847069],
       [-423.24073418,   17.54604304],
       [-433.96622233,   14.36072376],
       [-443.05275258,   11.94368163],
       [-446.89521434,    9.97960296],
       [-456.19359012,    8.54765215],
       [-465.79317394,    7.6133633 ],
       [-474.84869079,    7.10419182],
       [-487.66174033,    7.1211321 ],
       [-504.6528746 ,    7.81715451],
       [-506.76051587,    8.68135952],
       [-510.13247696,    9.7280697 ],
       [-512.39637431,   10.9610031 ],
       [-511.94189431,   12.32378146],
       [-509.32990832,   13.77980587],
       [-504.39389762,   15.29418648],
       [-495.15439769,   16.762472  ],
       [-480.31085928,   18.02633612],
       [-456.80082586,   18.80355017],
       [-437.35977492,   19.24869224],
       [-420.7706184 ,   19.52147918],
       [-405.59500937,   19.70357845],
       [-392.62770281,   19.8936389 ],
       [-388.8656724 ,   20.44525168],
       [-361.95411607,   20.57651509],
       [-352.32671579,   20.84174084],
       [-327.46028214,   20.77224385],
       [-319.75994982,   20.9443245 ],
       [-306.69948771,   21.24618955],
       [-287.03222693,   21.43135098]])

Может ли более яркая душа, чем я, дать мне несколько советов или примеров в правильном направлении? Все советы приветствуются!


person kramer65    schedule 12.04.2017    source источник
comment
Возможно, вам понадобится фильтр, но я не уверен, что вам нужен фильтр Калмана. Если вы не уверены, что вам нужен фильтр Калмана, я бы посоветовал спросить, какую сортировку использовать здесь: dsp.stackexchange.com   -  person Stephen Rauch    schedule 12.04.2017
comment
Не ответ на ваш вопрос; но удаление значений за пределами 3-сигмы избавит от всех ваших опубликованных шумных значений и ничего больше.   -  person Ben    schedule 12.04.2017
comment
В моем (слабом) понимании фильтр Калмана корректирует расхождения между предсказаниями (несовершенной) физической / математической модели и фактическими (зашумленными) измерениями. - В вашей постановке задачи я не могу распознать прогнозную модель позиции, поэтому мне интересно, может ли вам помочь фильтр Калмана.   -  person gboffi    schedule 21.04.2017
comment
@gboffi - Насколько я понимаю, фильтр Калмана заключается в том, что требуется серия измерений, сглаживающих его, чтобы его можно было использовать для получения A) результатов, которые ближе к реальности, поскольку шум более или менее нейтрализуется B) расширяется измеренные точки, чтобы можно было сделать прогноз на точки впереди. Или я здесь совершенно не прав?   -  person kramer65    schedule 21.04.2017
comment
Можете ли вы опубликовать код, который вы использовали для генерации результатов на ВАШИХ собственных данных?   -  person burglarhobbit    schedule 23.04.2017
comment
@ Mr.Robot - Вы имеете в виду код, с помощью которого я создал опубликованный smoothed_state_means?   -  person kramer65    schedule 23.04.2017
comment
@ Mr.Robot - я добавил код, который использовал для получения smoothed_state_means, к первоначальному вопросу выше. Вам это поможет?   -  person kramer65    schedule 23.04.2017
comment
Возможно, вы захотите почитать мою книгу с открытым исходным кодом «Kalman and Bayesian Filters in Python». В него вошли очень похожие проекты. Я не использую PyKalman, а использую свою собственную библиотеку FilterPy, которую вы можете установить с помощью pip или conda. Извините, если это похоже на рекламу, но книга в значительной степени точно отвечает на ваш вопрос. github.com/rlabbe/Kalman-and-Bayesian-Filters-in- Python   -  person Roger Labbe    schedule 21.06.2017
comment
@ kramer65, я был бы признателен, если бы вы могли взглянуть на это и поблагодарить вас: stackoverflow.com/questions/44944512/   -  person Desta Haileselassie Hagos    schedule 06.07.2017


Ответы (2)


TL; DR, смотрите код и картинку внизу.

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

Я настоятельно рекомендую прочитать эту веб-страницу. У меня нет связи или сведений об авторе, но я потратил около дня, пытаясь разобраться в фильтрах Калмана, и эта страница действительно заставила меня щелкнуть.

Вкратце; для системы, которая является линейной и которая имеет известную динамику (т.е. если вы знаете состояние и входные данные, вы можете предсказать будущее состояние), он обеспечивает оптимальный способ объединения того, что вы знаете о системе, для оценки ее истинного состояния. Умный бит (о котором заботится вся матричная алгебра, которую вы видите на страницах, описывающих ее) заключается в том, как она оптимально объединяет две части информации, которые у вас есть:

  1. Измерения (которые подвержены «измерительному шуму», т. Е. Датчики не идеальны)

  2. Динамика (то есть то, как, по вашему мнению, состояния развиваются в зависимости от входных данных, которые подвержены «технологическому шуму», что является просто способом сказать, что ваша модель не полностью соответствует реальности).

Вы указываете, насколько вы уверены в каждом из них (с помощью матриц ковариаций R и Q соответственно), а коэффициент Калмана определяет, как насколько вы должны верить своей модели (т.е. вашей текущей оценке вашего состояния) и насколько вы должны верить своим измерениям.

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

Давайте рассматривать воздушный змей как частицу (очевидно, упрощение, настоящий воздушный змей представляет собой протяженное тело, поэтому имеет ориентацию в трех измерениях), которая имеет четыре состояния, которые для удобства мы можем записать в векторе состояний:

x = [x, x_dot, y, y_dot],

Где x и y - позиции, а точки - скорости в каждом из этих направлений. Из вашего вопроса я предполагаю, что есть два (потенциально зашумленных) измерения, которые мы можем записать в векторе измерений:

z = [x, y],

Мы можем записать матрицу измерений (H обсуждался здесь и observation_matrices в pykalman библиотеке):

z = H x => H = [[1, 0, 0, 0], [0, 0, 1, 0]]

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

В этом случае динамика для каждого из наших состояний в текущей выборке «k» как функция состояний в предыдущих выборках «k-1» задается как:

х (к) = х (к-1) + dt * x_dot (к-1)

x_dot (k) = x_dot (k-1)

y (k) = y (k-1) + dt * y_dot (k-1)

y_dot (k) = y_dot (k-1)

Где «dt» - временной шаг. Мы предполагаем, что положение (x, y) обновляется на основе текущего положения и скорости, а скорость остается неизменной. Учитывая, что единицы измерения не указаны, мы можем просто сказать, что единицы скорости таковы, что мы можем опустить «dt» в приведенных выше уравнениях, то есть в единицах position_units / sample_interval (я предполагаю, что ваши измеренные образцы находятся с постоянным интервалом). Мы можем суммировать эти четыре уравнения в матрицу динамики как (рассмотренный здесь F и transition_matrices в pykalman библиотеке):

x (k) = Fx (k-1) => F = [[1, 1, 0, 0], [0, 1, 0, 0], [0, 0, 1, 1], [0, 0, 0, 1]].

Теперь мы можем попробовать использовать фильтр Калмана в Python. Изменено из вашего кода:

from pykalman import KalmanFilter
import numpy as np
import matplotlib.pyplot as plt
import time

measurements = np.asarray([(399,293),(403,299),(409,308),(416,315),(418,318),(420,323),(429,326),(423,328),(429,334),(431,337),(433,342),(434,352),(434,349),(433,350),(431,350),(430,349),(428,347),(427,345),(425,341),(429,338),(431,328),(410,313),(406,306),(402,299),(397,291),(391,294),(376,270),(372,272),(351,248),(336,244),(327,236),(307,220)])

initial_state_mean = [measurements[0, 0],
                      0,
                      measurements[0, 1],
                      0]

transition_matrix = [[1, 1, 0, 0],
                     [0, 1, 0, 0],
                     [0, 0, 1, 1],
                     [0, 0, 0, 1]]

observation_matrix = [[1, 0, 0, 0],
                      [0, 0, 1, 0]]

kf1 = KalmanFilter(transition_matrices = transition_matrix,
                  observation_matrices = observation_matrix,
                  initial_state_mean = initial_state_mean)

kf1 = kf1.em(measurements, n_iter=5)
(smoothed_state_means, smoothed_state_covariances) = kf1.smooth(measurements)

plt.figure(1)
times = range(measurements.shape[0])
plt.plot(times, measurements[:, 0], 'bo',
         times, measurements[:, 1], 'ro',
         times, smoothed_state_means[:, 0], 'b--',
         times, smoothed_state_means[:, 2], 'r--',)
plt.show()

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

введите описание изображения здесь

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

  1. Измерения (в данном случае двух наших состояний, x и y)
  2. Системная динамика (и текущая оценка состояния)

Динамика, запечатленная в модели выше, очень проста. Буквально они говорят, что позиции будут обновляться в соответствии с текущими скоростями (очевидным, физически разумным способом), и что скорости останутся постоянными (это явно не физически верно, но улавливает нашу интуицию, что скорости должны изменяться медленно).

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

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

kf2 = KalmanFilter(transition_matrices = transition_matrix,
                  observation_matrices = observation_matrix,
                  initial_state_mean = initial_state_mean,
                  observation_covariance = 10*kf1.observation_covariance,
                  em_vars=['transition_covariance', 'initial_state_covariance'])

kf2 = kf2.em(measurements, n_iter=5)
(smoothed_state_means, smoothed_state_covariances)  = kf2.smooth(measurements)

plt.figure(2)
times = range(measurements.shape[0])
plt.plot(times, measurements[:, 0], 'bo',
         times, measurements[:, 1], 'ro',
         times, smoothed_state_means[:, 0], 'b--',
         times, smoothed_state_means[:, 2], 'r--',)
plt.show()

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

введите описание изображения здесь

Наконец, если вы хотите использовать этот встроенный фильтр в режиме онлайн, вы можете сделать это с помощью метода filter_update. Обратите внимание, что здесь используется метод filter, а не метод smooth, потому что метод smooth может применяться только к пакетным измерениям. Подробнее здесь:

time_before = time.time()
n_real_time = 3

kf3 = KalmanFilter(transition_matrices = transition_matrix,
                  observation_matrices = observation_matrix,
                  initial_state_mean = initial_state_mean,
                  observation_covariance = 10*kf1.observation_covariance,
                  em_vars=['transition_covariance', 'initial_state_covariance'])

kf3 = kf3.em(measurements[:-n_real_time, :], n_iter=5)
(filtered_state_means, filtered_state_covariances) = kf3.filter(measurements[:-n_real_time,:])

print("Time to build and train kf3: %s seconds" % (time.time() - time_before))

x_now = filtered_state_means[-1, :]
P_now = filtered_state_covariances[-1, :]
x_new = np.zeros((n_real_time, filtered_state_means.shape[1]))
i = 0

for measurement in measurements[-n_real_time:, :]:
    time_before = time.time()
    (x_now, P_now) = kf3.filter_update(filtered_state_mean = x_now,
                                       filtered_state_covariance = P_now,
                                       observation = measurement)
    print("Time to update kf3: %s seconds" % (time.time() - time_before))
    x_new[i, :] = x_now
    i = i + 1

plt.figure(3)
old_times = range(measurements.shape[0] - n_real_time)
new_times = range(measurements.shape[0]-n_real_time, measurements.shape[0])
plt.plot(times, measurements[:, 0], 'bo',
         times, measurements[:, 1], 'ro',
         old_times, filtered_state_means[:, 0], 'b--',
         old_times, filtered_state_means[:, 2], 'r--',
         new_times, x_new[:, 0], 'b-',
         new_times, x_new[:, 2], 'r-')

plt.show()

График ниже показывает эффективность метода фильтрации, включая 3 точки, найденные с помощью метода filter_update. Точки - измерения, пунктирная линия - оценки состояния для периода обучения фильтра, сплошная линия - оценки состояний для периода "онлайн".

введите описание изображения здесь

И информация о сроках (на моем ноутбуке).

Time to build and train kf3: 0.0677888393402 seconds
Time to update kf3: 0.00038480758667 seconds
Time to update kf3: 0.000465154647827 seconds
Time to update kf3: 0.000463008880615 seconds
person kabdulla    schedule 23.04.2017
comment
Мы можем сравнить это с подходом обнаружения / устранения выбросов. Если бы модель воздушного змея не предполагала никакой динамики (мы не удосужились ввести состояния скорости _dot), я думаю, что фильтр Калмана был бы исключительно оценкой максимального правдоподобия (среднего положения), предполагая, что шум измерений является нулевым средним и нормально распределенным. Однако при этом теряется информация о том, что если кайт быстро движется вниз, то в следующем интервале он, вероятно, будет еще ниже. С другой стороны, как реализовано выше, большие отклонения не игнорируются полностью (см. Стробирование измерений, чтобы помочь с этим). - person kabdulla; 24.04.2017
comment
Спасибо за подробный ответ. Это заняло у меня несколько часов, но я думаю, что теперь я намного лучше понимаю идею фильтра Калмана. Еще один вопрос; что, если я хочу сделать сглаживание немного сильнее, чтобы линия была еще более гладкой, чем сейчас, как бы я это сделал? - person kramer65; 26.04.2017
comment
Не беспокойся. Надеюсь, это не слишком детально; Я сам новичок в фильтрах Kalman, поэтому старался не усложнять, но сейчас они мне очень нравятся, так что, возможно, я увлекся! То, что вы предлагаете, должно быть довольно простым. Я подробнее остановлюсь на ответе по этому поводу. - person kabdulla; 26.04.2017
comment
@ kramer65 вам подходит расширенный ответ? Он эффективно предполагает большую дисперсию (больше шума) ваших измерений, что означает, что больше внимания уделяется динамике (в приведенной выше модели скорости не меняются быстро). - person kabdulla; 27.04.2017
comment
Отлично, спасибо! И последний вопрос. Я использую это для определения траектории полета моего кайта в реальном времени, но, к сожалению, линия kf = kf.em(measurements, n_iter=5) занимает слишком много времени, чтобы он мог рассчитать ее 25 раз в секунду (это частота кадров видеозаписи). Вероятно, это маловероятно, но видите ли вы какую-либо возможность улучшения производительности с помощью этой функции? - person kramer65; 27.04.2017
comment
@ kramer65, я попытался добавить пример этого к ответу. Вам не нужно использовать kf.em в Интернете. Это пытается подобрать фильтр, вместо этого вы можете использовать предварительно обученный фильтр, чтобы сделать больше оценок. Возможно, вы захотите время от времени повторно устанавливать фильтр (в случае увеличения шума измерения или изменения динамики кайта). - person kabdulla; 28.04.2017
comment
У меня есть массив Python для точек «Широта и Долгота», как я могу рассчитать матрицу перехода и матрицу наблюдения на основе этих точек .. ?? У меня есть временные метки, такие как Python DateTIme, я не уверен, как я могу использовать эти уравнения для ввода данных, кто-нибудь может помочь? - person id101112; 01.05.2018

Из того, что я вижу, использование фильтрации Калмана, возможно, не подходит для вашего случая.

А как насчет того, чтобы сделать это ТАК? :

lstInputData = [
    [346, 226 ],
    [346, 211 ],
    [347, 196 ],
    [347, 180 ],
    [350, 2165],  ## noise
    [355, 154 ],
    [359, 138 ],
    [368, 120 ],
    [374, -830],  ## noise
    [346, 90  ],
    [349, 75  ],
    [1420, 67 ],  ## noise
    [357, 64  ],
    [358, 62  ]
]

import pandas as pd
import numpy as np
df = pd.DataFrame(lstInputData)
print( df )
from scipy import stats
print ( df[(np.abs(stats.zscore(df)) < 1).all(axis=1)] )

Вот вывод:

      0     1
0    346   226
1    346   211
2    347   196
3    347   180
4    350  2165
5    355   154
6    359   138
7    368   120
8    374  -830
9    346    90
10   349    75
11  1420    67
12   357    64
13   358    62
      0    1
0   346  226
1   346  211
2   347  196
3   347  180
5   355  154
6   359  138
7   368  120
9   346   90
10  349   75
12  357   64
13  358   62

См. здесь для получения дополнительной информации и источника, который у меня есть, приведенный выше код от.

person Claudio    schedule 12.04.2017
comment
Это действительно хорошая идея! Я обязательно воспользуюсь этой уловкой. В дополнение к этому я также хочу использовать фильтр Калмана. Не могли бы вы также дать мне пример того, как я могу использовать фильтр Калмана? - person kramer65; 12.04.2017
comment
@ kramer65 Я думаю, что тема использования фильтрации Калмана слишком широка, чтобы обсуждать ее здесь. Кроме того, я не эксперт по фильтрам Калмана, поэтому, если вы не можете жить с моим ответом и принять его, вам придется подождать других ответов. Я ответил здесь, потому что вы написали: Все советы приветствуются! :) - person Claudio; 12.04.2017