Last active
April 12, 2017 07:16
-
-
Save AakashKumarNain/9385d3041796187991d1667682a3e07f to your computer and use it in GitHub Desktop.
Extended Kalman Filter
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "FusionEKF.h" | |
#include "tools.h" | |
#include "Eigen/Dense" | |
#include <iostream> | |
using namespace std; | |
using Eigen::MatrixXd; | |
using Eigen::VectorXd; | |
using std::vector; | |
/* | |
* Constructor. | |
*/ | |
FusionEKF::FusionEKF() { | |
is_initialized_ = false; | |
long long previous_timestamp_ = 0; | |
// initializing matrices | |
/*R_laser_ = MatrixXd(2, 2); | |
R_radar_ = MatrixXd(3, 3); | |
H_laser_ = MatrixXd(2, 4); | |
Hj_ = MatrixXd(3, 4);*/ | |
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; | |
/** | |
TODO: | |
* Finish initializing the FusionEKF. | |
* Set the process and measurement noises | |
*/ | |
H_laser_ << 1,0,0,0, | |
0,1,0,0; | |
Hj_ << 1,0,0,0, | |
0,1,0,0, | |
0,0,1,0; | |
// std::cout<<Hj_; | |
} | |
/** | |
* Destructor. | |
*/ | |
FusionEKF::~FusionEKF() {} | |
void FusionEKF::ProcessMeasurement(const MeasurementPackage &measurement_pack) { | |
float rho; | |
float phi; | |
float rhodot; | |
float px; | |
float py; | |
float vx; | |
float vy; | |
/***************************************************************************** | |
* Initialization | |
****************************************************************************/ | |
if (!is_initialized_) { | |
/** | |
TODO: | |
* 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; | |
// Initialize transition matrix F | |
ekf_.F_ = MatrixXd(4,4); | |
ekf_.F_ << 1,0,1,0, | |
0,1,0,1, | |
0,0,1,0, | |
0,0,0,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; | |
ekf_.Q_ = MatrixXd(4,4); | |
if (measurement_pack.sensor_type_ == MeasurementPackage::RADAR) { | |
/** | |
Convert radar from polar to cartesian coordinates and initialize state. | |
*/ | |
rho = measurement_pack.raw_measurements_(0); | |
phi = measurement_pack.raw_measurements_(1); | |
rhodot = measurement_pack.raw_measurements_(2); | |
px = rho * cos(phi); | |
py = rho * sin(phi); | |
vx = rhodot * cos(phi); | |
vy = rhodot * sin(phi); | |
ekf_.x_ << px, py, vx, vy; | |
} | |
else if (measurement_pack.sensor_type_ == MeasurementPackage::LASER) { | |
/** | |
Initialize state. | |
*/ | |
ekf_.x_ << measurement_pack.raw_measurements_(0), measurement_pack.raw_measurements_(1), 0.0,0.0; | |
previous_timestamp_ = measurement_pack.timestamp_; | |
} | |
// done initializing, no need to predict or update | |
is_initialized_ = true; | |
return; | |
} | |
/***************************************************************************** | |
* Prediction | |
****************************************************************************/ | |
/** | |
TODO: | |
* 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. | |
*/ | |
//elapsed time | |
float dt = (measurement_pack.timestamp_ - previous_timestamp_)/1000000.0; | |
//initialize some variables to get a clean code | |
float dt_2 = dt * dt; | |
float dt_3 = dt_2 * dt; | |
float dt_4 = dt_3 * dt; | |
float noise_ax = 9.0; | |
float noise_ay = 9.0; | |
previous_timestamp_ = measurement_pack.timestamp_; | |
//Update the Q and F matrix | |
ekf_.F_ << 1,0,dt,0, | |
0,1,0,dt, | |
0,0,1,0, | |
0,0,0,1; | |
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 | |
****************************************************************************/ | |
/** | |
TODO: | |
* Use the sensor type to perform the update step. | |
* Update the state and covariance matrices. | |
*/ | |
if (measurement_pack.sensor_type_ == MeasurementPackage::RADAR) | |
{ | |
// Radar updates | |
rho = measurement_pack.raw_measurements_(0); | |
phi = measurement_pack.raw_measurements_(1); | |
rhodot = measurement_pack.raw_measurements_(2); | |
VectorXd z_Radar(3); | |
z_Radar << rho, phi, rhodot; | |
// For calculating JacobianMatrix, create an instance of Tools pacakge | |
Tools t; | |
// Define the Hj Matrix | |
ekf_.H_ = t.CalculateJacobian(ekf_.x_); | |
//Get the measurement covariance matrix | |
ekf_.R_ = R_radar_; | |
//Update the sensor measurements | |
ekf_.UpdateEKF(z_Radar); | |
} | |
else | |
{ | |
// Laser updates | |
VectorXd z_Laser(2); | |
z_Laser << measurement_pack.raw_measurements_(0), measurement_pack.raw_measurements_(1); | |
ekf_.H_ = H_laser_ ; // H matrix for the case of laser | |
ekf_.R_ = R_laser_ ;// measurement covariance matrix | |
ekf_.UpdateEKF(z_Laser); | |
} | |
// print the output | |
cout << "x_ = " << ekf_.x_ << endl; | |
cout << "P_ = " << ekf_.P_ << endl; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#ifndef FusionEKF_H_ | |
#define FusionEKF_H_ | |
#include "measurement_package.h" | |
#include "Eigen/Dense" | |
#include <vector> | |
#include <string> | |
#include <fstream> | |
#include "kalman_filter.h" | |
#include "tools.h" | |
class FusionEKF { | |
public: | |
/** | |
* Constructor. | |
*/ | |
FusionEKF(); | |
/** | |
* Destructor. | |
*/ | |
virtual ~FusionEKF(); | |
/** | |
* Run the whole flow of the Kalman Filter from here. | |
*/ | |
void ProcessMeasurement(const MeasurementPackage &measurement_pack); | |
/** | |
* Kalman Filter update and prediction math lives in here. | |
*/ | |
KalmanFilter ekf_; | |
private: | |
// check whether the tracking toolbox was initiallized or not (first measurement) | |
bool is_initialized_; | |
// previous timestamp | |
long long previous_timestamp_; | |
// tool object used to compute Jacobian and RMSE | |
Tools tools; | |
Eigen::MatrixXd R_laser_; | |
Eigen::MatrixXd R_radar_; | |
Eigen::MatrixXd H_laser_; | |
Eigen::MatrixXd Hj_; | |
}; | |
#endif /* FusionEKF_H_ */ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#ifndef GROUND_TRUTH_PACKAGE_H_ | |
#define GROUND_TRUTH_PACKAGE_H_ | |
#include "Eigen/Dense" | |
class GroundTruthPackage { | |
public: | |
long timestamp_; | |
enum SensorType{ | |
LASER, | |
RADAR | |
} sensor_type_; | |
Eigen::VectorXd gt_values_; | |
}; | |
#endif /* GROUND_TRUTH_PACKAGE_H_ */ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "kalman_filter.h" | |
using Eigen::MatrixXd; | |
using Eigen::VectorXd; | |
KalmanFilter::KalmanFilter() {} | |
KalmanFilter::~KalmanFilter() {} | |
void KalmanFilter::Init(VectorXd &x_in, MatrixXd &P_in, MatrixXd &F_in, | |
MatrixXd &H_in, MatrixXd &R_in, MatrixXd &Q_in) { | |
x_ = x_in; | |
P_ = P_in; | |
F_ = F_in; | |
H_ = H_in; | |
R_ = R_in; | |
Q_ = Q_in; | |
} | |
void KalmanFilter::Predict() { | |
/** | |
TODO: | |
* predict the state | |
*/ | |
x_ = F_ * x_; | |
MatrixXd Ft_ = F_.transpose(); | |
P_ = F_ * P_ * Ft_ + Q_; | |
} | |
void KalmanFilter::Update(const VectorXd &z) { | |
/** | |
TODO: | |
* update the state by using Kalman Filter equations | |
*/ | |
MatrixXd 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 estimates | |
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) { | |
/** | |
TODO: | |
* update the state by using Extended Kalman Filter equations | |
*/ | |
float px = x_(0); | |
float py = x_(1); | |
float vx = x_(2); | |
float vy = x_(3); | |
float rho_pred = sqrt(pow(px,2) + pow(py,2)); | |
float phi_pred = 0.0; | |
if (fabs(px) > 0.001) | |
{ | |
float phi_pred = atan2(x_(1), x_(0)); | |
} | |
float rhodot_pred = 0.0; | |
if (fabs(rho_pred) > 0.001) | |
{ | |
rhodot_pred = (px*vx + py*vy) / rho_pred; | |
} | |
VectorXd z_pred(3); | |
z_pred << rho_pred, phi_pred, rhodot_pred; | |
//Now apply the udate equations again | |
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 estimates | |
x_ = x_ + (K * y); | |
long x_size = x_.size(); | |
MatrixXd I = MatrixXd :: Identity(x_size, x_size); | |
P_ = (I - K * H_) * P_; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#ifndef KALMAN_FILTER_H_ | |
#define KALMAN_FILTER_H_ | |
#include "Eigen/Dense" | |
class KalmanFilter { | |
public: | |
// state vector | |
Eigen::VectorXd x_; | |
// state covariance matrix | |
Eigen::MatrixXd P_; | |
// state transistion matrix | |
Eigen::MatrixXd F_; | |
// process covariance matrix | |
Eigen::MatrixXd Q_; | |
// measurement matrix | |
Eigen::MatrixXd H_; | |
// measurement covariance matrix | |
Eigen::MatrixXd R_; | |
/** | |
* Constructor | |
*/ | |
KalmanFilter(); | |
/** | |
* Destructor | |
*/ | |
virtual ~KalmanFilter(); | |
/** | |
* Init Initializes Kalman filter | |
* @param x_in Initial state | |
* @param P_in Initial state covariance | |
* @param F_in Transition matrix | |
* @param H_in Measurement matrix | |
* @param R_in Measurement covariance matrix | |
* @param Q_in Process covariance matrix | |
*/ | |
void Init(Eigen::VectorXd &x_in, Eigen::MatrixXd &P_in, Eigen::MatrixXd &F_in, | |
Eigen::MatrixXd &H_in, Eigen::MatrixXd &R_in, Eigen::MatrixXd &Q_in); | |
/** | |
* Prediction Predicts the state and the state covariance | |
* using the process model | |
* @param delta_T Time between k and k+1 in s | |
*/ | |
void Predict(); | |
/** | |
* Updates the state by using standard Kalman Filter equations | |
* @param z The measurement at k+1 | |
*/ | |
void Update(const Eigen::VectorXd &z); | |
/** | |
* Updates the state by using Extended Kalman Filter equations | |
* @param z The measurement at k+1 | |
*/ | |
void UpdateEKF(const Eigen::VectorXd &z); | |
}; | |
#endif /* KALMAN_FILTER_H_ */ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <fstream> | |
#include <iostream> | |
#include <sstream> | |
#include <vector> | |
#include <stdlib.h> | |
#include "Eigen/Dense" | |
#include "FusionEKF.h" | |
#include "ground_truth_package.h" | |
#include "measurement_package.h" | |
using namespace std; | |
using Eigen::MatrixXd; | |
using Eigen::VectorXd; | |
using std::vector; | |
void check_arguments(int argc, char* argv[]) { | |
string usage_instructions = "Usage instructions: "; | |
usage_instructions += argv[0]; | |
usage_instructions += " path/to/input.txt output.txt"; | |
bool has_valid_args = false; | |
// make sure the user has provided input and output files | |
if (argc == 1) { | |
cerr << usage_instructions << endl; | |
} else if (argc == 2) { | |
cerr << "Please include an output file.\n" << usage_instructions << endl; | |
} else if (argc == 3) { | |
has_valid_args = true; | |
} else if (argc > 3) { | |
cerr << "Too many arguments.\n" << usage_instructions << endl; | |
} | |
if (!has_valid_args) { | |
exit(EXIT_FAILURE); | |
} | |
} | |
void check_files(ifstream& in_file, string& in_name, | |
ofstream& out_file, string& out_name) { | |
if (!in_file.is_open()) { | |
cerr << "Cannot open input file: " << in_name << endl; | |
exit(EXIT_FAILURE); | |
} | |
if (!out_file.is_open()) { | |
cerr << "Cannot open output file: " << out_name << endl; | |
exit(EXIT_FAILURE); | |
} | |
} | |
int main(int argc, char* argv[]) { | |
check_arguments(argc, argv); | |
string in_file_name_ = argv[1]; | |
ifstream in_file_(in_file_name_.c_str(), ifstream::in); | |
string out_file_name_ = argv[2]; | |
ofstream out_file_(out_file_name_.c_str(), ofstream::out); | |
check_files(in_file_, in_file_name_, out_file_, out_file_name_); | |
vector<MeasurementPackage> measurement_pack_list; | |
vector<GroundTruthPackage> gt_pack_list; | |
string line; | |
// prep the measurement packages (each line represents a measurement at a | |
// timestamp) | |
while (getline(in_file_, line)) { | |
string sensor_type; | |
MeasurementPackage meas_package; | |
GroundTruthPackage gt_package; | |
istringstream iss(line); | |
long long timestamp; | |
// reads first element from the current line | |
iss >> sensor_type; | |
if (sensor_type.compare("L") == 0) { | |
// LASER MEASUREMENT | |
// read measurements at this timestamp | |
meas_package.sensor_type_ = MeasurementPackage::LASER; | |
meas_package.raw_measurements_ = VectorXd(2); | |
float x; | |
float y; | |
iss >> x; | |
iss >> y; | |
meas_package.raw_measurements_ << x, y; | |
iss >> timestamp; | |
meas_package.timestamp_ = timestamp; | |
measurement_pack_list.push_back(meas_package); | |
} else if (sensor_type.compare("R") == 0) { | |
// RADAR MEASUREMENT | |
// read measurements at this timestamp | |
meas_package.sensor_type_ = MeasurementPackage::RADAR; | |
meas_package.raw_measurements_ = VectorXd(3); | |
float ro; | |
float phi; | |
float ro_dot; | |
iss >> ro; | |
iss >> phi; | |
iss >> ro_dot; | |
meas_package.raw_measurements_ << ro, phi, ro_dot; | |
iss >> timestamp; | |
meas_package.timestamp_ = timestamp; | |
measurement_pack_list.push_back(meas_package); | |
} | |
// read ground truth data to compare later | |
float x_gt; | |
float y_gt; | |
float vx_gt; | |
float vy_gt; | |
iss >> x_gt; | |
iss >> y_gt; | |
iss >> vx_gt; | |
iss >> vy_gt; | |
gt_package.gt_values_ = VectorXd(4); | |
gt_package.gt_values_ << x_gt, y_gt, vx_gt, vy_gt; | |
gt_pack_list.push_back(gt_package); | |
} | |
// Create a Fusion EKF instance | |
FusionEKF fusionEKF; | |
// used to compute the RMSE later | |
vector<VectorXd> estimations; | |
vector<VectorXd> ground_truth; | |
//Call the EKF-based fusion | |
size_t N = measurement_pack_list.size(); | |
for (size_t k = 0; k < N; ++k) { | |
// start filtering from the second frame (the speed is unknown in the first | |
// frame) | |
fusionEKF.ProcessMeasurement(measurement_pack_list[k]); | |
// output the estimation | |
out_file_ << fusionEKF.ekf_.x_(0) << "\t"; | |
out_file_ << fusionEKF.ekf_.x_(1) << "\t"; | |
out_file_ << fusionEKF.ekf_.x_(2) << "\t"; | |
out_file_ << fusionEKF.ekf_.x_(3) << "\t"; | |
// output the measurements | |
if (measurement_pack_list[k].sensor_type_ == MeasurementPackage::LASER) { | |
// output the estimation | |
out_file_ << measurement_pack_list[k].raw_measurements_(0) << "\t"; | |
out_file_ << measurement_pack_list[k].raw_measurements_(1) << "\t"; | |
} else if (measurement_pack_list[k].sensor_type_ == MeasurementPackage::RADAR) { | |
// output the estimation in the cartesian coordinates | |
float ro = measurement_pack_list[k].raw_measurements_(0); | |
float phi = measurement_pack_list[k].raw_measurements_(1); | |
out_file_ << ro * cos(phi) << "\t"; // p1_meas | |
out_file_ << ro * sin(phi) << "\t"; // ps_meas | |
} | |
// output the ground truth packages | |
out_file_ << gt_pack_list[k].gt_values_(0) << "\t"; | |
out_file_ << gt_pack_list[k].gt_values_(1) << "\t"; | |
out_file_ << gt_pack_list[k].gt_values_(2) << "\t"; | |
out_file_ << gt_pack_list[k].gt_values_(3) << "\n"; | |
estimations.push_back(fusionEKF.ekf_.x_); | |
ground_truth.push_back(gt_pack_list[k].gt_values_); | |
} | |
// compute the accuracy (RMSE) | |
Tools tools; | |
cout << "Accuracy - RMSE:" << endl << tools.CalculateRMSE(estimations, ground_truth) << endl; | |
// close files | |
if (out_file_.is_open()) { | |
out_file_.close(); | |
} | |
if (in_file_.is_open()) { | |
in_file_.close(); | |
} | |
return 0; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <iostream> | |
#include "tools.h" | |
using Eigen::VectorXd; | |
using Eigen::MatrixXd; | |
using std::vector; | |
Tools::Tools() {} | |
Tools::~Tools() {} | |
VectorXd Tools::CalculateRMSE(const vector<VectorXd> &estimations, | |
const vector<VectorXd> &ground_truth) { | |
/** | |
TODO: | |
* Calculate the RMSE here. | |
*/ | |
VectorXd rmse(4); | |
rmse << 0, 0, 0, 0; | |
if (estimations.size() != ground_truth.size() || estimations.size() == 0) { | |
std::cout << "Invalid estimation or ground truth data" << std::endl; | |
return rmse; | |
} | |
for (unsigned int i = 0; i < estimations.size(); ++i) { | |
VectorXd residual = estimations[i] - ground_truth[i]; | |
//coefficient wise multiplication | |
residual = residual.array() * residual.array(); | |
rmse += residual; | |
} | |
//Calculate the mean | |
rmse = rmse / estimations.size(); | |
//Calculate the square root | |
rmse = rmse.array().sqrt(); | |
//return rmse | |
return rmse; | |
} | |
MatrixXd Tools::CalculateJacobian(const VectorXd &x_state) { | |
/** | |
TODO: | |
* Calculate a Jacobian here. | |
*/ | |
MatrixXd Hj(3, 4); | |
//State parameters | |
float px = x_state(0); | |
float py = x_state(1); | |
float vx = x_state(2); | |
float vy = x_state(3); | |
//Define some variables so that the calculations don't look cluttered | |
float c1 = (px * px) + (py * py); | |
float c2 = sqrt(c1); | |
float c3 = (c1 * c2); | |
//Check for division by zero | |
if (fabs(c1) <=0.000001) { | |
std::cout << "CalculateJacobian () - Error - Division by Zero" << std::endl; | |
return Hj; | |
} | |
//Calculate the Jacobian matrix values | |
Hj << (px / c2), (py / c2), 0, 0, | |
-(py / c1), (px / c1), 0, 0, | |
py*(vx * py - vy * px)/c3, px*(vy * px - vx * py)/c3, px/c2, py/c2; | |
return Hj; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#ifndef TOOLS_H_ | |
#define TOOLS_H_ | |
#include <vector> | |
#include "Eigen/Dense" | |
class Tools { | |
public: | |
/** | |
* Constructor. | |
*/ | |
Tools(); | |
/** | |
* Destructor. | |
*/ | |
virtual ~Tools(); | |
/** | |
* A helper method to calculate RMSE. | |
*/ | |
Eigen::VectorXd CalculateRMSE(const std::vector<Eigen::VectorXd> &estimations, const std::vector<Eigen::VectorXd> &ground_truth); | |
/** | |
* A helper method to calculate Jacobians. | |
*/ | |
Eigen::MatrixXd CalculateJacobian(const Eigen::VectorXd& x_state); | |
}; | |
#endif /* TOOLS_H_ */ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment