|
| 1 | +""" |
| 2 | +
|
| 3 | +Extended kalman filter (EKF) localization with velocity correction sample |
| 4 | +
|
| 5 | +author: Atsushi Sakai (@Atsushi_twi) |
| 6 | +modified by: Ryohei Sasaki (@rsasaki0109) |
| 7 | +
|
| 8 | +""" |
| 9 | +import sys |
| 10 | +import pathlib |
| 11 | + |
| 12 | +sys.path.append(str(pathlib.Path(__file__).parent.parent.parent)) |
| 13 | + |
| 14 | +import math |
| 15 | +import matplotlib.pyplot as plt |
| 16 | +import numpy as np |
| 17 | + |
| 18 | +from utils.plot import plot_covariance_ellipse |
| 19 | + |
| 20 | +# Covariance for EKF simulation |
| 21 | +Q = np.diag([ |
| 22 | + 0.1, # variance of location on x-axis |
| 23 | + 0.1, # variance of location on y-axis |
| 24 | + np.deg2rad(1.0), # variance of yaw angle |
| 25 | + 0.4, # variance of velocity |
| 26 | + 0.1 # variance of scale factor |
| 27 | +]) ** 2 # predict state covariance |
| 28 | +R = np.diag([0.1, 0.1]) ** 2 # Observation x,y position covariance |
| 29 | + |
| 30 | +# Simulation parameter |
| 31 | +INPUT_NOISE = np.diag([0.1, np.deg2rad(5.0)]) ** 2 |
| 32 | +GPS_NOISE = np.diag([0.05, 0.05]) ** 2 |
| 33 | + |
| 34 | +DT = 0.1 # time tick [s] |
| 35 | +SIM_TIME = 50.0 # simulation time [s] |
| 36 | + |
| 37 | +show_animation = True |
| 38 | + |
| 39 | + |
| 40 | +def calc_input(): |
| 41 | + v = 1.0 # [m/s] |
| 42 | + yawrate = 0.1 # [rad/s] |
| 43 | + u = np.array([[v], [yawrate]]) |
| 44 | + return u |
| 45 | + |
| 46 | + |
| 47 | +def observation(xTrue, xd, u): |
| 48 | + xTrue = motion_model(xTrue, u) |
| 49 | + |
| 50 | + # add noise to gps x-y |
| 51 | + z = observation_model(xTrue) + GPS_NOISE @ np.random.randn(2, 1) |
| 52 | + |
| 53 | + # add noise to input |
| 54 | + ud = u + INPUT_NOISE @ np.random.randn(2, 1) |
| 55 | + |
| 56 | + xd = motion_model(xd, ud) |
| 57 | + |
| 58 | + return xTrue, z, xd, ud |
| 59 | + |
| 60 | + |
| 61 | +def motion_model(x, u): |
| 62 | + F = np.array([[1.0, 0, 0, 0, 0], |
| 63 | + [0, 1.0, 0, 0, 0], |
| 64 | + [0, 0, 1.0, 0, 0], |
| 65 | + [0, 0, 0, 0, 0], |
| 66 | + [0, 0, 0, 0, 1.0]]) |
| 67 | + |
| 68 | + B = np.array([[DT * math.cos(x[2, 0]) * x[4, 0], 0], |
| 69 | + [DT * math.sin(x[2, 0]) * x[4, 0], 0], |
| 70 | + [0.0, DT], |
| 71 | + [1.0, 0.0], |
| 72 | + [0.0, 0.0]]) |
| 73 | + |
| 74 | + x = F @ x + B @ u |
| 75 | + |
| 76 | + return x |
| 77 | + |
| 78 | + |
| 79 | +def observation_model(x): |
| 80 | + H = np.array([ |
| 81 | + [1, 0, 0, 0, 0], |
| 82 | + [0, 1, 0, 0, 0] |
| 83 | + ]) |
| 84 | + z = H @ x |
| 85 | + |
| 86 | + return z |
| 87 | + |
| 88 | + |
| 89 | +def jacob_f(x, u): |
| 90 | + """ |
| 91 | + Jacobian of Motion Model |
| 92 | +
|
| 93 | + motion model |
| 94 | + x_{t+1} = x_t+v*s*dt*cos(yaw) |
| 95 | + y_{t+1} = y_t+v*s*dt*sin(yaw) |
| 96 | + yaw_{t+1} = yaw_t+omega*dt |
| 97 | + v_{t+1} = v{t} |
| 98 | + s_{t+1} = s{t} |
| 99 | + so |
| 100 | + dx/dyaw = -v*s*dt*sin(yaw) |
| 101 | + dx/dv = dt*s*cos(yaw) |
| 102 | + dx/ds = dt*v*cos(yaw) |
| 103 | + dy/dyaw = v*s*dt*cos(yaw) |
| 104 | + dy/dv = dt*s*sin(yaw) |
| 105 | + dy/ds = dt*v*sin(yaw) |
| 106 | + """ |
| 107 | + yaw = x[2, 0] |
| 108 | + v = u[0, 0] |
| 109 | + s = x[4, 0] |
| 110 | + jF = np.array([ |
| 111 | + [1.0, 0.0, -DT * v * s * math.sin(yaw), DT * s * math.cos(yaw), DT * v * math.cos(yaw)], |
| 112 | + [0.0, 1.0, DT * v * s * math.cos(yaw), DT * s * math.sin(yaw), DT * v * math.sin(yaw)], |
| 113 | + [0.0, 0.0, 1.0, 0.0, 0.0], |
| 114 | + [0.0, 0.0, 0.0, 1.0, 0.0], |
| 115 | + [0.0, 0.0, 0.0, 0.0, 1.0]]) |
| 116 | + return jF |
| 117 | + |
| 118 | + |
| 119 | +def jacob_h(): |
| 120 | + jH = np.array([[1, 0, 0, 0, 0], |
| 121 | + [0, 1, 0, 0, 0]]) |
| 122 | + return jH |
| 123 | + |
| 124 | + |
| 125 | +def ekf_estimation(xEst, PEst, z, u): |
| 126 | + # Predict |
| 127 | + xPred = motion_model(xEst, u) |
| 128 | + jF = jacob_f(xEst, u) |
| 129 | + PPred = jF @ PEst @ jF.T + Q |
| 130 | + |
| 131 | + # Update |
| 132 | + jH = jacob_h() |
| 133 | + zPred = observation_model(xPred) |
| 134 | + y = z - zPred |
| 135 | + S = jH @ PPred @ jH.T + R |
| 136 | + K = PPred @ jH.T @ np.linalg.inv(S) |
| 137 | + xEst = xPred + K @ y |
| 138 | + PEst = (np.eye(len(xEst)) - K @ jH) @ PPred |
| 139 | + return xEst, PEst |
| 140 | + |
| 141 | + |
| 142 | +def main(): |
| 143 | + print(__file__ + " start!!") |
| 144 | + |
| 145 | + time = 0.0 |
| 146 | + |
| 147 | + # State Vector [x y yaw v s]' |
| 148 | + xEst = np.zeros((5, 1)) |
| 149 | + xEst[4, 0] = 1.0 # Initial scale factor |
| 150 | + xTrue = np.zeros((5, 1)) |
| 151 | + true_scale_factor = 0.9 # True scale factor |
| 152 | + xTrue[4, 0] = true_scale_factor |
| 153 | + PEst = np.eye(5) |
| 154 | + |
| 155 | + xDR = np.zeros((5, 1)) # Dead reckoning |
| 156 | + |
| 157 | + # history |
| 158 | + hxEst = xEst |
| 159 | + hxTrue = xTrue |
| 160 | + hxDR = xTrue |
| 161 | + hz = np.zeros((2, 1)) |
| 162 | + |
| 163 | + while SIM_TIME >= time: |
| 164 | + time += DT |
| 165 | + u = calc_input() |
| 166 | + |
| 167 | + xTrue, z, xDR, ud = observation(xTrue, xDR, u) |
| 168 | + |
| 169 | + xEst, PEst = ekf_estimation(xEst, PEst, z, ud) |
| 170 | + |
| 171 | + # store data history |
| 172 | + hxEst = np.hstack((hxEst, xEst)) |
| 173 | + hxDR = np.hstack((hxDR, xDR)) |
| 174 | + hxTrue = np.hstack((hxTrue, xTrue)) |
| 175 | + hz = np.hstack((hz, z)) |
| 176 | + estimated_scale_factor = hxEst[4, -1] |
| 177 | + if show_animation: |
| 178 | + plt.cla() |
| 179 | + # for stopping simulation with the esc key. |
| 180 | + plt.gcf().canvas.mpl_connect('key_release_event', |
| 181 | + lambda event: [exit(0) if event.key == 'escape' else None]) |
| 182 | + plt.plot(hz[0, :], hz[1, :], ".g") |
| 183 | + plt.plot(hxTrue[0, :].flatten(), |
| 184 | + hxTrue[1, :].flatten(), "-b") |
| 185 | + plt.plot(hxDR[0, :].flatten(), |
| 186 | + hxDR[1, :].flatten(), "-k") |
| 187 | + plt.plot(hxEst[0, :].flatten(), |
| 188 | + hxEst[1, :].flatten(), "-r") |
| 189 | + plt.text(0.45, 0.85, f"True Velocity Scale Factor: {true_scale_factor:.2f}", ha='left', va='top', transform=plt.gca().transAxes) |
| 190 | + plt.text(0.45, 0.95, f"Estimated Velocity Scale Factor: {estimated_scale_factor:.2f}", ha='left', va='top', transform=plt.gca().transAxes) |
| 191 | + plot_covariance_ellipse(xEst[0, 0], xEst[1, 0], PEst) |
| 192 | + plt.axis("equal") |
| 193 | + plt.grid(True) |
| 194 | + plt.pause(0.001) |
| 195 | + |
| 196 | + |
| 197 | +if __name__ == '__main__': |
| 198 | + main() |
0 commit comments