325 lines
12 KiB
C++
325 lines
12 KiB
C++
/* ----------------------------------------------------------------------------
|
|
|
|
* GTSAM Copyright 2010, Georgia Tech Research Corporation,
|
|
* Atlanta, Georgia 30332-0415
|
|
* All Rights Reserved
|
|
* Authors: Frank Dellaert, et al. (see THANKS for the full author list)
|
|
|
|
* See LICENSE for the license information
|
|
|
|
* -------------------------------------------------------------------------- */
|
|
|
|
/**
|
|
* @file ImuFactorsExample
|
|
* @brief Test example for using GTSAM ImuFactor and ImuCombinedFactor
|
|
* navigation code.
|
|
* @author Garrett (ghemann@gmail.com), Luca Carlone
|
|
*/
|
|
|
|
/**
|
|
* Example of use of the imuFactors (imuFactor and combinedImuFactor) in
|
|
* conjunction with GPS
|
|
* - imuFactor is used by default. You can test combinedImuFactor by
|
|
* appending a `-c` flag at the end (see below for example command).
|
|
* - we read IMU and GPS data from a CSV file, with the following format:
|
|
* A row starting with "i" is the first initial position formatted with
|
|
* N, E, D, qx, qY, qZ, qW, velN, velE, velD
|
|
* A row starting with "0" is an imu measurement
|
|
* (body frame - Forward, Right, Down)
|
|
* linAccX, linAccY, linAccZ, angVelX, angVelY, angVelX
|
|
* A row starting with "1" is a gps correction formatted with
|
|
* N, E, D, qX, qY, qZ, qW
|
|
* Note that for GPS correction, we're only using the position not the
|
|
* rotation. The rotation is provided in the file for ground truth comparison.
|
|
*
|
|
* See usage: ./ImuFactorsExample --help
|
|
*/
|
|
|
|
#include <boost/program_options.hpp>
|
|
|
|
// GTSAM related includes.
|
|
#include <gtsam/inference/Symbol.h>
|
|
#include <gtsam/navigation/CombinedImuFactor.h>
|
|
#include <gtsam/navigation/GPSFactor.h>
|
|
#include <gtsam/navigation/ImuFactor.h>
|
|
#include <gtsam/nonlinear/ISAM2.h>
|
|
#include <gtsam/nonlinear/LevenbergMarquardtOptimizer.h>
|
|
#include <gtsam/nonlinear/NonlinearFactorGraph.h>
|
|
#include <gtsam/slam/BetweenFactor.h>
|
|
#include <gtsam/slam/dataset.h>
|
|
|
|
#include <cstring>
|
|
#include <cassert>
|
|
#include <fstream>
|
|
#include <iostream>
|
|
|
|
using namespace gtsam;
|
|
using namespace std;
|
|
|
|
using symbol_shorthand::B; // Bias (ax,ay,az,gx,gy,gz)
|
|
using symbol_shorthand::V; // Vel (xdot,ydot,zdot)
|
|
using symbol_shorthand::X; // Pose3 (x,y,z,r,p,y)
|
|
|
|
namespace po = boost::program_options;
|
|
|
|
po::variables_map parseOptions(int argc, char* argv[]) {
|
|
po::options_description desc;
|
|
desc.add_options()("help,h", "produce help message")(
|
|
"data_csv_path", po::value<string>()->default_value("imuAndGPSdata.csv"),
|
|
"path to the CSV file with the IMU data")(
|
|
"output_filename",
|
|
po::value<string>()->default_value("imuFactorExampleResults.csv"),
|
|
"path to the result file to use")("use_isam", po::bool_switch(),
|
|
"use ISAM as the optimizer");
|
|
|
|
po::variables_map vm;
|
|
po::store(po::parse_command_line(argc, argv, desc), vm);
|
|
|
|
if (vm.count("help")) {
|
|
cout << desc << "\n";
|
|
exit(1);
|
|
}
|
|
|
|
return vm;
|
|
}
|
|
|
|
std::shared_ptr<PreintegratedCombinedMeasurements::Params> imuParams() {
|
|
// We use the sensor specs to build the noise model for the IMU factor.
|
|
double accel_noise_sigma = 0.0003924;
|
|
double gyro_noise_sigma = 0.000205689024915;
|
|
double accel_bias_rw_sigma = 0.004905;
|
|
double gyro_bias_rw_sigma = 0.000001454441043;
|
|
Matrix33 measured_acc_cov = I_3x3 * pow(accel_noise_sigma, 2);
|
|
Matrix33 measured_omega_cov = I_3x3 * pow(gyro_noise_sigma, 2);
|
|
Matrix33 integration_error_cov =
|
|
I_3x3 * 1e-8; // error committed in integrating position from velocities
|
|
Matrix33 bias_acc_cov = I_3x3 * pow(accel_bias_rw_sigma, 2);
|
|
Matrix33 bias_omega_cov = I_3x3 * pow(gyro_bias_rw_sigma, 2);
|
|
Matrix66 bias_acc_omega_init =
|
|
I_6x6 * 1e-5; // error in the bias used for preintegration
|
|
|
|
auto p = PreintegratedCombinedMeasurements::Params::MakeSharedD(0.0);
|
|
// PreintegrationBase params:
|
|
p->accelerometerCovariance =
|
|
measured_acc_cov; // acc white noise in continuous
|
|
p->integrationCovariance =
|
|
integration_error_cov; // integration uncertainty continuous
|
|
// should be using 2nd order integration
|
|
// PreintegratedRotation params:
|
|
p->gyroscopeCovariance =
|
|
measured_omega_cov; // gyro white noise in continuous
|
|
// PreintegrationCombinedMeasurements params:
|
|
p->biasAccCovariance = bias_acc_cov; // acc bias in continuous
|
|
p->biasOmegaCovariance = bias_omega_cov; // gyro bias in continuous
|
|
p->biasAccOmegaInt = bias_acc_omega_init;
|
|
|
|
return p;
|
|
}
|
|
|
|
int main(int argc, char* argv[]) {
|
|
string data_filename, output_filename;
|
|
|
|
bool use_isam = false;
|
|
|
|
po::variables_map var_map = parseOptions(argc, argv);
|
|
|
|
data_filename = findExampleDataFile(var_map["data_csv_path"].as<string>());
|
|
output_filename = var_map["output_filename"].as<string>();
|
|
use_isam = var_map["use_isam"].as<bool>();
|
|
|
|
ISAM2* isam2 = 0;
|
|
if (use_isam) {
|
|
printf("Using ISAM2\n");
|
|
ISAM2Params parameters;
|
|
parameters.relinearizeThreshold = 0.01;
|
|
parameters.relinearizeSkip = 1;
|
|
isam2 = new ISAM2(parameters);
|
|
|
|
} else {
|
|
printf("Using Levenberg Marquardt Optimizer\n");
|
|
}
|
|
|
|
// Set up output file for plotting errors
|
|
FILE* fp_out = fopen(output_filename.c_str(), "w+");
|
|
fprintf(fp_out,
|
|
"#time(s),x(m),y(m),z(m),qx,qy,qz,qw,gt_x(m),gt_y(m),gt_z(m),gt_qx,"
|
|
"gt_qy,gt_qz,gt_qw\n");
|
|
|
|
// Begin parsing the CSV file. Input the first line for initialization.
|
|
// From there, we'll iterate through the file and we'll preintegrate the IMU
|
|
// or add in the GPS given the input.
|
|
ifstream file(data_filename.c_str());
|
|
string value;
|
|
|
|
// Format is (N,E,D,qX,qY,qZ,qW,velN,velE,velD)
|
|
Vector10 initial_state;
|
|
getline(file, value, ','); // i
|
|
for (int i = 0; i < 9; i++) {
|
|
getline(file, value, ',');
|
|
initial_state(i) = stof(value.c_str());
|
|
}
|
|
getline(file, value, '\n');
|
|
initial_state(9) = stof(value.c_str());
|
|
cout << "initial state:\n" << initial_state.transpose() << "\n\n";
|
|
|
|
// Assemble initial quaternion through GTSAM constructor
|
|
// ::quaternion(w,x,y,z);
|
|
Rot3 prior_rotation = Rot3::Quaternion(initial_state(6), initial_state(3),
|
|
initial_state(4), initial_state(5));
|
|
Point3 prior_point(initial_state.head<3>());
|
|
Pose3 prior_pose(prior_rotation, prior_point);
|
|
Vector3 prior_velocity(initial_state.tail<3>());
|
|
imuBias::ConstantBias prior_imu_bias; // assume zero initial bias
|
|
|
|
Values initial_values;
|
|
int correction_count = 0;
|
|
initial_values.insert(X(correction_count), prior_pose);
|
|
initial_values.insert(V(correction_count), prior_velocity);
|
|
initial_values.insert(B(correction_count), prior_imu_bias);
|
|
|
|
// Assemble prior noise model and add it the graph.`
|
|
auto pose_noise_model = noiseModel::Diagonal::Sigmas(
|
|
(Vector(6) << 0.01, 0.01, 0.01, 0.5, 0.5, 0.5)
|
|
.finished()); // rad,rad,rad,m, m, m
|
|
auto velocity_noise_model = noiseModel::Isotropic::Sigma(3, 0.1); // m/s
|
|
auto bias_noise_model = noiseModel::Isotropic::Sigma(6, 1e-3);
|
|
|
|
// Add all prior factors (pose, velocity, bias) to the graph.
|
|
NonlinearFactorGraph* graph = new NonlinearFactorGraph();
|
|
graph->addPrior(X(correction_count), prior_pose, pose_noise_model);
|
|
graph->addPrior(V(correction_count), prior_velocity, velocity_noise_model);
|
|
graph->addPrior(B(correction_count), prior_imu_bias, bias_noise_model);
|
|
|
|
auto p = imuParams();
|
|
|
|
std::shared_ptr<PreintegrationType> preintegrated =
|
|
std::make_shared<PreintegratedImuMeasurements>(p, prior_imu_bias);
|
|
|
|
assert(preintegrated);
|
|
|
|
// Store previous state for imu integration and latest predicted outcome.
|
|
NavState prev_state(prior_pose, prior_velocity);
|
|
NavState prop_state = prev_state;
|
|
imuBias::ConstantBias prev_bias = prior_imu_bias;
|
|
|
|
// Keep track of total error over the entire run as simple performance metric.
|
|
double current_position_error = 0.0, current_orientation_error = 0.0;
|
|
|
|
double output_time = 0.0;
|
|
double dt = 0.005; // The real system has noise, but here, results are nearly
|
|
// exactly the same, so keeping this for simplicity.
|
|
|
|
// All priors have been set up, now iterate through the data file.
|
|
while (file.good()) {
|
|
// Parse out first value
|
|
getline(file, value, ',');
|
|
int type = stoi(value.c_str());
|
|
|
|
if (type == 0) { // IMU measurement
|
|
Vector6 imu;
|
|
for (int i = 0; i < 5; ++i) {
|
|
getline(file, value, ',');
|
|
imu(i) = stof(value.c_str());
|
|
}
|
|
getline(file, value, '\n');
|
|
imu(5) = stof(value.c_str());
|
|
|
|
// Adding the IMU preintegration.
|
|
preintegrated->integrateMeasurement(imu.head<3>(), imu.tail<3>(), dt);
|
|
|
|
} else if (type == 1) { // GPS measurement
|
|
Vector7 gps;
|
|
for (int i = 0; i < 6; ++i) {
|
|
getline(file, value, ',');
|
|
gps(i) = stof(value.c_str());
|
|
}
|
|
getline(file, value, '\n');
|
|
gps(6) = stof(value.c_str());
|
|
|
|
correction_count++;
|
|
|
|
// Adding IMU factor and GPS factor and optimizing.
|
|
auto preint_imu =
|
|
dynamic_cast<const PreintegratedImuMeasurements&>(*preintegrated);
|
|
ImuFactor imu_factor(X(correction_count - 1), V(correction_count - 1),
|
|
X(correction_count), V(correction_count),
|
|
B(correction_count - 1), preint_imu);
|
|
graph->add(imu_factor);
|
|
imuBias::ConstantBias zero_bias(Vector3(0, 0, 0), Vector3(0, 0, 0));
|
|
graph->add(BetweenFactor<imuBias::ConstantBias>(
|
|
B(correction_count - 1), B(correction_count), zero_bias,
|
|
bias_noise_model));
|
|
|
|
auto correction_noise = noiseModel::Isotropic::Sigma(3, 1.0);
|
|
GPSFactor gps_factor(X(correction_count),
|
|
Point3(gps(0), // N,
|
|
gps(1), // E,
|
|
gps(2)), // D,
|
|
correction_noise);
|
|
graph->add(gps_factor);
|
|
|
|
// Now optimize and compare results.
|
|
prop_state = preintegrated->predict(prev_state, prev_bias);
|
|
initial_values.insert(X(correction_count), prop_state.pose());
|
|
initial_values.insert(V(correction_count), prop_state.v());
|
|
initial_values.insert(B(correction_count), prev_bias);
|
|
|
|
Values result;
|
|
|
|
if (use_isam) {
|
|
isam2->update(*graph, initial_values);
|
|
result = isam2->calculateEstimate();
|
|
|
|
// reset the graph
|
|
graph->resize(0);
|
|
initial_values.clear();
|
|
|
|
} else {
|
|
LevenbergMarquardtOptimizer optimizer(*graph, initial_values);
|
|
result = optimizer.optimize();
|
|
}
|
|
|
|
// Overwrite the beginning of the preintegration for the next step.
|
|
prev_state = NavState(result.at<Pose3>(X(correction_count)),
|
|
result.at<Vector3>(V(correction_count)));
|
|
prev_bias = result.at<imuBias::ConstantBias>(B(correction_count));
|
|
|
|
// Reset the preintegration object.
|
|
preintegrated->resetIntegrationAndSetBias(prev_bias);
|
|
|
|
// Print out the position and orientation error for comparison.
|
|
Vector3 gtsam_position = prev_state.pose().translation();
|
|
Vector3 position_error = gtsam_position - gps.head<3>();
|
|
current_position_error = position_error.norm();
|
|
|
|
Quaternion gtsam_quat = prev_state.pose().rotation().toQuaternion();
|
|
Quaternion gps_quat(gps(6), gps(3), gps(4), gps(5));
|
|
Quaternion quat_error = gtsam_quat * gps_quat.inverse();
|
|
quat_error.normalize();
|
|
Vector3 euler_angle_error(quat_error.x() * 2, quat_error.y() * 2,
|
|
quat_error.z() * 2);
|
|
current_orientation_error = euler_angle_error.norm();
|
|
|
|
// display statistics
|
|
cout << "Position error:" << current_position_error << "\t "
|
|
<< "Angular error:" << current_orientation_error << "\n";
|
|
|
|
fprintf(fp_out, "%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f\n",
|
|
output_time, gtsam_position(0), gtsam_position(1),
|
|
gtsam_position(2), gtsam_quat.x(), gtsam_quat.y(), gtsam_quat.z(),
|
|
gtsam_quat.w(), gps(0), gps(1), gps(2), gps_quat.x(),
|
|
gps_quat.y(), gps_quat.z(), gps_quat.w());
|
|
|
|
output_time += 1.0;
|
|
|
|
} else {
|
|
cerr << "ERROR parsing file\n";
|
|
return 1;
|
|
}
|
|
}
|
|
fclose(fp_out);
|
|
cout << "Complete, results written to " << output_filename << "\n\n";
|
|
|
|
return 0;
|
|
}
|