Lab 7 provides a more robust solution to the state estimation task in Lab 5. Using Kalman Filter, the states between ToF sensor updates are expected to be more reliable.
You can click on the image to view it in a larger window.
This lab introduces techniques of picking the state representation \( X\in \mathbb{R}^{n} \), control input representation \( u \in \mathbb{R}^l \), observation \( o \in \mathbb{R}^m \) and characterizing these matrices:
\( A \in \mathbb{R}^{n\times n}\): state transition matrix;
\( B \in \mathbb{R}^{l\times n} \): control input matrix;
\( C \in \mathbb{R}^{m\times n} \): observation matrix;
as well as other noise matrices. Finally, we will be able to predict the ToF reading in addition to the slow update of the ToF sensor.
Having those params, we can directly initialize the KF filter matrices along with defining the state as [distance, speed].
\( \left[ \begin{matrix} x \\ \dot{x} \end{matrix} \right] = \left[ \begin{matrix} 1 & dt \\ 0 & 1 - dt\cdot\frac{d}{m} \end{matrix}\right]\cdot \left[ \begin{matrix} x \\ \dot{x} \end{matrix} \right] + \left[ \begin{matrix} 0 \\ dt\cdot\frac{1}{m} \end{matrix} \right]\cdot u \)
The Kalman Filter params can be stored using a python object. Due to the unfixed time interval between ToF data, the A and B matrices below remain updated in real-time.
u = 183/255 # maxPWM count
vel = 1.8 # m/s
d = u/vel
dt = 1.125 # 90% rise time, sec. 1.125.
m = -dt*d/(np.log(0.1))
class KF():
def __init__(self, d, m, sigma1, sigma2, sigma3):
self.d = d
self.m = m
self.R = np.diag([sigma1 ** 2, sigma2 ** 2])
self.Q = np.array([sigma3 ** 2])
self.C = C = np.array([[1],
[0]])
def A(self, dtloop):
A = np.array([[1, dtloop],
[0, 1 - dtloop*self.d/self.m]])
return A
def B(self, dtloop):
B = np.array([[0],
[dtloop/self.m]])
return B
Now we use the following code to implement the Kalman Filter updates.
# Kalman Filter Update
KFStates = []
KFSigmas = []
myKF = KF(d, m, 0.05, 0.05, 0.02)
Sigma = np.diag([0.001, 0.001])
KFSigmas.append(Sigma)
KFPreds = []
for i, oneData in enumerate(data):
# prediction step
oneData = oneData.copy()
oneData[1] *= (-0.001) # convert to nagative meters.
oneData[0] *= 0.001 # convert to seconds.
oneData[2] = oneData[2] / 255
if i == 0:
lastTime = oneData[0]
X = np.array([[oneData[1]],
[oneData[2]]])
KFStates.append(X)
KFPreds.append(X)
continue
dt = (oneData[0] - lastTime)
# print(dt)
lastTime = oneData[0]
Xpred = myKF.A(dt).dot(X) + myKF.B(dt)*(oneData[2]) # mean update.
KFPreds.append(Xpred)
SigmaPred = myKF.A(dt).dot(Sigma).dot(myKF.A(dt).T) + myKF.R # covariance update.
# calculate Kalman Gain.
inv = np.linalg.inv(myKF.C.T.dot(SigmaPred).dot(myKF.C) + myKF.Q) # 1x1
K = SigmaPred.dot(myKF.C).dot(inv) # 2x1
# update state belief.
X = Xpred + K.dot(oneData[1] - myKF.C.T.dot(Xpred)) # convert observation to meters.
KFStates.append(X)
# update the uncertainty.
Sigma = (np.eye(2) - K.dot(myKF.C.T)).dot(SigmaPred)
KFSigmas.append(Sigma)
error = np.sum(np.abs(data[:, 1] / 1000 + KFStates[:, 0])) / 50
# Kalman Filter Update
KFStates = []
KFSigmas = []
myKF = KF(d, m, 0.03, 0.03, 0.02)
Sigma = np.diag([0.001, 0.001])
KFSigmas.append(Sigma)
KFPreds = []
KFPredsStamps = []
dtInt = 15/1000 # sec.
for i, oneData in enumerate(data):
# prediction step
oneData = oneData.copy()
oneData[1] *= (-0.001) # convert to nagative meters.
oneData[0] *= 0.001 # convert to seconds.
oneData[2] = oneData[2] / 255
if i == 0:
lastTime = oneData[0]
X = np.array([[oneData[1]],
[oneData[2]]]) # store the initial PWM as the initial speed.
KFStates.append(X)
KFPreds.append(X)
KFPredsStamps.append(lastTime)
lastControl = oneData[2]
continue
# interpolation
while(lastTime + dtInt < oneData[0]): # no new data.
lastTime += dtInt
dt = dtInt
Xpred = myKF.A(dt).dot(X) + myKF.B(dt)*(lastControl) # mean update.
KFPreds.append(Xpred)
KFPredsStamps.append(lastTime)
SigmaPred = myKF.A(dt).dot(Sigma).dot(myKF.A(dt).T) + myKF.R # covariance update.
dt = oneData[0] - lastTime
lastTime = oneData[0]
Xpred = myKF.A(dt).dot(X) + myKF.B(dt)*(oneData[2]) # mean update.
KFPreds.append(Xpred)
KFPredsStamps.append(lastTime)
SigmaPred = myKF.A(dt).dot(Sigma).dot(myKF.A(dt).T) + myKF.R # covariance update.
# calculate Kalman Gain.
inv = np.linalg.inv(myKF.C.T.dot(SigmaPred).dot(myKF.C) + myKF.Q) # 1x1
# print("inv", inv)
K = SigmaPred.dot(myKF.C).dot(inv) # 2x1
# print("K:\n", K)
# update state belief.
X = Xpred + K.dot(oneData[1] - myKF.C.T.dot(Xpred)) # convert observation to meters.
# print("X: ", X)
KFStates.append(X)
# update the uncertainty.
Sigma = (np.eye(2) - K.dot(myKF.C.T)).dot(SigmaPred)
KFSigmas.append(Sigma)
lastControl = oneData[2]
THE END