In this project we will utilize a kalman filter to estimate the state of a moving object of interest with noisy lidar and radar measurements.
This project involves the Term 2 Simulator which can be downloaded here
This repository includes two files that can be used to set up and install uWebSocketIO for either Linux or Mac systems. For windows you can use either Docker, VMware, or even Windows 10 Bash on Ubuntu to install uWebSocketIO. Please see this concept in the classroom for the required version and installation scripts.
Once the install for uWebSocketIO is complete, the main program can be built and run by doing the following from the project top directory.
- mkdir build
- cd build
- cmake ..
- make
- ./ExtendedKF
- cmake >= 3.5
- All OSes: click here for installation instructions
- make >= 4.1 (Linux, Mac), 3.81 (Windows)
- Linux: make is installed by default on most Linux distros
- Mac: install Xcode command line tools to get make
- Windows: Click here for installation instructions
- gcc/g++ >= 5.4
- Linux: gcc / g++ is installed by default on most Linux distros
- Mac: same deal as make - install Xcode command line tools
- Windows: recommend using MinGW
- Clone this repo.
- Make a build directory:
mkdir build && cd build
- Compile:
cmake .. && make
- On windows, you may need to run:
cmake .. -G "Unix Makefiles" && make
- On windows, you may need to run:
- Run it:
./ExtendedKF
The Kalman Filter algorithm will go through the following steps:
-
First measurement - the filter will receive initial measurements of the bicycle's position relative to the car. These measurements will come from a radar or lidar sensor.
-
Initialize state and covariance matrices - the filter will initialize the bicycle's position based on the first measurement.
-
Then the car will receive another sensor measurement after a time period Δt.
-
Predict - the algorithm will predict where the bicycle will be after time Δt. One basic way to predict the bicycle location after Δt is to assume the bicycle's velocity is constant; thus the bicycle will have moved velocity * Δt. In the extended Kalman filter lesson, we will assume the velocity is constant.
-
Update - the filter compares the "predicted" location with what the sensor measurement says. The predicted location and the measured location are combined to give an updated location. The Kalman filter will put more weight on either the predicted location or the measured location depending on the uncertainty of each value.
-
Then the car will receive another sensor measurement after a time period Δt. The algorithm then does another predict and update step.
The initialization is done in FusionEKF.cpp
file. First we initialize some variables and covariance matrices for radar and laser:
is_initialized_ = false;
previous_timestamp_ = 0;
// initializing matrices
R_laser_ = MatrixXd(2, 2);
R_radar_ = MatrixXd(3, 3);
H_laser_ = MatrixXd(2, 4);
Hj_ = MatrixXd(3, 4);
//measurement covariance matrix - laser
R_laser_ << 0.0225, 0,
0, 0.0225;
//measurement covariance matrix - radar
R_radar_ << 0.09, 0, 0,
0, 0.0009, 0,
0, 0, 0.09;
/**
* Set the process and measurement noises
*/
H_laser_ << 1, 0, 0, 0,
0, 1, 0, 0;
As for lidar we don't measure velocity, we only look for position px
and py
. Next, on initialization we do:
if (!is_initialized_) {
/**
* Initialize the state ekf_.x_ with the first measurement.
* Create the covariance matrix.
* Remember: you'll need to convert radar from polar to cartesian coordinates.
*/
// first measurement
cout << "EKF: " << endl;
ekf_.x_ = VectorXd(4);
ekf_.x_ << 1, 1, 1, 1;
// covariance matrix
ekf_.P_ = MatrixXd(4, 4);
ekf_.P_ << 1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1000, 0,
0, 0, 0, 1000;
//initial transition matrix
ekf_.F_ = MatrixXd(4,4);
ekf_.F_ << 1, 0, 1, 0,
0, 1, 0, 1,
0, 0, 1, 0,
0, 0, 0, 1;
float x = 0.0;
float y = 0.0;
if (measurement_pack.sensor_type_ == MeasurementPackage::RADAR) {
/**
Convert radar from polar to cartesian coordinates and initialize state.
*/
double rho = measurement_pack.raw_measurements_(0);
double theta = measurement_pack.raw_measurements_(1);
double rho_dot = measurement_pack.raw_measurements_(2);
x = rho * cos(theta);
y = rho * sin(theta);
double vx = rho_dot * cos(theta);
double vy = rho_dot * sin(theta);
ekf_.x_ << x, y, vx, vy;
}
else if (measurement_pack.sensor_type_ == MeasurementPackage::LASER) {
/**
Initialize state.
*/
double x = measurement_pack.raw_measurements_(0);
double y = measurement_pack.raw_measurements_(1);
ekf_.x_ << x, y, 0, 0;
}
previous_timestamp_ = measurement_pack.timestamp_;
// done initializing, no need to predict or update
is_initialized_ = true;
return;
}
We define state params waiting for nextmeasurements. Then, in the next measurement we will call Predict
and Update
the values.
/*****************************************************************************
* Prediction
****************************************************************************/
/**
* Update the state transition matrix F according to the new elapsed time.
- Time is measured in seconds.
* Update the process noise covariance matrix.
* Use noise_ax = 9 and noise_ay = 9 for your Q matrix.
*/
//compute the time elapsed between the current and previous measurements
float dt = (measurement_pack.timestamp_ - previous_timestamp_) / 1000000.0; //dt - expressed in seconds
previous_timestamp_ = measurement_pack.timestamp_;
float dt_2 = dt * dt;
float dt_3 = dt_2 * dt;
float dt_4 = dt_3 * dt;
float noise_ax = 9;
float noise_ay = 9;
//Modify the F matrix so that the time is integrated
ekf_.F_(0, 2) = dt;
ekf_.F_(1, 3) = dt;
//set the process covariance matrix Q
ekf_.Q_ = MatrixXd(4, 4);
ekf_.Q_ << dt_4/4*noise_ax, 0, dt_3/2*noise_ax, 0,
0, dt_4/4*noise_ay, 0, dt_3/2*noise_ay,
dt_3/2*noise_ax, 0, dt_2*noise_ax, 0,
0, dt_3/2*noise_ay, 0, dt_2*noise_ay;
ekf_.Predict();
/*****************************************************************************
* Update
****************************************************************************/
/**
* Use the sensor type to perform the update step.
* Update the state and covariance matrices.
*/
if (measurement_pack.sensor_type_ == MeasurementPackage::RADAR) {
// Radar updates
Tools tools;
ekf_.R_ = R_radar_;
ekf_.H_ = tools.CalculateJacobian(ekf_.x_);
ekf_.UpdateEKF(measurement_pack.raw_measurements_);
} else {
// Laser updates
ekf_.R_ = R_laser_;
ekf_.H_ = H_laser_;
ekf_.Update(measurement_pack.raw_measurements_);
}
// print the output
cout << "x_ = " << ekf_.x_ << endl;
cout << "P_ = " << ekf_.P_ << endl;
In this piece of code we prepare the covariance matrices and we calculate the Jacobian
to predict
and update
then.
This step is done in kalman_filter.cpp
file.
void KalmanFilter::Predict() {
/**
* predict the state
*/
x_ = F_ * x_;
MatrixXd Ft = F_.transpose();
P_ = F_ * P_ * Ft + Q_;
}
This method is pretty straight, we predict the next state of the moving object.
Next, we have the update step which is done in kalman_filter.cpp
file.
void KalmanFilter::Update(const VectorXd &z) {
/**
* update the state by using Kalman Filter equations
*/
VectorXd z_pred = H_ * x_;
VectorXd y = z - z_pred;
MatrixXd Ht = H_.transpose();
MatrixXd S = H_ * P_ * Ht + R_;
MatrixXd Si = S.inverse();
MatrixXd PHt = P_ * Ht;
MatrixXd K = PHt * Si;
//new estimate
x_ = x_ + (K * y);
long x_size = x_.size();
MatrixXd I = MatrixXd::Identity(x_size, x_size);
P_ = (I - K * H_) * P_;
}
void KalmanFilter::UpdateEKF(const VectorXd &z) {
/**
* update the state by using Extended Kalman Filter equations
*/
double px = x_(0);
double py = x_(1);
double vx = x_(2);
double vy = x_(3);
double rho = sqrt(px * px + py * py);
if(rho < .00001) {
px += .001;
py += .001;
rho = sqrt(px * px + py * py);
}
double phi = atan2(py, px);
double rho_dot = (px * vx + py * vy) / rho;
VectorXd h = VectorXd(3);
h << rho, phi, rho_dot;
VectorXd y = z - h;
// Normalize the angle
while (y(1) > M_PI) {
y(1) -= 2*M_PI;
}
while (y(1) < - M_PI) {
y(1) += 2*M_PI;
}
MatrixXd Ht = H_.transpose();
MatrixXd S = H_ * P_ * Ht + R_;
MatrixXd Si = S.inverse();
MatrixXd PHt = P_ * Ht;
MatrixXd K = PHt * Si;
//new estimate
x_ = x_ + (K * y);
long x_size = x_.size();
MatrixXd I = MatrixXd::Identity(x_size, x_size);
P_ = (I - K * H_) * P_;
}
Here we have two methods. Depending on the measurement type (radar or lidar) we will call Update
(Standard Kalman Filter Update) or UpdateEKF
(Extended Kalman Filter Update) methods. The difference between this methods is that, in UpdateEKF
we need to calculate the z_pred
(which in this case is h
) prediction by ourself.
After testing with the 2 datasets provided by the simulator, the RMSE
results were:
Input | RMSE |
---|---|
px | 0.0974 |
py | 0.0855 |
vx | 0.4517 |
vy | 0.4404 |
Input | RMSE |
---|---|
px | 0.0726 |
py | 0.0965 |
vx | 0.4216 |
vy | 0.4932 |