カルマンフィルタの基礎 第15回
今回は、カルマンフィルタによる「相補フィルタ」を紹介する
特性の異なる2つのセンサを用いてセンサフュージョンを行い、高精度の測定を実現する
相補フィルタ
状態モデル
観測対象
下記のモデルを測定する
ただし、今回は、対象のモデルは信号生成にのみ使用して、フィルタリングには使用しない
センサ1
センサ2
センサの計測値
数値シミュレーションのパラメータ
相補フィルタに用いる状態モデル
状態モデルを拡張し、下記のようにする
観測量は が計測できないため、 、 の差分を用いる
カルマンフィルタのパラメータは下記にする
ちなみに、それぞれのセンサの測定誤差は下記のように求めることができる
import matplotlib.pyplot as plt import numpy as np import pandas as pd A_ = np.array([[0.75, 0, 0], [0, 1, 0], [0, 0, 0]]) B_ = np.array([[0.25, 0], [0, 0], [0, 1]]) C_ = np.array([[1], [1], [-1]]) Q_ = np.array([[60, 0], [0, 60]]) R_ = np.array([[1e-4]])
線形カルマンフィルタの実装
今回は線形カルマンフィルタを用いる
def kf(A_, B_, C_, Q_, R_, y_, xhat, P_): xhatm = A_ @ xhat Pm = A_ @ P_ @ A_.T + B_ @ Q_ @ B_.T G_ = Pm @ C_ @ (C_.T @ Pm @ C_ + R_)**-1 xhat_new = xhatm + G_ @ (y_ - C_.T @ xhatm) P_new = (np.eye(A_.shape[0]) - G_ @ C_.T) @ Pm return xhat_new, P_new, G_, Pm
シミュレーションの実施
N_ = 200 np.random.seed(0) v_ = np.array([ [np.random.randn() * np.sqrt(Q_[0][0])], [np.random.randn() * np.sqrt(Q_[1][1])], ]) # システム雑音 w_ = np.array([[np.random.randn() * np.sqrt(R_[0][0])]]) # 観測雑音 z_arr = [10] x_arr = [np.array([[0], [20], [0]])] y_arr = [C_.T @ x_arr[0] + w_] xhat_arr = [np.array([[10], [30], [20]])] P_arr = [np.diag([1000, 1000, 1000])] G_arr = [] for i in range(1, N_): d_ = np.random.randn() * np.sqrt(5) z_ = z_arr[-1] + 2 * np.cos(0.05 * i) + d_ v_ = np.array([ [np.random.randn() * np.sqrt(Q_[0][0])], [np.random.randn() * np.sqrt(Q_[1][1])], ]) # システム雑音 w_ = np.array([[np.random.randn() * np.sqrt(R_[0][0])]]) # 観測雑音 x_ = A_ @ x_arr[-1] + B_ @ v_ y_ = C_.T @ x_ + w_ z_arr += [z_] x_arr += [x_] y_arr += [y_] xhat, P_, G_, Pm = kf( A_, B_, C_, Q_, R_, y_, xhat_arr[-1], P_arr[-1]) xhat_arr += [xhat] P_arr += [P_] G_arr += [G_] x_arr = np.array(x_arr) y_arr = np.array(y_arr) xhat_arr = np.array(xhat_arr) P_arr = np.array(P_arr) G_arr = np.array(G_arr)
シミュレーション結果の確認
真値 と センサ計測値 、
fig = plt.figure(figsize=(6, 5)) ax1 = fig.add_subplot(2, 1, 1) ax2 = fig.add_subplot(2, 1, 2, sharex=ax1) ax1.plot(z_arr, '-', label='$z$') ax1.plot(z_arr + x_arr[:, 0, 0] + x_arr[:, 1, 0], '--', label='$y_1$') ax1.set_ylabel('sensor1') ax1.grid(ls=':'); ax1.legend() ax2.plot(z_arr, '-', label='$z$') ax2.plot(z_arr + x_arr[:, 2, 0], '--', label='$y_2$') ax2.set_ylabel('sensor2') ax2.grid(ls=':'); ax2.legend() ax2.set_xlabel('$k$') fig.tight_layout()
センサ1は、低ノイズだが、バイアスがあるセンサ
センサ2は、高ノイズだが、バイアルがないセンサ
推定値の確認
fig = plt.figure(figsize=(6, 5)) ax1 = fig.add_subplot(2, 1, 1) ax2 = fig.add_subplot(2, 1, 2, sharex=ax1) ax1.plot(z_arr, '-', label='$z$') ax1.plot(z_arr + x_arr[:, 0, 0] + x_arr[:, 1, 0], '--', label='$y_1$') ax1.plot(z_arr + x_arr[:, 0, 0] + x_arr[:, 1, 0] - xhat_arr[:, 0, 0] - xhat_arr[:, 1, 0], '--', label='$\hat{y}_1$') ax1.set_ylabel('sensor1') ax1.grid(ls=':'); ax1.legend() ax2.plot(z_arr, '-', label='$z$') ax2.plot(z_arr + x_arr[:, 2, 0], '--', label='$y_2$') ax2.plot(z_arr + x_arr[:, 2, 0] - xhat_arr[:, 2, 0], '--', label='$\hat{y}_2$') ax2.set_ylabel('sensor2') ax2.grid(ls=':'); ax2.legend() ax2.set_xlabel('$k$') fig.tight_layout()
推定値が真値をよく表していることがわかる
を 計測生値 と 推定値 で比較する
mean_ = (x_arr[:, 0, 0] + x_arr[:, 1, 0])[100:].mean() std_ = (x_arr[:, 0, 0] + x_arr[:, 1, 0])[100:].std() m3std1 = np.abs(mean_) + 3 * std_ mean_ = (x_arr[:, 2, 0])[100:].mean() std_ = (x_arr[:, 2, 0])[100:].std() m3std2 = np.abs(mean_) + 3 * std_ mean_ = (x_arr[:, 0, 0] + x_arr[:, 1, 0] - xhat_arr[:, 0, 0] - xhat_arr[:, 1, 0])[100:].mean() std_ = (x_arr[:, 0, 0] + x_arr[:, 1, 0] - xhat_arr[:, 0, 0] - xhat_arr[:, 1, 0])[100:].std() m3std3 = np.abs(mean_) + 3 * std_ mean_ = (x_arr[:, 2, 0] - xhat_arr[:, 2, 0])[100:].mean() std_ = (x_arr[:, 2, 0] - xhat_arr[:, 2, 0])[100:].std() m3std4 = np.abs(mean_) + 3 * std_ print('raw y1 : %7.3f / y2 : %7.3f' % (m3std1, m3std2)) print('est z(y1) : %7.3f / z(y2) : %7.3f' % (m3std3, m3std4))
raw y1 : 26.519 / y2 : 24.820 est z(y1) : 9.529 / z(y2) : 9.530
相補フィルタで、より高精度の計測ができることが分かる
参考文献
この記事は以下の書籍を参考にしましたが、
私の拙い知識で書いておりますので、誤り等ありましたらご指摘ください