Add IMU orientation correction to the pose graph optimization. (#361)

Orientation of the IMU, most importantly pitch, is crucial to correctly
track changes in altitude. Each trajectory gets optimization variables
to correct for small errors in the IMU orientation.

Also adds separate gravity constants for each trajectory. The idea is
that different trajectories might use different hardware and slightly
disagree about the strength of gravity.

PAIR=damonkohler
master
Wolfgang Hess 2017-06-28 18:12:24 +02:00 committed by GitHub
parent 19da0ab7c8
commit 955e190166
4 changed files with 43 additions and 10 deletions

View File

@ -41,9 +41,14 @@ class AccelerationCostFunction {
template <typename T> template <typename T>
bool operator()(const T* const middle_rotation, const T* const start_position, bool operator()(const T* const middle_rotation, const T* const start_position,
const T* const middle_position, const T* const end_position, const T* const middle_position, const T* const end_position,
const T* const gravity_constant, T* residual) const { const T* const gravity_constant,
const T* const imu_calibration, T* residual) const {
const Eigen::Quaternion<T> eigen_imu_calibration(
imu_calibration[0], imu_calibration[1], imu_calibration[2],
imu_calibration[3]);
const Eigen::Matrix<T, 3, 1> imu_delta_velocity = const Eigen::Matrix<T, 3, 1> imu_delta_velocity =
ToEigen(middle_rotation) * delta_velocity_imu_frame_.cast<T>() - ToEigen(middle_rotation) * eigen_imu_calibration *
delta_velocity_imu_frame_.cast<T>() -
*gravity_constant * *gravity_constant *
(0.5 * (first_delta_time_seconds_ + second_delta_time_seconds_) * (0.5 * (first_delta_time_seconds_ + second_delta_time_seconds_) *
Eigen::Vector3d::UnitZ()) Eigen::Vector3d::UnitZ())

View File

@ -36,13 +36,17 @@ class RotationCostFunction {
template <typename T> template <typename T>
bool operator()(const T* const start_rotation, const T* const end_rotation, bool operator()(const T* const start_rotation, const T* const end_rotation,
T* residual) const { const T* const imu_calibration, T* residual) const {
const Eigen::Quaternion<T> start(start_rotation[0], start_rotation[1], const Eigen::Quaternion<T> start(start_rotation[0], start_rotation[1],
start_rotation[2], start_rotation[3]); start_rotation[2], start_rotation[3]);
const Eigen::Quaternion<T> end(end_rotation[0], end_rotation[1], const Eigen::Quaternion<T> end(end_rotation[0], end_rotation[1],
end_rotation[2], end_rotation[3]); end_rotation[2], end_rotation[3]);
const Eigen::Quaternion<T> eigen_imu_calibration(
imu_calibration[0], imu_calibration[1], imu_calibration[2],
imu_calibration[3]);
const Eigen::Quaternion<T> error = const Eigen::Quaternion<T> error =
end.conjugate() * start * delta_rotation_imu_frame_.cast<T>(); end.conjugate() * start * eigen_imu_calibration *
delta_rotation_imu_frame_.cast<T>() * eigen_imu_calibration.conjugate();
residual[0] = scaling_factor_ * error.x(); residual[0] = scaling_factor_ * error.x();
residual[1] = scaling_factor_ * error.y(); residual[1] = scaling_factor_ * error.y();
residual[2] = scaling_factor_ * error.z(); residual[2] = scaling_factor_ * error.z();

View File

@ -191,8 +191,12 @@ void OptimizationProblem::Solve(const std::vector<Constraint>& constraints) {
// Add constraints based on IMU observations of angular velocities and // Add constraints based on IMU observations of angular velocities and
// linear acceleration. // linear acceleration.
trajectory_data_.resize(imu_data_.size());
for (size_t trajectory_id = 0; trajectory_id != node_data_.size(); for (size_t trajectory_id = 0; trajectory_id != node_data_.size();
++trajectory_id) { ++trajectory_id) {
TrajectoryData& trajectory_data = trajectory_data_.at(trajectory_id);
problem.AddParameterBlock(trajectory_data.imu_calibration.data(), 4,
new ceres::QuaternionParameterization());
const std::deque<ImuData>& imu_data = imu_data_.at(trajectory_id); const std::deque<ImuData>& imu_data = imu_data_.at(trajectory_id);
CHECK(!imu_data.empty()); CHECK(!imu_data.empty());
// TODO(whess): Add support for empty trajectories. // TODO(whess): Add support for empty trajectories.
@ -233,7 +237,7 @@ void OptimizationProblem::Solve(const std::vector<Constraint>& constraints) {
result_center_to_center.delta_velocity; result_center_to_center.delta_velocity;
problem.AddResidualBlock( problem.AddResidualBlock(
new ceres::AutoDiffCostFunction<AccelerationCostFunction, 3, 4, 3, new ceres::AutoDiffCostFunction<AccelerationCostFunction, 3, 4, 3,
3, 3, 1>( 3, 3, 1, 4>(
new AccelerationCostFunction( new AccelerationCostFunction(
options_.acceleration_weight(), delta_velocity, options_.acceleration_weight(), delta_velocity,
common::ToSeconds(first_duration), common::ToSeconds(first_duration),
@ -242,14 +246,16 @@ void OptimizationProblem::Solve(const std::vector<Constraint>& constraints) {
C_nodes[trajectory_id].at(node_index - 1).translation(), C_nodes[trajectory_id].at(node_index - 1).translation(),
C_nodes[trajectory_id].at(node_index).translation(), C_nodes[trajectory_id].at(node_index).translation(),
C_nodes[trajectory_id].at(node_index + 1).translation(), C_nodes[trajectory_id].at(node_index + 1).translation(),
&gravity_constant_); &trajectory_data.gravity_constant,
trajectory_data.imu_calibration.data());
} }
problem.AddResidualBlock( problem.AddResidualBlock(
new ceres::AutoDiffCostFunction<RotationCostFunction, 3, 4, 4>( new ceres::AutoDiffCostFunction<RotationCostFunction, 3, 4, 4, 4>(
new RotationCostFunction(options_.rotation_weight(), new RotationCostFunction(options_.rotation_weight(),
result.delta_rotation)), result.delta_rotation)),
nullptr, C_nodes[trajectory_id].at(node_index - 1).rotation(), nullptr, C_nodes[trajectory_id].at(node_index - 1).rotation(),
C_nodes[trajectory_id].at(node_index).rotation()); C_nodes[trajectory_id].at(node_index).rotation(),
trajectory_data.imu_calibration.data());
} }
} }
@ -261,7 +267,20 @@ void OptimizationProblem::Solve(const std::vector<Constraint>& constraints) {
if (options_.log_solver_summary()) { if (options_.log_solver_summary()) {
LOG(INFO) << summary.FullReport(); LOG(INFO) << summary.FullReport();
LOG(INFO) << "Gravity was: " << gravity_constant_; for (size_t trajectory_id = 0; trajectory_id != trajectory_data_.size();
++trajectory_id) {
if (trajectory_id != 0) {
LOG(INFO) << "Trajectory " << trajectory_id << ":";
}
LOG(INFO) << "Gravity was: "
<< trajectory_data_[trajectory_id].gravity_constant;
LOG(INFO) << "IMU correction was: "
<< common::RadToDeg(
2. *
std::acos(
trajectory_data_[trajectory_id].imu_calibration[0]))
<< " deg";
}
} }
// Store the result. // Store the result.

View File

@ -75,12 +75,17 @@ class OptimizationProblem {
const std::vector<std::vector<SubmapData>>& submap_data() const; const std::vector<std::vector<SubmapData>>& submap_data() const;
private: private:
struct TrajectoryData {
double gravity_constant = 9.8;
std::array<double, 4> imu_calibration{{1., 0., 0., 0.}};
};
mapping::sparse_pose_graph::proto::OptimizationProblemOptions options_; mapping::sparse_pose_graph::proto::OptimizationProblemOptions options_;
FixZ fix_z_; FixZ fix_z_;
std::vector<std::deque<ImuData>> imu_data_; std::vector<std::deque<ImuData>> imu_data_;
std::vector<std::vector<NodeData>> node_data_; std::vector<std::vector<NodeData>> node_data_;
std::vector<std::vector<SubmapData>> submap_data_; std::vector<std::vector<SubmapData>> submap_data_;
double gravity_constant_ = 9.8; std::vector<TrajectoryData> trajectory_data_;
}; };
} // namespace sparse_pose_graph } // namespace sparse_pose_graph