Lab 5' Homework - Sensor Filter and Fusion
Introduction
Sensor fusion is the process of combining uncertain information to obtain an estimate that is more accurate than either source. Please watch the video below for a high level introduction to sensor fusion.
Kalman Filter
The Kalman filter is used to estimate the state of an object. Recall from Tuesday's lecture, the Kalman filter uses two steps: predict the next state and update the estimated state. In this lab, the state of the drone is a 2-dimensional vector containing the altitude and the vertical velocity. We combine information from three different sensors (the gps altitude, the pressure altitude, and velocity) to maintain an estimated altitude and vertical velocity of the drone.
Thus, the state vector has two components, the altitude and the vertical velocity:
Prediction Step
We start by defining the matrices and vectors used in the prediction step equations.
F is the state transition matrix. We assume a linear motion model with constant velocity.
u is the prediction noise. In our case, u is the zero vector, because we assume that the noise is a Gaussian distribution with mean zero.
The matrix P is the process covariance matrix.
The matrix P' is the posterior estimate of the process covariance matrix P.
Q is the covariance matrix of process noise. Since, in fact, the motion is not at constant velocity, but it has a vertical acceleration, the uncertainty caused by acceleration can be added to the noise component.
The kinematic equations for constant acceleration motion are:
The acceleration term is added to the noise:
The matrix Q is the covariance matrix of the normal noise distribution with two terms: a constant term which is the variance of normal distribution (the square of the standard deviation) of the acceleration and a term dependent on the time between measurements. The longer the time, the higher the uncertainty of the predicted position.
The state prediction equations are:
where x' is the predicted state or the posterior state, and x is the state and P' is the posterior estimate of the process covariance matrix.
In your code, x' and x can be represented by the same variable. The same is true for P' and P.
Update Step
The update step equations are the same for all measurements, but the constant matrices must be initialized with different values.
Matrices for the GPS and pressure measurements
For an GPS or pressure altitude measurement, the vector z has exactly one component. If the measurement is of the altitude type, then z is the measured altitude,
he H matrix projects the state vector x' onto the altitude component.
The matrix R is a 1x1 matrix, that is the measurement noise covariance matrix, and it is equal to the square of the standard deviation.
Matrices for the velocity measurements
For a vertical velocity measurement, the vector z has exactly one component.
The H matrix projects the state vector x' onto the vertical velocity component.
The matrix R is a 1x1 matrix, that is the measurement noise covariance matrix, and it is equal to the square of the standard deviation.
In the implementation, the attribute measurement_type
of the measurement (an instance of the class Measurement
) gives you the type of the measurement.
The measurement update equations are:
Implementing the Kalman Filter
Create a new node that uses the Kalman filter to estimate the altitude by fusing the information from three sensors: pressure altitude, gps altitude and velocity. You may use the code below as a starting point. The formulas for predict and update contain matrix operations. Use the numpy library functions for matrix addition, multiplication, transpose, inverse (https://numpy.org/).
The node is implemented in the class KalmanFilter
. When a message is received on any of the three topics corresponding to the three sensors fused, an object of type Measurement
containing the sensor type, the value and the time stamp is added to a queue. The mainloop
of the KalmanFilter
removes an object from the queue and the predict and update steps are executed. You will have to implement the predict
and update
steps, using the formulas described above.
You might notice that in the code below, we do not use locks anymore. We don't need to do it here, because the LifoQueue
is thread-safe. All the complexities dealing with locks are encapsulated in the LifoQueue.
Each subscriber adds a message to the queue
on its own thread. The mainloop
runs on another thread that keeps removing elements from the queue.
#!/usr/bin/env python
import rospy
from geometry_msgs.msg import TwistStamped
from altitude.msg import AltitudeStamped
import numpy as np
from Queue import LifoQueue
from enum import Enum
class MeasurementType(Enum):
PRESSURE_ALTITUDE = 1
GPS_ALTITUDE = 2
VELOCITY = 3
class Measurement:
def __init__(self, measurement_type, value, timestamp):
self.measurement_type = measurement_type
self.value = value
self.timestamp = timestamp
class KalmanFilter():
def __init__(self):
self.last_timestamp = None
# the variance used in the matrix Q
self.process_variance = rospy.get_param("/kalman_filter_node/process_variance", 100)
# the variance used in the matrix R for the GPS measurements
self.gps_variance = rospy.get_param("/kalman_filter_node/gps_variance", 0.1)
# the variance used in the matrix R of the pressure altitude measurements
self.pressure_variance = rospy.get_param("/kalman_filter_node/pressure_variance", 0.1)
$ the variance used in the matrix R of the vertical velocity measurements
self.velocity_variance = rospy.get_param("/kalman_filter_node/velocity_variance", 0.1)
self.queue = LifoQueue()
self.sub_pa = rospy.Subscriber('/uav/sensors/pressure_altitude',
AltitudeStamped, self.process_pressure_altitude,
queue_size = 1)
self.sub_gps_a = rospy.Subscriber('/uav/sensors/gps_altitude',
AltitudeStamped, self.process_gps_altitude,
queue_size = 1)
self.sub_vel = rospy.Subscriber('/uav/sensors/velocity',
TwistStamped, self.process_velocity,
queue_size = 1)
self.pub = rospy.Publisher('/uav/sensors/kalman_filter_altitude',
AltitudeStamped,
queue_size=1)
self.mainloop()
def process_pressure_altitude(self, msg):
self.queue.put(Measurement(MeasurementType.PRESSURE_ALTITUDE,
msg.value,
msg.stamp))
def process_gps_altitude(self, msg):
self.queue.put(Measurement(MeasurementType.GPS_ALTITUDE,
msg.value,
msg.stamp))
def process_velocity(self, msg):
self.queue.put(Measurement(MeasurementType.VELOCITY,
msg.twist.linear.z,
msg.header.stamp))
# The main loop of the node
def mainloop(self):
# Set the rate of this loop
rate = rospy.Rate(20)
# state vector
x = np.zeros((2,1), dtype=float)
# state transition matrix
F = np.zeros((2,2), dtype=float)
F[0][0] = 1
F[1][1] = 1
# process covariance matrix
Q = np.zeros((2,2), dtype=float)
# initialize P
P = np.zeros((2,2), dtype=float)
P[0][0] = 1
P[1][1] = 1000
# initialize I
I = np.zeros((2,2), dtype=float)
I[0][0] = 1
I[1][1] = 1
# While ROS is still running
while not rospy.is_shutdown():
while not self.queue.empty():
measurement = self.queue.get()
if self.last_timestamp:
########### predict step
delta_t = (measurement.timestamp - self.last_timestamp).to_sec()
# TODO: initialize matrices F and Q which depend on the delta_t
# TODO: update the state and the process matrix
########### update step
# TODO: initialize H for the measurement type
# TODO: initialize R for the measurement type
# TODO: initialize the measurement z
z = np.zeros((1,1), dtype=float)
z[0] = #TODO
# TODO: implement the update equations
else:
# first measurement
if measurement.measurement_type == MeasurementType.VELOCITY:
x[1] = measurement.value
else:
x[0] = measurement.value
# update timestamp
self.last_timestamp = measurement.timestamp
# publish message
msg = AltitudeStamped()
msg.value = x[0]
msg.stamp = self.last_timestamp
self.pub.publish(msg)
# Sleep for the remainder of the loop
rate.sleep()
if __name__ == '__main__':
rospy.init_node('kalman_filter_node')
try:
KalmanFilter()
except rospy.ROSInterruptException:
pass
Checkpoint 1
Display the altitude value calculated using the Kalman filter.
1. How does this graph compare with the moving average?
2. What would you change to improve the accuracy of the value calculated using the Kalman filter?
Checkpoint 2
Describe the Kalman filter when the state is a column vector with the following components: x, vx, y, vz, a, v, where x and y are the Cartesian coordinates and vx and vy are the velocities along those coordinates, a is the altitude (z coordinate) and v the corresponding velocity.
The gps sensor provides x,y, and altitude measurements. The velocity sensor provides measurements for vx, vy and v. The pressure altitude sensor provides altitude measurements.
For full credit, your answer must contain the sizes of the matrices and definition of the constant matrices.
Checkpoint 3
Please fill in the following survey: forms.gle/iiSsoSiyz7US9uxR6