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

[редактирование] Ответ @Claudio дает мне действительно хороший совет о том, как отфильтровать выбросы. Однако я хочу начать использовать фильтр Калмана для своих данных. Поэтому я изменил приведенные ниже данные, чтобы они имели тонкий вариационный шум, который не так экстремален (который я тоже вижу). Если бы кто-нибудь еще мог дать мне какое-то направление о том, как использовать 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]])

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

2 ответов


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

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

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

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

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

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

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

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

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

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

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

z = [x, y],

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

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

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

в этом случае динамика для каждого из наших состояний в текущей выборке " 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]].

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

enter image description here

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

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

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

если мы думаем, что оценочное состояние должно быть более плавным, один из способов достичь этого-сказать, что у нас меньше уверенности в наших измерениях, чем в нашей динамике (т. е. у нас выше 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()

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

enter image description here

наконец, если вы хотите использовать этот фильтр установлен на линии, вы можете сделать это с помощью 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".

enter image description here

и время (по моим ноутбук.)

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

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

что это? :

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

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