** code and text not yet correct - ignore!!! **
If you can trust your sensor to never malfunction or provide a spurious measurement you are lucky. Skip to the next section. The reality is that sensors are imperfect. Stray voltages affect signals, birds fly in front of distance sensors. Computer vision sensors mistake shadows for pedestrians. And so on.
Entire books are devoted to this topic. I've found that books which address multi-target tracking with radar to be particularly useful. I'll just address a couple of points that will greatly improve your filters with only a tiny amount of theory and code.
We've discussed the likelihood function. Recall Bayes rule:
$$\text{posterior} = \frac{\text{likelihood}\times\text{prior}}{\text{evidence}}$$where the likelihood is defined as
$$\mathcal L = p(\mathbf z \mid \mathbf x)$$That is simply the likelihood (probability) of the measurements given the prior $\mathbf x$. This suggests a trivial gating function. Here gating means a function that accepts or rejects the measurement based on some criteria. We assume that the measurement noise is Gaussian. Recall from the Gaussians chapter that 99.7% of all values fall within 3 standard deviations of the mean. If the measurement $\mathbf z > 3 \sigma$ we can discard it as being very unlikely. Easy.
In practice you probably shouldn't do that. We model the sensors as being Gaussian, but in practice they probably aren't. The tails might be thick, it might be a slightly different distribution such as Students t-distribution, and so on. For example, NASA uses between $5\sigma$ and $6\sigma$ to accommodate the true behavior of the sensors on the Orion mission. Chances are your budget is less than NASA's, but I suspect the stakes are commensurately less. There is no way to analytically determine the correct value. You will need to run experiments with your data to see what value is reasonable for your application.
from math import sqrt
def gated_fusion(pos_data, vel_data, wheel_std, ps_std, gate=3.):
kf = KalmanFilter(dim_x=2, dim_z=1)
kf.F = array([[1., 1.], [0., 1.]])
kf.H = array([[1., 0.], [1., 0.]])
kf.x = array([[0.], [1.]])
kf.P *= 100
xs, ts = [], []
# copy data for plotting
zs_wheel = np.array(vel_data)
zs_ps = np.array(pos_data)
last_t = 0
while len(pos_data) > 0 and len(vel_data) > 0:
if pos_data[0][0] < vel_data[0][0]:
t, z = pos_data.pop(0)
dt = t - last_t
last_t = t
p_index = 0
kf.H = np.array([[1., 0.]])
kf.R[0,0] = ps_std**2
si
else:
t, z = vel_data.pop(0)
dt = t - last_t
last_t = t
p_index = 1
kf.H = np.array([[0., 1.]])
kf.R[0,0] = wheel_std**2
kf.F[0,1] = dt
kf.Q = Q_discrete_white_noise(2, dt=dt, var=.02)
kf.predict()
std = sqrt(kf.P[p_index, p_index])
y = abs(kf.residual_of(z))
if y <= std * gate:
kf.update(np.array([z]))
else:
print('skip', p_index, kf.x.T, kf.P.diagonal(), "%.3f" % z, y)
xs.append(kf.x.T[0])
ts.append(t)
plot_fusion(xs, ts, zs_ps, zs_wheel)
random.seed(1123)
pos_data, vel_data = gen_sensor_data(25, 1.5, .3)
gated_fusion(pos_data, vel_data, 1.5, 3.0, gate=4.);