Skip to content

Commit aaddee4

Browse files
koide3Copilot
andauthored
IMU prediction validation (#294)
* IMU prediction validation * validate IMU and skip if vel is too small * Update include/glim/common/imu_validation.hpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent d440144 commit aaddee4

7 files changed

Lines changed: 235 additions & 0 deletions

File tree

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ add_library(glim SHARED
7171
src/glim/preprocess/cloud_preprocessor.cpp
7272
# common process
7373
src/glim/common/imu_integration.cpp
74+
src/glim/common/imu_validation.cpp
7475
src/glim/common/cloud_deskewing.cpp
7576
src/glim/common/cloud_covariance_estimation.cpp
7677
# odometry

config/config_odometry_cpu.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
"vgicp_voxelmap_levels": 1,
3131
"vgicp_voxelmap_scaling_factor": 2.0,
3232
// Optimizer params
33+
"validate_imu": true,
3334
"save_imu_rate_trajectory": true,
3435
"num_threads": 2
3536
}

config/config_odometry_gpu.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
// keyframe_delta_(trans|rot) : Keyframe displacement threshold [m / rad]
3333
//
3434
// --- MISC ---
35+
// validate_imu : If true, perform IMU validation and log the validation results
3536
// save_imu_rate_trajectory : If true, save the IMU rate trajectory
3637
// num_threads : Number of threads for odometry estimation
3738
*/
@@ -64,6 +65,7 @@
6465
"keyframe_delta_rot": 0.5,
6566
"keyframe_entropy_thresh": 0.99,
6667
// MISC
68+
"validate_imu": true,
6769
"save_imu_rate_trajectory": true,
6870
"num_threads": 2
6971
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
#pragma once
2+
3+
#include <Eigen/Core>
4+
#include <Eigen/Geometry>
5+
#include <spdlog/spdlog.h>
6+
#include <gtsam_points/util/runnning_statistics.hpp>
7+
8+
namespace glim {
9+
10+
class IMUValidation {
11+
public:
12+
IMUValidation(const std::shared_ptr<spdlog::logger>& logger, bool enabled = true);
13+
~IMUValidation();
14+
15+
void validate(
16+
const Eigen::Isometry3d& last_T_world_imu,
17+
const Eigen::Vector3d& last_v_world_imu,
18+
const Eigen::Isometry3d& predicted_T_world_imu,
19+
const Eigen::Vector3d& predicted_v_world_imu,
20+
const Eigen::Isometry3d& corrected_T_world_imu,
21+
const Eigen::Vector3d& corrected_v_world_imu,
22+
double dt);
23+
24+
void validate(const Eigen::Matrix<double, 6, 1>& imu_bias);
25+
26+
private:
27+
const bool enabled;
28+
std::shared_ptr<spdlog::logger> logger;
29+
30+
int num_validations;
31+
gtsam_points::RunningStatistics<Eigen::Array3d> noimu_error_stats; // rotation, translation, velocity
32+
gtsam_points::RunningStatistics<Eigen::Array3d> imu_error_stats; // rotation, translation, velocity
33+
Eigen::Array3i imu_better_counts; // rotation, translation, velocity
34+
35+
int num_bias_validations;
36+
gtsam_points::RunningStatistics<Eigen::Array<double, 6, 1>> imu_bias_stats; // Acc, Gyro bias
37+
};
38+
39+
} // namespace glim

include/glim/odometry/odometry_estimation_imu.hpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ class IncrementalFixedLagSmootherExtWithFallback;
2323
namespace glim {
2424

2525
class IMUIntegration;
26+
class IMUValidation;
2627
class CloudDeskewing;
2728
class CloudCovarianceEstimation;
2829
class InitialStateEstimation;
@@ -58,6 +59,7 @@ struct OdometryEstimationIMUParams {
5859
double isam2_relinearize_thresh;
5960

6061
// Logging params
62+
bool validate_imu;
6163
bool save_imu_rate_trajectory;
6264

6365
int num_threads; // Number of threads for preprocessing and per-factor parallelism
@@ -103,6 +105,7 @@ class OdometryEstimationIMU : public OdometryEstimationBase {
103105
// Utility classes
104106
std::unique_ptr<InitialStateEstimation> init_estimation;
105107
std::unique_ptr<IMUIntegration> imu_integration;
108+
std::unique_ptr<IMUValidation> imu_validation;
106109
std::unique_ptr<CloudDeskewing> deskewing;
107110
std::unique_ptr<CloudCovarianceEstimation> covariance_estimation;
108111

src/glim/common/imu_validation.cpp

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
#include <glim/common/imu_validation.hpp>
2+
3+
namespace glim {
4+
5+
IMUValidation::IMUValidation(const std::shared_ptr<spdlog::logger>& logger, bool enabled) : enabled(enabled), logger(logger) {
6+
num_validations = 0;
7+
num_bias_validations = 0;
8+
imu_better_counts.setZero();
9+
}
10+
11+
IMUValidation::~IMUValidation() {}
12+
13+
void IMUValidation::validate(
14+
const Eigen::Isometry3d& last_T_world_imu,
15+
const Eigen::Vector3d& last_v_world_imu,
16+
const Eigen::Isometry3d& predicted_T_world_imu,
17+
const Eigen::Vector3d& predicted_v_world_imu,
18+
const Eigen::Isometry3d& corrected_T_world_imu,
19+
const Eigen::Vector3d& corrected_v_world_imu,
20+
double dt) {
21+
if (!enabled) {
22+
return;
23+
}
24+
25+
logger->debug("validating IMU prediction (|v|={:.3f} m/s)", corrected_v_world_imu.norm());
26+
if (corrected_v_world_imu.norm() < 0.1) {
27+
// Skip validation when the velocity is too small, as the errors can be dominated by noise.
28+
logger->debug("skipping IMU validation due to small velocity");
29+
return;
30+
}
31+
32+
Eigen::Isometry3d noimu_T_world_imu = last_T_world_imu;
33+
noimu_T_world_imu.translation() += last_v_world_imu * dt;
34+
const auto& noimu_v_world_imu = last_v_world_imu;
35+
36+
const Eigen::Isometry3d noimu_error_T = corrected_T_world_imu.inverse() * noimu_T_world_imu;
37+
const Eigen::Vector3d noimu_error_v = corrected_v_world_imu - noimu_v_world_imu;
38+
39+
const Eigen::Isometry3d imu_error_T = corrected_T_world_imu.inverse() * predicted_T_world_imu;
40+
const Eigen::Vector3d imu_error_v = corrected_v_world_imu - predicted_v_world_imu;
41+
42+
const double noimu_error_rot = Eigen::AngleAxisd(noimu_error_T.linear()).angle();
43+
const double noimu_error_trans = noimu_error_T.translation().norm();
44+
const double noimu_error_vel = noimu_error_v.norm();
45+
const Eigen::Array3d noimu_errors(noimu_error_rot, noimu_error_trans, noimu_error_vel);
46+
noimu_error_stats.add(noimu_errors);
47+
48+
const double imu_error_rot = Eigen::AngleAxisd(imu_error_T.linear()).angle();
49+
const double imu_error_trans = imu_error_T.translation().norm();
50+
const double imu_error_vel = imu_error_v.norm();
51+
const Eigen::Array3d imu_errors(imu_error_rot, imu_error_trans, imu_error_vel);
52+
imu_error_stats.add(imu_errors);
53+
54+
imu_better_counts += (imu_errors < noimu_errors).cast<int>();
55+
num_validations++;
56+
57+
// Print statistics every 64 validations
58+
if (num_validations % 64 == 0) {
59+
const Eigen::Array3d imu_better_ratios = imu_better_counts.cast<double>() / num_validations;
60+
61+
bool good_imu = true;
62+
63+
// Heuristic rules to determine if IMU prediction is good or not.
64+
// Reference: os1_128_01 dataset, better ratios for rot=1.00, trans=0.55, vel=0.66
65+
good_imu &= imu_better_ratios[0] > 0.7; // Rotation
66+
good_imu &= imu_better_ratios[1] > 0.4; // Translation
67+
good_imu &= imu_better_ratios[2] > 0.5; // Velocity
68+
69+
auto log_level = good_imu ? spdlog::level::debug : spdlog::level::warn;
70+
71+
const Eigen::Array3d noimu_error_means = noimu_error_stats.mean();
72+
const Eigen::Array3d noimu_error_stds = noimu_error_stats.std();
73+
const Eigen::Array3d imu_error_means = imu_error_stats.mean();
74+
const Eigen::Array3d imu_error_stds = imu_error_stats.std();
75+
76+
if (!good_imu) {
77+
logger->log(log_level, "IMU prediction is not good.");
78+
logger->log(log_level, "Possibly T_lidar_imu is not accurate or IMU bias is not well estimated.");
79+
}
80+
logger->log(log_level, "IMU validation results:");
81+
logger->log(log_level, "num_validations={}", num_validations);
82+
logger->log(
83+
log_level,
84+
"No-IMU errors rot={:.3f} +- {:.3f} deg, trans={:.3f} +- {:.3f} m, vel={:.3f} +- {:.3f} m/s",
85+
noimu_error_means[0] * 180.0 / M_PI,
86+
noimu_error_stds[0] * 180.0 / M_PI,
87+
noimu_error_means[1],
88+
noimu_error_stds[1],
89+
noimu_error_means[2],
90+
noimu_error_stds[2]);
91+
logger->log(
92+
log_level,
93+
"IMU errors rot={:.3f} +- {:.3f} deg, trans={:.3f} +- {:.3f} m, vel={:.3f} +- {:.3f} m/s",
94+
imu_error_means[0] * 180.0 / M_PI,
95+
imu_error_stds[0] * 180.0 / M_PI,
96+
imu_error_means[1],
97+
imu_error_stds[1],
98+
imu_error_means[2],
99+
imu_error_stds[2]);
100+
logger->log(log_level, "IMU better ratios rot={:.2f}, trans={:.2f}, vel={:.2f}", imu_better_ratios[0], imu_better_ratios[1], imu_better_ratios[2]);
101+
}
102+
}
103+
104+
void IMUValidation::validate(const Eigen::Matrix<double, 6, 1>& imu_bias) {
105+
if (!enabled) {
106+
return;
107+
}
108+
109+
num_bias_validations++;
110+
if (num_bias_validations % 64) {
111+
return;
112+
}
113+
114+
const Eigen::Array3d bias_acc = imu_bias.head<3>();
115+
const Eigen::Array3d bias_gyro = imu_bias.tail<3>();
116+
117+
bool good_imu = true;
118+
good_imu &= bias_acc.cwiseAbs().maxCoeff() < 0.5; // Acceleration bias should be small (less than 0.5 m/s^2)
119+
good_imu &= bias_gyro.cwiseAbs().maxCoeff() < 0.5; // Gyro bias should be small (less than 0.5 rad/s)
120+
121+
imu_bias_stats.add(imu_bias);
122+
const Eigen::Array<double, 6, 1> bias_mean = imu_bias_stats.mean();
123+
const Eigen::Array<double, 6, 1> bias_std = imu_bias_stats.std();
124+
const Eigen::Array<double, 6, 1> bias_max = imu_bias_stats.max();
125+
const Eigen::Array<double, 6, 1> bias_min = imu_bias_stats.min();
126+
127+
auto log_level = good_imu ? spdlog::level::debug : spdlog::level::warn;
128+
if (!good_imu) {
129+
logger->log(log_level, "IMU bias estimation seems inaccurate.");
130+
logger->log(log_level, "Possibly T_lidar_imu is not accurate or LiDAR-IMU synchronization is not correct.");
131+
}
132+
logger->log(log_level, "IMU bias validation results:");
133+
logger->log(log_level, "num_bias_validations={}", num_bias_validations);
134+
logger->log(
135+
log_level,
136+
"bias_acc=[{:.3f}, {:.3f}, {:.3f}] m/s^2, bias_gyro=[{:.3f}, {:.3f}, {:.3f}] rad/s",
137+
bias_acc[0],
138+
bias_acc[1],
139+
bias_acc[2],
140+
bias_gyro[0],
141+
bias_gyro[1],
142+
bias_gyro[2]);
143+
logger->log(
144+
log_level,
145+
"acc_stat ax={:.3f} +- {:.3f} [min={:.3f}, max={:.3f}], ay={:.3f} +- {:.3f} [min={:.3f}, max={:.3f}], az={:.3f} +- {:.3f} [min={:.3f}, max={:.3f}] m/s^2",
146+
bias_mean[0],
147+
bias_std[0],
148+
bias_min[0],
149+
bias_max[0],
150+
bias_mean[1],
151+
bias_std[1],
152+
bias_min[1],
153+
bias_max[1],
154+
bias_mean[2],
155+
bias_std[2],
156+
bias_min[2],
157+
bias_max[2]);
158+
logger->log(
159+
log_level,
160+
"gyro_stat wx={:.3f} +- {:.3f} [min={:.3f}, max={:.3f}], wy={:.3f} +- {:.3f} [min={:.3f}, max={:.3f}], wz={:.3f} +- {:.3f} [min={:.3f}, max={:.3f}] rad/s",
161+
bias_mean[3],
162+
bias_std[3],
163+
bias_min[3],
164+
bias_max[3],
165+
bias_mean[4],
166+
bias_std[4],
167+
bias_min[4],
168+
bias_max[4],
169+
bias_mean[5],
170+
bias_std[5],
171+
bias_min[5],
172+
bias_max[5]);
173+
}
174+
175+
} // namespace glim

src/glim/odometry/odometry_estimation_imu.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include <glim/util/config.hpp>
1414
#include <glim/util/convert_to_string.hpp>
1515
#include <glim/common/imu_integration.hpp>
16+
#include <glim/common/imu_validation.hpp>
1617
#include <glim/common/cloud_deskewing.hpp>
1718
#include <glim/common/cloud_covariance_estimation.hpp>
1819
#include <glim/odometry/initial_state_estimation.hpp>
@@ -61,6 +62,7 @@ OdometryEstimationIMUParams::OdometryEstimationIMUParams() {
6162
isam2_relinearize_skip = config.param<int>("odometry_estimation", "isam2_relinearize_skip", 1);
6263
isam2_relinearize_thresh = config.param<double>("odometry_estimation", "isam2_relinearize_thresh", 0.1);
6364

65+
validate_imu = config.param<bool>("odometry_estimation", "validate_imu", true);
6466
save_imu_rate_trajectory = config.param<bool>("odometry_estimation", "save_imu_rate_trajectory", false);
6567

6668
num_threads = config.param<int>("odometry_estimation", "num_threads", 4);
@@ -88,6 +90,7 @@ OdometryEstimationIMU::OdometryEstimationIMU(std::unique_ptr<OdometryEstimationI
8890
}
8991

9092
imu_integration.reset(new IMUIntegration);
93+
imu_validation.reset(new IMUValidation(logger, params->validate_imu));
9194
deskewing.reset(new CloudDeskewing);
9295
covariance_estimation.reset(new CloudCovarianceEstimation(params->num_threads));
9396

@@ -345,6 +348,17 @@ EstimationFrame::ConstPtr OdometryEstimationIMU::insert_frame(const Preprocessed
345348
// Update frames
346349
update_frames(current, new_factors);
347350

351+
// Check if IMU prediction is good or not
352+
imu_validation->validate(
353+
Eigen::Isometry3d(last_T_world_imu.matrix()),
354+
last_v_world_imu,
355+
Eigen::Isometry3d(predicted_T_world_imu.matrix()),
356+
predicted_v_world_imu,
357+
new_frame->T_world_imu,
358+
new_frame->v_world_imu,
359+
new_frame->stamp - last_stamp);
360+
imu_validation->validate(new_frame->imu_bias);
361+
348362
std::vector<EstimationFrame::ConstPtr> active_frames(frames.inner_begin(), frames.inner_end());
349363
Callbacks::on_update_new_frame(active_frames.back());
350364
Callbacks::on_update_frames(active_frames);

0 commit comments

Comments
 (0)