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

[EDIT] Ответ @Claudio дает мне очень хороший совет о том, как отфильтровать выбросы. Однако я хочу использовать фильтр Kalman для своих данных. Поэтому я изменил приведенные ниже данные примера, так что он имеет тонкий вариационный шум, который не столь экстремален (что я также вижу). Если бы кто-нибудь еще мог дать мне какое-то направление, как использовать PyKalman по моим данным, это было бы здорово. [/EDIT]

Для проекта робототехники я пытаюсь отследить кайт в воздухе с помощью камеры. Я программирую на 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 docs я нашел следующий пример:

>>> 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]])

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

Ответы

Ответ 1

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

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

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

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

  • Измерения (которые подвержены "шуму измерения", то есть датчики не идеальны)

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

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

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

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

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

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

z= [x, y],

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

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

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

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

x (k) = x (k-1) + dt * x_dot (k-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]].

Теперь мы можем использовать фильтр Kalman в 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 - просто номер образца).

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

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

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

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

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

Начиная с конца вышеприведенного кода, исправьте observation covariance до 10x ранее оцененное значение, установка 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. Точки - это измерения, пунктирная линия - это оценки состояний для периода обучения фильтра, сплошная линия - это оценки состояний для периода "on-line".

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

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

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

Ответ 2

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

Как насчет этого?

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

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