-
Notifications
You must be signed in to change notification settings - Fork 0
/
Extended_Kalman_Filter.py
211 lines (175 loc) · 8.59 KB
/
Extended_Kalman_Filter.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
import numpy as np
# Description: Extended Kalman Filter
# Supress scientific notation when printing NumPy arrays
np.set_printoptions(precision=3,suppress=True)
# A matrix
# 3x3 matrix -> number of states x number of states matrix
# Expresses how the state of the system [x,y,yaw] changes
# from k-1 to k when no control command is executed.
# Typically a robot on wheels only drives when the wheels are told to turn.
# For this case, A is the identity matrix.
# A is sometimes F in the literature.
A_k_minus_1 = np.array([[1.0, 0, 0],
[ 0,1.0, 0],
[ 0, 0, 1.0]])
# Noise applied to the forward kinematics (calculation
# of the estimated state at time k from the state
# transition model of the mobile robot). This is a vector
# with the number of elements equal to the number of states
process_noise_v_k_minus_1 = np.array([0.01,0.01,0.003])
# State model noise covariance matrix Q_k
# When Q is large, the Kalman Filter tracks large changes in
# the sensor measurements more closely than for smaller Q.
# Q is a square matrix that has the same number of rows as states.
Q_k = np.array([[1.0, 0, 0],
[ 0, 1.0, 0],
[ 0, 0, 1.0]])
# Measurement matrix H_k
# Used to convert the predicted state estimate at time k
# into predicted sensor measurements at time k.
# In this case, H will be the identity matrix since the
# estimated state maps directly to state measurements from the
# odometry data [x, y, yaw]
# H has the same number of rows as sensor measurements
# and same number of columns as states.
H_k = np.array([[1.0, 0, 0],
[ 0,1.0, 0],
[ 0, 0, 1.0]])
# Sensor measurement noise covariance matrix R_k
# Has the same number of rows and columns as sensor measurements.
# If we are sure about the measurements, R will be near zero.
R_k = np.array([[1.0, 0, 0],
[ 0, 1.0, 0],
[ 0, 0, 1.0]])
# Sensor noise. This is a vector with the
# number of elements equal to the number of sensor measurements.
sensor_noise_w_k = np.array([0.07,0.07,0.04])
def getB(yaw, deltak):
"""
Calculates and returns the B matrix
3x2 matix -> number of states x number of control inputs
The control inputs are the forward speed and the
rotation rate around the z axis from the x-axis in the
counterclockwise direction.
[v,yaw_rate]
Expresses how the state of the system [x,y,yaw] changes
from k-1 to k due to the control commands (i.e. control input).
:param yaw: The yaw angle (rotation angle around the z axis) in rad
:param deltak: The change in time from time step k-1 to k in sec
"""
B = np.array([ [np.cos(yaw)*deltak, 0],
[np.sin(yaw)*deltak, 0],
[0, deltak]])
return B
def ekf(z_k_observation_vector, state_estimate_k_minus_1,
control_vector_k_minus_1, P_k_minus_1, dk):
"""
Extended Kalman Filter. Fuses noisy sensor measurement to
create an optimal estimate of the state of the robotic system.
INPUT
:param z_k_observation_vector The observation from the Odometry
3x1 NumPy Array [x,y,yaw] in the global reference frame
in [meters,meters,radians].
:param state_estimate_k_minus_1 The state estimate at time k-1
3x1 NumPy Array [x,y,yaw] in the global reference frame
in [meters,meters,radians].
:param control_vector_k_minus_1 The control vector applied at time k-1
3x1 NumPy Array [v,v,yaw rate] in the global reference frame
in [meters per second,meters per second,radians per second].
:param P_k_minus_1 The state covariance matrix estimate at time k-1
3x3 NumPy Array
:param dk Time interval in seconds
OUTPUT
:return state_estimate_k near-optimal state estimate at time k
3x1 NumPy Array ---> [meters,meters,radians]
:return P_k state covariance_estimate for time k
3x3 NumPy Array
"""
######################### Predict #############################
# Predict the state estimate at time k based on the state
# estimate at time k-1 and the control input applied at time k-1.
state_estimate_k = A_k_minus_1 @ (
state_estimate_k_minus_1) + (
getB(state_estimate_k_minus_1[2],dk)) @ (
control_vector_k_minus_1) + (
process_noise_v_k_minus_1)
print(f'State Estimate Before EKF={state_estimate_k}')
# Predict the state covariance estimate based on the previous
# covariance and some noise
P_k = A_k_minus_1 @ P_k_minus_1 @ A_k_minus_1.T + (
Q_k)
################### Update (Correct) ##########################
# Calculate the difference between the actual sensor measurements
# at time k minus what the measurement model predicted
# the sensor measurements would be for the current timestep k.
measurement_residual_y_k = z_k_observation_vector - (
(H_k @ state_estimate_k) + (
sensor_noise_w_k))
print(f'Observation={z_k_observation_vector}')
# Calculate the measurement residual covariance
S_k = H_k @ P_k @ H_k.T + R_k
# Calculate the near-optimal Kalman gain
# We use pseudoinverse since some of the matrices might be
# non-square or singular.
K_k = P_k @ H_k.T @ np.linalg.pinv(S_k)
# Calculate an updated state estimate for time k
state_estimate_k = state_estimate_k + (K_k @ measurement_residual_y_k)
# Update the state covariance estimate for time k
P_k = P_k - (K_k @ H_k @ P_k)
# Print the best (near-optimal) estimate of the current state of the robot
print(f'State Estimate After EKF={state_estimate_k}')
# Return the updated state and covariance estimates
return state_estimate_k, P_k
def main():
# We start at time k=1
k = 1
# Time interval in seconds
dk = 1
# Create a list of sensor observations at successive timesteps
# Each list within z_k is an observation vector.
z_k = np.array([[4.721,0.143,0.006], # k=1
[9.353,0.284,0.007], # k=2
[14.773,0.422,0.009],# k=3
[18.246,0.555,0.011], # k=4
[22.609,0.715,0.012]])# k=5
# The estimated state vector at time k-1 in the global reference frame.
# [x_k_minus_1, y_k_minus_1, yaw_k_minus_1]
# [meters, meters, radians]
state_estimate_k_minus_1 = np.array([0.0,0.0,0.0])
# The control input vector at time k-1 in the global reference frame.
# [v, yaw_rate]
# [meters/second, radians/second]
# In the literature, this is commonly u.
# Because there is no angular velocity and the robot begins at the
# origin with a 0 radians yaw angle, this robot is traveling along
# the positive x-axis in the global reference frame.
control_vector_k_minus_1 = np.array([4.5,0.0])
# State covariance matrix P_k_minus_1
# This matrix has the same number of rows (and columns) as the
# number of states (i.e. 3x3 matrix). P is sometimes referred
# to as Sigma in the literature. It represents an estimate of
# the accuracy of the state estimate at time k made using the
# state transition matrix. We start off with guessed values.
P_k_minus_1 = np.array([[0.1, 0, 0],
[ 0,0.1, 0],
[ 0, 0, 0.1]])
# Start at k=1 and go through each of the 5 sensor observations,
# one at a time.
# We stop right after timestep k=5 (i.e. the last sensor observation)
for k, obs_vector_z_k in enumerate(z_k,start=1):
# Print the current timestep
print(f'Timestep k={k}')
# Run the Extended Kalman Filter and store the
# near-optimal state and covariance estimates
optimal_state_estimate_k, covariance_estimate_k = ekf(
obs_vector_z_k, # Most recent sensor measurement
state_estimate_k_minus_1, # Our most recent estimate of the state
control_vector_k_minus_1, # Our most recent control input
P_k_minus_1, # Our most recent state covariance matrix
dk) # Time interval
# Get ready for the next timestep by updating the variable values
state_estimate_k_minus_1 = optimal_state_estimate_k
P_k_minus_1 = covariance_estimate_k
# Print a blank line
print()
main()