Skip to content

Instantly share code, notes, and snippets.

@AakashKumarNain
Last active April 12, 2017 07:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save AakashKumarNain/9385d3041796187991d1667682a3e07f to your computer and use it in GitHub Desktop.
Save AakashKumarNain/9385d3041796187991d1667682a3e07f to your computer and use it in GitHub Desktop.
Extended Kalman Filter
#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;
}
#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_ */
#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_ */
#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_;
}
#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_ */
#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;
}
#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;
}
#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