DynamicInitializer.cpp
Go to the documentation of this file.
1 /*
2  * OpenVINS: An Open Platform for Visual-Inertial Research
3  * Copyright (C) 2018-2023 Patrick Geneva
4  * Copyright (C) 2018-2023 Guoquan Huang
5  * Copyright (C) 2018-2023 OpenVINS Contributors
6  * Copyright (C) 2018-2019 Kevin Eckenhoff
7  *
8  * This program is free software: you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation, either version 3 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program. If not, see <https://www.gnu.org/licenses/>.
20  */
21 
22 #include "DynamicInitializer.h"
23 
26 #include "ceres/Factor_ImuCPIv1.h"
28 #include "utils/helper.h"
29 
30 #include "cpi/CpiV1.h"
31 #include "feat/Feature.h"
32 #include "feat/FeatureDatabase.h"
33 #include "types/IMU.h"
34 #include "types/Landmark.h"
35 #include "utils/colors.h"
36 #include "utils/print.h"
37 #include "utils/quat_ops.h"
38 #include "utils/sensor_data.h"
39 
40 using namespace ov_core;
41 using namespace ov_type;
42 using namespace ov_init;
43 
44 bool DynamicInitializer::initialize(double &timestamp, Eigen::MatrixXd &covariance, std::vector<std::shared_ptr<ov_type::Type>> &order,
45  std::shared_ptr<ov_type::IMU> &_imu, std::map<double, std::shared_ptr<ov_type::PoseJPL>> &_clones_IMU,
46  std::unordered_map<size_t, std::shared_ptr<ov_type::Landmark>> &_features_SLAM) {
47 
48  // Get the newest and oldest timestamps we will try to initialize between!
49  auto rT1 = boost::posix_time::microsec_clock::local_time();
50  double newest_cam_time = -1;
51  for (auto const &feat : _db->get_internal_data()) {
52  for (auto const &camtimepair : feat.second->timestamps) {
53  for (auto const &time : camtimepair.second) {
54  newest_cam_time = std::max(newest_cam_time, time);
55  }
56  }
57  }
58  double oldest_time = newest_cam_time - params.init_window_time;
59  if (newest_cam_time < 0 || oldest_time < 0) {
60  return false;
61  }
62 
63  // Remove all measurements that are older than our initialization window
64  // Then we will try to use all features that are in the feature database!
65  _db->cleanup_measurements(oldest_time);
66  bool have_old_imu_readings = false;
67  auto it_imu = imu_data->begin();
68  while (it_imu != imu_data->end() && it_imu->timestamp < oldest_time + params.calib_camimu_dt) {
69  have_old_imu_readings = true;
70  it_imu = imu_data->erase(it_imu);
71  }
72  if (_db->get_internal_data().size() < 0.75 * params.init_max_features) {
73  PRINT_WARNING(RED "[init-d]: only %zu valid features of required (%.0f thresh)!!\n" RESET, _db->get_internal_data().size(),
74  0.95 * params.init_max_features);
75  return false;
76  }
77  if (imu_data->size() < 2 || !have_old_imu_readings) {
78  // PRINT_WARNING(RED "[init-d]: waiting for window to reach full size (%zu imu readings)!!\n" RESET, imu_data->size());
79  return false;
80  }
81 
82  // Now we will make a copy of our features here
83  // We do this to ensure that the feature database can continue to have new
84  // measurements appended to it in an async-manor so this initialization
85  // can be performed in a secondary thread while feature tracking is still performed.
86  std::unordered_map<size_t, std::shared_ptr<Feature>> features;
87  for (const auto &feat : _db->get_internal_data()) {
88  auto feat_new = std::make_shared<Feature>();
89  feat_new->featid = feat.second->featid;
90  feat_new->uvs = feat.second->uvs;
91  feat_new->uvs_norm = feat.second->uvs_norm;
92  feat_new->timestamps = feat.second->timestamps;
93  features.insert({feat.first, feat_new});
94  }
95 
96  // ======================================================
97  // ======================================================
98 
99  // Settings
100  const int min_num_meas_to_optimize = (int)params.init_window_time;
101  const int min_valid_features = 8;
102 
103  // Validation information for features we can use
104  bool have_stereo = false;
105  int count_valid_features = 0;
106  std::map<size_t, int> map_features_num_meas;
107  int num_measurements = 0;
108  double oldest_camera_time = INFINITY;
109  std::map<double, bool> map_camera_times;
110  map_camera_times[newest_cam_time] = true; // always insert final pose
111  std::map<size_t, bool> map_camera_ids;
112  double pose_dt_avg = params.init_window_time / (double)(params.init_dyn_num_pose + 1);
113  for (auto const &feat : features) {
114 
115  // Loop through each timestamp and make sure it is a valid pose
116  std::vector<double> times;
117  std::map<size_t, bool> camids;
118  for (auto const &camtime : feat.second->timestamps) {
119  for (double time : camtime.second) {
120  double time_dt = INFINITY;
121  for (auto const &tmp : map_camera_times) {
122  time_dt = std::min(time_dt, std::abs(time - tmp.first));
123  }
124  for (auto const &tmp : times) {
125  time_dt = std::min(time_dt, std::abs(time - tmp));
126  }
127  // either this pose is a new one at the desired frequency
128  // or it is a timestamp that we already have, thus can use for free
129  if (time_dt >= pose_dt_avg || time_dt == 0.0) {
130  times.push_back(time);
131  camids[camtime.first] = true;
132  }
133  }
134  }
135 
136  // This isn't a feature we should use if there are not enough measurements
137  map_features_num_meas[feat.first] = (int)times.size();
138  if (map_features_num_meas[feat.first] < min_num_meas_to_optimize)
139  continue;
140 
141  // If we have enough measurements we should append this feature!
142  for (auto const &tmp : times) {
143  map_camera_times[tmp] = true;
144  oldest_camera_time = std::min(oldest_camera_time, tmp);
145  num_measurements += 2;
146  }
147  for (auto const &tmp : camids) {
148  map_camera_ids[tmp.first] = true;
149  }
150  if (camids.size() > 1) {
151  have_stereo = true;
152  }
153  count_valid_features++;
154  }
155 
156  // Return if we do not have our full window or not enough measurements
157  // Also check that we have enough features to initialize with
158  if ((int)map_camera_times.size() < params.init_dyn_num_pose) {
159  return false;
160  }
161  if (count_valid_features < min_valid_features) {
162  PRINT_WARNING(RED "[init-d]: only %zu valid features of required %d!!\n" RESET, count_valid_features, min_valid_features);
163  return false;
164  }
165 
166  // Bias initial guesses specified by the launch file
167  // We don't go through the effort to recover the biases right now since they should be
168  // Semi-well known before launching or can be considered to be near zero...
169  Eigen::Vector3d gyroscope_bias = params.init_dyn_bias_g;
170  Eigen::Vector3d accelerometer_bias = params.init_dyn_bias_a;
171 
172  // Check that we have some angular velocity / orientation change
173  double accel_inI_norm = 0.0;
174  double theta_inI_norm = 0.0;
175  double time0_in_imu = oldest_camera_time + params.calib_camimu_dt;
176  double time1_in_imu = newest_cam_time + params.calib_camimu_dt;
177  std::vector<ov_core::ImuData> readings = InitializerHelper::select_imu_readings(*imu_data, time0_in_imu, time1_in_imu);
178  assert(readings.size() > 2);
179  for (size_t k = 0; k < readings.size() - 1; k++) {
180  auto imu0 = readings.at(k);
181  auto imu1 = readings.at(k + 1);
182  double dt = imu1.timestamp - imu0.timestamp;
183  Eigen::Vector3d wm = 0.5 * (imu0.wm + imu1.wm) - gyroscope_bias;
184  Eigen::Vector3d am = 0.5 * (imu0.am + imu1.am) - accelerometer_bias;
185  theta_inI_norm += (-wm * dt).norm();
186  accel_inI_norm += am.norm();
187  }
188  accel_inI_norm /= (double)(readings.size() - 1);
189  if (180.0 / M_PI * theta_inI_norm < params.init_dyn_min_deg) {
190  PRINT_WARNING(YELLOW "[init-d]: gyroscope only %.2f degree change (%.2f thresh)\n" RESET, 180.0 / M_PI * theta_inI_norm,
191  params.init_dyn_min_deg);
192  return false;
193  }
194  PRINT_DEBUG("[init-d]: |theta_I| = %.4f deg and |accel| = %.4f\n", 180.0 / M_PI * theta_inI_norm, accel_inI_norm);
195 
196  // // Create feature bearing vector in the first frame
197  // // This gives us: p_FinI0 = depth * bearing
198  // Eigen::Vector4d q_ItoC = data_ori.camera_q_ItoC.at(cam_id);
199  // Eigen::Vector3d p_IinC = data_init.camera_p_IinC.at(cam_id);
200  // Eigen::Matrix3d R_ItoC = quat_2_Rot(q_ItoC);
201  // std::map<size_t, Eigen::Vector3d> features_bearings;
202  // std::map<size_t, int> features_index;
203  // for (auto const &feat : features) {
204  // if (map_features_num_meas[feat.first] < min_num_meas_to_optimize)
205  // continue;
206  // assert(feat->timestamps.find(cam_id) != feat->timestamps.end());
207  // double timestamp = data_ori.timestamps_cam.at(cam_id).at(0);
208  // auto it0 = std::find(feat->timestamps.at(cam_id).begin(), feat->timestamps.at(cam_id).end(), timestamp);
209  // if (it0 == feat->timestamps.at(cam_id).end())
210  // continue;
211  // auto idx0 = std::distance(feat->timestamps.at(cam_id).begin(), it0);
212  // Eigen::Vector3d bearing;
213  // bearing << feat->uvs_norm.at(cam_id).at(idx0)(0), feat->uvs_norm.at(cam_id).at(idx0)(1), 1;
214  // bearing = bearing / bearing.norm();
215  // bearing = R_ItoC.transpose() * bearing;
216  // features_bearings.insert({feat->featid, bearing});
217  // features_index.insert({feat->featid, (int)features_index.size()});
218  // }
219  auto rT2 = boost::posix_time::microsec_clock::local_time();
220 
221  // ======================================================
222  // ======================================================
223 
224  // We will recover position of feature, velocity, gravity
225  // Based on Equation (14) in the following paper:
226  // https://ieeexplore.ieee.org/abstract/document/6386235
227  // State ordering is: [features, velocity, gravity]
228  // Feature size of 1 will use the first ever bearing of the feature as true (depth only..)
229  const bool use_single_depth = false;
230  int size_feature = (use_single_depth) ? 1 : 3;
231  int num_features = count_valid_features;
232  int system_size = size_feature * num_features + 3 + 3;
233 
234  // Make sure we have enough measurements to fully constrain the system
235  if (num_measurements < system_size) {
236  PRINT_WARNING(YELLOW "[init-d]: not enough feature measurements (%d meas vs %d state size)!\n" RESET, num_measurements, system_size);
237  return false;
238  }
239 
240  // Now lets pre-integrate from the first time to the last
241  assert(oldest_camera_time < newest_cam_time);
242  double last_camera_timestamp = 0.0;
243  std::map<double, std::shared_ptr<ov_core::CpiV1>> map_camera_cpi_I0toIi, map_camera_cpi_IitoIi1;
244  for (auto const &timepair : map_camera_times) {
245 
246  // No preintegration at the first timestamp
247  double current_time = timepair.first;
248  if (current_time == oldest_camera_time) {
249  map_camera_cpi_I0toIi.insert({current_time, nullptr});
250  map_camera_cpi_IitoIi1.insert({current_time, nullptr});
251  last_camera_timestamp = current_time;
252  continue;
253  }
254 
255  // Perform our preintegration from I0 to Ii (used in the linear system)
256  double cpiI0toIi1_time0_in_imu = oldest_camera_time + params.calib_camimu_dt;
257  double cpiI0toIi1_time1_in_imu = current_time + params.calib_camimu_dt;
258  auto cpiI0toIi1 = std::make_shared<ov_core::CpiV1>(params.sigma_w, params.sigma_wb, params.sigma_a, params.sigma_ab, true);
259  cpiI0toIi1->setLinearizationPoints(gyroscope_bias, accelerometer_bias);
260  std::vector<ov_core::ImuData> cpiI0toIi1_readings =
261  InitializerHelper::select_imu_readings(*imu_data, cpiI0toIi1_time0_in_imu, cpiI0toIi1_time1_in_imu);
262  if (cpiI0toIi1_readings.size() < 2) {
263  PRINT_DEBUG(YELLOW "[init-d]: camera %.2f in has %zu IMU readings!\n" RESET, (cpiI0toIi1_time1_in_imu - cpiI0toIi1_time0_in_imu),
264  cpiI0toIi1_readings.size());
265  return false;
266  }
267  double cpiI0toIi1_dt_imu = cpiI0toIi1_readings.at(cpiI0toIi1_readings.size() - 1).timestamp - cpiI0toIi1_readings.at(0).timestamp;
268  if (std::abs(cpiI0toIi1_dt_imu - (cpiI0toIi1_time1_in_imu - cpiI0toIi1_time0_in_imu)) > 0.01) {
269  PRINT_DEBUG(YELLOW "[init-d]: camera IMU was only propagated %.3f of %.3f\n" RESET, cpiI0toIi1_dt_imu,
270  (cpiI0toIi1_time1_in_imu - cpiI0toIi1_time0_in_imu));
271  return false;
272  }
273  for (size_t k = 0; k < cpiI0toIi1_readings.size() - 1; k++) {
274  auto imu0 = cpiI0toIi1_readings.at(k);
275  auto imu1 = cpiI0toIi1_readings.at(k + 1);
276  cpiI0toIi1->feed_IMU(imu0.timestamp, imu1.timestamp, imu0.wm, imu0.am, imu1.wm, imu1.am);
277  }
278 
279  // Perform our preintegration from Ii to Ii1 (used in the mle optimization)
280  double cpiIitoIi1_time0_in_imu = last_camera_timestamp + params.calib_camimu_dt;
281  double cpiIitoIi1_time1_in_imu = current_time + params.calib_camimu_dt;
282  auto cpiIitoIi1 = std::make_shared<ov_core::CpiV1>(params.sigma_w, params.sigma_wb, params.sigma_a, params.sigma_ab, true);
283  cpiIitoIi1->setLinearizationPoints(gyroscope_bias, accelerometer_bias);
284  std::vector<ov_core::ImuData> cpiIitoIi1_readings =
285  InitializerHelper::select_imu_readings(*imu_data, cpiIitoIi1_time0_in_imu, cpiIitoIi1_time1_in_imu);
286  if (cpiIitoIi1_readings.size() < 2) {
287  PRINT_DEBUG(YELLOW "[init-d]: camera %.2f in has %zu IMU readings!\n" RESET, (cpiIitoIi1_time1_in_imu - cpiIitoIi1_time0_in_imu),
288  cpiIitoIi1_readings.size());
289  return false;
290  }
291  double cpiIitoIi1_dt_imu = cpiIitoIi1_readings.at(cpiIitoIi1_readings.size() - 1).timestamp - cpiIitoIi1_readings.at(0).timestamp;
292  if (std::abs(cpiIitoIi1_dt_imu - (cpiIitoIi1_time1_in_imu - cpiIitoIi1_time0_in_imu)) > 0.01) {
293  PRINT_DEBUG(YELLOW "[init-d]: camera IMU was only propagated %.3f of %.3f\n" RESET, cpiIitoIi1_dt_imu,
294  (cpiIitoIi1_time1_in_imu - cpiIitoIi1_time0_in_imu));
295  return false;
296  }
297  for (size_t k = 0; k < cpiIitoIi1_readings.size() - 1; k++) {
298  auto imu0 = cpiIitoIi1_readings.at(k);
299  auto imu1 = cpiIitoIi1_readings.at(k + 1);
300  cpiIitoIi1->feed_IMU(imu0.timestamp, imu1.timestamp, imu0.wm, imu0.am, imu1.wm, imu1.am);
301  }
302 
303  // Finally push back our integrations!
304  map_camera_cpi_I0toIi.insert({current_time, cpiI0toIi1});
305  map_camera_cpi_IitoIi1.insert({current_time, cpiIitoIi1});
306  last_camera_timestamp = current_time;
307  }
308 
309  // Loop through each feature observation and append it!
310  // State ordering is: [features, velocity, gravity]
311  Eigen::MatrixXd A = Eigen::MatrixXd::Zero(num_measurements, system_size);
312  Eigen::VectorXd b = Eigen::VectorXd::Zero(num_measurements);
313  PRINT_DEBUG("[init-d]: system of %d measurement x %d states created (%d features, %s)\n", num_measurements, system_size, num_features,
314  (have_stereo) ? "stereo" : "mono");
315  int index_meas = 0;
316  int idx_feat = 0;
317  std::map<size_t, int> A_index_features;
318  for (auto const &feat : features) {
319  if (map_features_num_meas[feat.first] < min_num_meas_to_optimize)
320  continue;
321  if (A_index_features.find(feat.first) == A_index_features.end()) {
322  A_index_features.insert({feat.first, idx_feat});
323  idx_feat += 1;
324  }
325  for (auto const &camtime : feat.second->timestamps) {
326 
327  // This camera
328  size_t cam_id = camtime.first;
329  Eigen::Vector4d q_ItoC = params.camera_extrinsics.at(cam_id).block(0, 0, 4, 1);
330  Eigen::Vector3d p_IinC = params.camera_extrinsics.at(cam_id).block(4, 0, 3, 1);
331  Eigen::Matrix3d R_ItoC = quat_2_Rot(q_ItoC);
332 
333  // Loop through each observation
334  for (size_t i = 0; i < camtime.second.size(); i++) {
335 
336  // Skip measurements we don't have poses for
337  double time = feat.second->timestamps.at(cam_id).at(i);
338  if (map_camera_times.find(time) == map_camera_times.end())
339  continue;
340 
341  // Our measurement
342  Eigen::Vector2d uv_norm;
343  uv_norm << (double)feat.second->uvs_norm.at(cam_id).at(i)(0), (double)feat.second->uvs_norm.at(cam_id).at(i)(1);
344 
345  // Preintegration values
346  double DT = 0.0;
347  Eigen::MatrixXd R_I0toIk = Eigen::MatrixXd::Identity(3, 3);
348  Eigen::MatrixXd alpha_I0toIk = Eigen::MatrixXd::Zero(3, 1);
349  if (map_camera_cpi_I0toIi.find(time) != map_camera_cpi_I0toIi.end() && map_camera_cpi_I0toIi.at(time) != nullptr) {
350  DT = map_camera_cpi_I0toIi.at(time)->DT;
351  R_I0toIk = map_camera_cpi_I0toIi.at(time)->R_k2tau;
352  alpha_I0toIk = map_camera_cpi_I0toIi.at(time)->alpha_tau;
353  }
354 
355  // Create the linear system based on the feature reprojection
356  // [ 1 0 -u ] p_FinCi = [ 0 ]
357  // [ 0 1 -v ] [ 0 ]
358  // where
359  // p_FinCi = R_C0toCi * R_ItoC * (p_FinI0 - p_IiinI0) + p_IinC
360  // = R_C0toCi * R_ItoC * (p_FinI0 - v_I0inI0 * dt - 0.5 * grav_inI0 * dt^2 - alpha) + p_IinC
361  Eigen::MatrixXd H_proj = Eigen::MatrixXd::Zero(2, 3);
362  H_proj << 1, 0, -uv_norm(0), 0, 1, -uv_norm(1);
363  Eigen::MatrixXd Y = H_proj * R_ItoC * R_I0toIk;
364  Eigen::MatrixXd H_i = Eigen::MatrixXd::Zero(2, system_size);
365  Eigen::MatrixXd b_i = Y * alpha_I0toIk - H_proj * p_IinC;
366  if (size_feature == 1) {
367  assert(false);
368  // Substitute in p_FinI0 = z*bearing_inC0_rotI0 - R_ItoC^T*p_IinC
369  // H_i.block(0, size_feature * A_index_features.at(feat.first), 2, 1) = Y * features_bearings.at(feat.first);
370  // b_i += Y * R_ItoC.transpose() * p_IinC;
371  } else {
372  H_i.block(0, size_feature * A_index_features.at(feat.first), 2, 3) = Y; // feat
373  }
374  H_i.block(0, size_feature * num_features + 0, 2, 3) = -DT * Y; // vel
375  H_i.block(0, size_feature * num_features + 3, 2, 3) = 0.5 * DT * DT * Y; // grav
376 
377  // Else lets append this to our system!
378  A.block(index_meas, 0, 2, A.cols()) = H_i;
379  b.block(index_meas, 0, 2, 1) = b_i;
380  index_meas += 2;
381  }
382  }
383  }
384  auto rT3 = boost::posix_time::microsec_clock::local_time();
385 
386  // ======================================================
387  // ======================================================
388 
389  // Solve the linear system without constraint
390  // Eigen::MatrixXd AtA = A.transpose() * A;
391  // Eigen::MatrixXd Atb = A.transpose() * b;
392  // Eigen::MatrixXd x_hat = AtA.colPivHouseholderQr().solve(Atb);
393 
394  // Constrained solving |g| = 9.81 constraint
395  Eigen::MatrixXd A1 = A.block(0, 0, A.rows(), A.cols() - 3);
396  // Eigen::MatrixXd A1A1_inv = (A1.transpose() * A1).inverse();
397  Eigen::MatrixXd A1A1_inv = (A1.transpose() * A1).llt().solve(Eigen::MatrixXd::Identity(A1.cols(), A1.cols()));
398  Eigen::MatrixXd A2 = A.block(0, A.cols() - 3, A.rows(), 3);
399  Eigen::MatrixXd Temp = A2.transpose() * (Eigen::MatrixXd::Identity(A1.rows(), A1.rows()) - A1 * A1A1_inv * A1.transpose());
400  Eigen::MatrixXd D = Temp * A2;
401  Eigen::MatrixXd d = Temp * b;
402  Eigen::Matrix<double, 7, 1> coeff = InitializerHelper::compute_dongsi_coeff(D, d, params.gravity_mag);
403 
404  // Create companion matrix of our polynomial
405  // https://en.wikipedia.org/wiki/Companion_matrix
406  assert(coeff(0) == 1);
407  Eigen::Matrix<double, 6, 6> companion_matrix = Eigen::Matrix<double, 6, 6>::Zero(coeff.rows() - 1, coeff.rows() - 1);
408  companion_matrix.diagonal(-1).setOnes();
409  companion_matrix.col(companion_matrix.cols() - 1) = -coeff.reverse().head(coeff.rows() - 1);
410  Eigen::JacobiSVD<Eigen::Matrix<double, 6, 6>> svd0(companion_matrix);
411  Eigen::MatrixXd singularValues0 = svd0.singularValues();
412  double cond0 = singularValues0(0) / singularValues0(singularValues0.rows() - 1);
413  PRINT_DEBUG("[init-d]: CM cond = %.3f | rank = %d of %d (%4.3e thresh)\n", cond0, (int)svd0.rank(), (int)companion_matrix.cols(),
414  svd0.threshold());
415  if (svd0.rank() != companion_matrix.rows()) {
416  PRINT_ERROR(RED "[init-d]: eigenvalue decomposition not full rank!!\n" RESET);
417  return false;
418  }
419 
420  // Find its eigenvalues (can be complex)
421  Eigen::EigenSolver<Eigen::Matrix<double, 6, 6>> solver(companion_matrix, false);
422  if (solver.info() != Eigen::Success) {
423  PRINT_ERROR(RED "[init-d]: failed to compute the eigenvalue decomposition!!\n" RESET);
424  return false;
425  }
426 
427  // Find the smallest real eigenvalue
428  // NOTE: we find the one that gives us minimal constraint cost
429  // NOTE: not sure if the best, but one that gives the correct mag should be good?
430  bool lambda_found = false;
431  double lambda_min = -1;
432  double cost_min = INFINITY;
433  Eigen::MatrixXd I_dd = Eigen::MatrixXd::Identity(D.rows(), D.rows());
434  // double g2 = params.gravity_mag * params.gravity_mag;
435  // Eigen::MatrixXd ddt = d * d.transpose();
436  for (int i = 0; i < solver.eigenvalues().size(); i++) {
437  auto val = solver.eigenvalues()(i);
438  if (val.imag() == 0) {
439  double lambda = val.real();
440  // Eigen::MatrixXd mat = (D - lambda * I_dd) * (D - lambda * I_dd) - 1 / g2 * ddt;
441  // double cost = mat.determinant();
442  Eigen::MatrixXd D_lambdaI_inv = (D - lambda * I_dd).llt().solve(I_dd);
443  Eigen::VectorXd state_grav = D_lambdaI_inv * d;
444  double cost = std::abs(state_grav.norm() - params.gravity_mag);
445  // std::cout << lambda << " - " << cost << " -> " << state_grav.transpose() << std::endl;
446  if (!lambda_found || cost < cost_min) {
447  lambda_found = true;
448  lambda_min = lambda;
449  cost_min = cost;
450  }
451  }
452  }
453  if (!lambda_found) {
454  PRINT_ERROR(RED "[init-d]: failed to find a real eigenvalue!!!\n" RESET);
455  return false;
456  }
457  PRINT_DEBUG("[init-d]: smallest real eigenvalue = %.5f (cost of %f)\n", lambda_min, cost_min);
458 
459  // Recover our gravity from the constraint!
460  // Eigen::MatrixXd D_lambdaI_inv = (D - lambda_min * I_dd).inverse();
461  Eigen::MatrixXd D_lambdaI_inv = (D - lambda_min * I_dd).llt().solve(I_dd);
462  Eigen::VectorXd state_grav = D_lambdaI_inv * d;
463 
464  // Overwrite our state: [features, velocity, gravity]
465  Eigen::VectorXd state_feat_vel = -A1A1_inv * A1.transpose() * A2 * state_grav + A1A1_inv * A1.transpose() * b;
466  Eigen::MatrixXd x_hat = Eigen::MatrixXd::Zero(system_size, 1);
467  x_hat.block(0, 0, size_feature * num_features + 3, 1) = state_feat_vel;
468  x_hat.block(size_feature * num_features + 3, 0, 3, 1) = state_grav;
469  Eigen::Vector3d v_I0inI0 = x_hat.block(size_feature * num_features + 0, 0, 3, 1);
470  PRINT_INFO("[init-d]: velocity in I0 was %.3f,%.3f,%.3f and |v| = %.4f\n", v_I0inI0(0), v_I0inI0(1), v_I0inI0(2), v_I0inI0.norm());
471 
472  // Check gravity magnitude to see if converged
473  Eigen::Vector3d gravity_inI0 = x_hat.block(size_feature * num_features + 3, 0, 3, 1);
474  double init_max_grav_difference = 1e-3;
475  if (std::abs(gravity_inI0.norm() - params.gravity_mag) > init_max_grav_difference) {
476  PRINT_WARNING(YELLOW "[init-d]: gravity did not converge (%.3f > %.3f)\n" RESET, std::abs(gravity_inI0.norm() - params.gravity_mag),
477  init_max_grav_difference);
478  return false;
479  }
480  PRINT_INFO("[init-d]: gravity in I0 was %.3f,%.3f,%.3f and |g| = %.4f\n", gravity_inI0(0), gravity_inI0(1), gravity_inI0(2),
481  gravity_inI0.norm());
482  auto rT4 = boost::posix_time::microsec_clock::local_time();
483 
484  // ======================================================
485  // ======================================================
486 
487  // Extract imu state elements
488  std::map<double, Eigen::VectorXd> ori_I0toIi, pos_IiinI0, vel_IiinI0;
489  for (auto const &timepair : map_camera_times) {
490 
491  // Timestamp of this pose
492  double time = timepair.first;
493 
494  // Get our CPI integration values
495  double DT = 0.0;
496  Eigen::MatrixXd R_I0toIk = Eigen::MatrixXd::Identity(3, 3);
497  Eigen::MatrixXd alpha_I0toIk = Eigen::MatrixXd::Zero(3, 1);
498  Eigen::MatrixXd beta_I0toIk = Eigen::MatrixXd::Zero(3, 1);
499  if (map_camera_cpi_I0toIi.find(time) != map_camera_cpi_I0toIi.end() && map_camera_cpi_I0toIi.at(time) != nullptr) {
500  auto cpi = map_camera_cpi_I0toIi.at(time);
501  DT = cpi->DT;
502  R_I0toIk = cpi->R_k2tau;
503  alpha_I0toIk = cpi->alpha_tau;
504  beta_I0toIk = cpi->beta_tau;
505  }
506 
507  // Integrate to get the relative to the current timestamp
508  Eigen::Vector3d p_IkinI0 = v_I0inI0 * DT - 0.5 * gravity_inI0 * DT * DT + alpha_I0toIk;
509  Eigen::Vector3d v_IkinI0 = v_I0inI0 - gravity_inI0 * DT + beta_I0toIk;
510 
511  // Record the values all transformed to the I0 frame
512  ori_I0toIi.insert({time, rot_2_quat(R_I0toIk)});
513  pos_IiinI0.insert({time, p_IkinI0});
514  vel_IiinI0.insert({time, v_IkinI0});
515  }
516 
517  // Recover the features in the first IMU frame
518  count_valid_features = 0;
519  std::map<size_t, Eigen::Vector3d> features_inI0;
520  for (auto const &feat : features) {
521  if (map_features_num_meas[feat.first] < min_num_meas_to_optimize)
522  continue;
523  Eigen::Vector3d p_FinI0;
524  if (size_feature == 1) {
525  assert(false);
526  // double depth = x_hat(size_feature * A_index_features.at(feat.first), 0);
527  // p_FinI0 = depth * features_bearings.at(feat.first) - R_ItoC.transpose() * p_IinC;
528  } else {
529  p_FinI0 = x_hat.block(size_feature * A_index_features.at(feat.first), 0, 3, 1);
530  }
531  bool is_behind = false;
532  for (auto const &camtime : feat.second->timestamps) {
533  size_t cam_id = camtime.first;
534  Eigen::Vector4d q_ItoC = params.camera_extrinsics.at(cam_id).block(0, 0, 4, 1);
535  Eigen::Vector3d p_IinC = params.camera_extrinsics.at(cam_id).block(4, 0, 3, 1);
536  Eigen::Vector3d p_FinC0 = quat_2_Rot(q_ItoC) * p_FinI0 + p_IinC;
537  if (p_FinC0(2) < 0) {
538  is_behind = true;
539  }
540  }
541  if (!is_behind) {
542  features_inI0.insert({feat.first, p_FinI0});
543  count_valid_features++;
544  }
545  }
546  if (count_valid_features < min_valid_features) {
547  PRINT_ERROR(YELLOW "[init-d]: not enough features for our mle (%zu < %d)!\n" RESET, count_valid_features, min_valid_features);
548  return false;
549  }
550 
551  // Convert our states to be a gravity aligned global frame of reference
552  // Here we say that the I0 frame is at 0,0,0 and shared the global origin
553  Eigen::Matrix3d R_GtoI0;
554  InitializerHelper::gram_schmidt(gravity_inI0, R_GtoI0);
555  Eigen::Vector4d q_GtoI0 = rot_2_quat(R_GtoI0);
556  Eigen::Vector3d gravity;
557  gravity << 0.0, 0.0, params.gravity_mag;
558  std::map<double, Eigen::VectorXd> ori_GtoIi, pos_IiinG, vel_IiinG;
559  std::map<size_t, Eigen::Vector3d> features_inG;
560  for (auto const &timepair : map_camera_times) {
561  ori_GtoIi[timepair.first] = quat_multiply(ori_I0toIi.at(timepair.first), q_GtoI0);
562  pos_IiinG[timepair.first] = R_GtoI0.transpose() * pos_IiinI0.at(timepair.first);
563  vel_IiinG[timepair.first] = R_GtoI0.transpose() * vel_IiinI0.at(timepair.first);
564  }
565  for (auto const &feat : features_inI0) {
566  features_inG[feat.first] = R_GtoI0.transpose() * feat.second;
567  }
568 
569  // ======================================================
570  // ======================================================
571 
572  // Ceres problem stuff
573  // NOTE: By default the problem takes ownership of the memory
574  ceres::Problem problem;
575 
576  // Our system states (map from time to index)
577  std::map<double, int> map_states;
578  std::vector<double *> ceres_vars_ori;
579  std::vector<double *> ceres_vars_pos;
580  std::vector<double *> ceres_vars_vel;
581  std::vector<double *> ceres_vars_bias_g;
582  std::vector<double *> ceres_vars_bias_a;
583 
584  // Feature states (3dof p_FinG)
585  std::map<size_t, int> map_features;
586  std::vector<double *> ceres_vars_feat;
587 
588  // Setup extrinsic calibration q_ItoC, p_IinC (map from camera id to index)
589  std::map<size_t, int> map_calib_cam2imu;
590  std::vector<double *> ceres_vars_calib_cam2imu_ori;
591  std::vector<double *> ceres_vars_calib_cam2imu_pos;
592 
593  // Setup intrinsic calibration focal, center, distortion (map from camera id to index)
594  std::map<size_t, int> map_calib_cam;
595  std::vector<double *> ceres_vars_calib_cam_intrinsics;
596 
597  // Helper lambda that will free any memory we have allocated
598  auto free_state_memory = [&]() {
599  for (auto const &ptr : ceres_vars_ori)
600  delete[] ptr;
601  for (auto const &ptr : ceres_vars_pos)
602  delete[] ptr;
603  for (auto const &ptr : ceres_vars_vel)
604  delete[] ptr;
605  for (auto const &ptr : ceres_vars_bias_g)
606  delete[] ptr;
607  for (auto const &ptr : ceres_vars_bias_a)
608  delete[] ptr;
609  for (auto const &ptr : ceres_vars_feat)
610  delete[] ptr;
611  for (auto const &ptr : ceres_vars_calib_cam2imu_ori)
612  delete[] ptr;
613  for (auto const &ptr : ceres_vars_calib_cam2imu_pos)
614  delete[] ptr;
615  for (auto const &ptr : ceres_vars_calib_cam_intrinsics)
616  delete[] ptr;
617  };
618 
619  // Set the optimization settings
620  // NOTE: We use dense schur since after eliminating features we have a dense problem
621  // NOTE: http://ceres-solver.org/solving_faqs.html#solving
622  ceres::Solver::Options options;
623  options.linear_solver_type = ceres::DENSE_SCHUR;
624  options.trust_region_strategy_type = ceres::DOGLEG;
625  // options.linear_solver_type = ceres::SPARSE_SCHUR;
626  // options.trust_region_strategy_type = ceres::LEVENBERG_MARQUARDT;
627  // options.preconditioner_type = ceres::SCHUR_JACOBI;
628  // options.linear_solver_type = ceres::ITERATIVE_SCHUR;
629  options.num_threads = params.init_dyn_mle_max_threads;
630  options.max_solver_time_in_seconds = params.init_dyn_mle_max_time;
631  options.max_num_iterations = params.init_dyn_mle_max_iter;
632  // options.minimizer_progress_to_stdout = true;
633  // options.linear_solver_ordering = ordering;
634  options.function_tolerance = 1e-5;
635  options.gradient_tolerance = 1e-4 * options.function_tolerance;
636 
637  // Loop through each CPI integration and add its measurement to the problem
638  double timestamp_k = -1;
639  for (auto const &timepair : map_camera_times) {
640 
641  // Get our predicted state at the requested camera timestep
642  double timestamp_k1 = timepair.first;
643  std::shared_ptr<ov_core::CpiV1> cpi = map_camera_cpi_IitoIi1.at(timestamp_k1);
644  Eigen::Matrix<double, 16, 1> state_k1;
645  state_k1.block(0, 0, 4, 1) = ori_GtoIi.at(timestamp_k1);
646  state_k1.block(4, 0, 3, 1) = pos_IiinG.at(timestamp_k1);
647  state_k1.block(7, 0, 3, 1) = vel_IiinG.at(timestamp_k1);
648  state_k1.block(10, 0, 3, 1) = gyroscope_bias;
649  state_k1.block(13, 0, 3, 1) = accelerometer_bias;
650 
651  // ================================================================
652  // ADDING GRAPH STATE / ESTIMATES!
653  // ================================================================
654 
655  // Load our state variables into our allocated state pointers
656  auto *var_ori = new double[4];
657  for (int j = 0; j < 4; j++) {
658  var_ori[j] = state_k1(0 + j, 0);
659  }
660  auto *var_pos = new double[3];
661  auto *var_vel = new double[3];
662  auto *var_bias_g = new double[3];
663  auto *var_bias_a = new double[3];
664  for (int j = 0; j < 3; j++) {
665  var_pos[j] = state_k1(4 + j, 0);
666  var_vel[j] = state_k1(7 + j, 0);
667  var_bias_g[j] = state_k1(10 + j, 0);
668  var_bias_a[j] = state_k1(13 + j, 0);
669  }
670 
671  // Now actually create the parameter block in the ceres problem
672  auto ceres_jplquat = new State_JPLQuatLocal();
673  problem.AddParameterBlock(var_ori, 4, ceres_jplquat);
674  problem.AddParameterBlock(var_pos, 3);
675  problem.AddParameterBlock(var_vel, 3);
676  problem.AddParameterBlock(var_bias_g, 3);
677  problem.AddParameterBlock(var_bias_a, 3);
678 
679  // Fix this first ever pose to constrain the problem
680  // NOTE: If we don't do this, then the problem won't be full rank
681  // NOTE: Since init is over a small window, we are likely to be degenerate
682  // NOTE: Thus we need to fix these parameters
683  if (map_states.empty()) {
684 
685  // Construct state and prior
686  Eigen::MatrixXd x_lin = Eigen::MatrixXd::Zero(13, 1);
687  for (int j = 0; j < 4; j++) {
688  x_lin(0 + j) = var_ori[j];
689  }
690  for (int j = 0; j < 3; j++) {
691  x_lin(4 + j) = var_pos[j];
692  x_lin(7 + j) = var_bias_g[j];
693  x_lin(10 + j) = var_bias_a[j];
694  }
695  Eigen::MatrixXd prior_grad = Eigen::MatrixXd::Zero(10, 1);
696  Eigen::MatrixXd prior_Info = Eigen::MatrixXd::Identity(10, 10);
697  prior_Info.block(0, 0, 4, 4) *= 1.0 / std::pow(1e-5, 2); // 4dof unobservable yaw and position
698  prior_Info.block(4, 4, 3, 3) *= 1.0 / std::pow(0.05, 2); // bias_g prior
699  prior_Info.block(7, 7, 3, 3) *= 1.0 / std::pow(0.10, 2); // bias_a prior
700 
701  // Construct state type and ceres parameter pointers
702  std::vector<std::string> x_types;
703  std::vector<double *> factor_params;
704  factor_params.push_back(var_ori);
705  x_types.emplace_back("quat_yaw");
706  factor_params.push_back(var_pos);
707  x_types.emplace_back("vec3");
708  factor_params.push_back(var_bias_g);
709  x_types.emplace_back("vec3");
710  factor_params.push_back(var_bias_a);
711  x_types.emplace_back("vec3");
712 
713  // Append it to the problem
714  auto *factor_prior = new Factor_GenericPrior(x_lin, x_types, prior_Info, prior_grad);
715  problem.AddResidualBlock(factor_prior, nullptr, factor_params);
716  }
717 
718  // Append to our historical vector of states
719  map_states.insert({timestamp_k1, (int)ceres_vars_ori.size()});
720  ceres_vars_ori.push_back(var_ori);
721  ceres_vars_pos.push_back(var_pos);
722  ceres_vars_vel.push_back(var_vel);
723  ceres_vars_bias_g.push_back(var_bias_g);
724  ceres_vars_bias_a.push_back(var_bias_a);
725 
726  // ================================================================
727  // ADDING GRAPH FACTORS!
728  // ================================================================
729 
730  // Append the new IMU factor
731  if (cpi != nullptr) {
732  assert(timestamp_k != -1);
733  std::vector<double *> factor_params;
734  factor_params.push_back(ceres_vars_ori.at(map_states.at(timestamp_k)));
735  factor_params.push_back(ceres_vars_bias_g.at(map_states.at(timestamp_k)));
736  factor_params.push_back(ceres_vars_vel.at(map_states.at(timestamp_k)));
737  factor_params.push_back(ceres_vars_bias_a.at(map_states.at(timestamp_k)));
738  factor_params.push_back(ceres_vars_pos.at(map_states.at(timestamp_k)));
739  factor_params.push_back(ceres_vars_ori.at(map_states.at(timestamp_k1)));
740  factor_params.push_back(ceres_vars_bias_g.at(map_states.at(timestamp_k1)));
741  factor_params.push_back(ceres_vars_vel.at(map_states.at(timestamp_k1)));
742  factor_params.push_back(ceres_vars_bias_a.at(map_states.at(timestamp_k1)));
743  factor_params.push_back(ceres_vars_pos.at(map_states.at(timestamp_k1)));
744  auto *factor_imu = new Factor_ImuCPIv1(cpi->DT, gravity, cpi->alpha_tau, cpi->beta_tau, cpi->q_k2tau, cpi->b_a_lin, cpi->b_w_lin,
745  cpi->J_q, cpi->J_b, cpi->J_a, cpi->H_b, cpi->H_a, cpi->P_meas);
746  problem.AddResidualBlock(factor_imu, nullptr, factor_params);
747  }
748 
749  // Move time forward
750  timestamp_k = timestamp_k1;
751  }
752 
753  // First make sure we have calibration states added
754  for (auto const &idpair : map_camera_ids) {
755  size_t cam_id = idpair.first;
756  if (map_calib_cam2imu.find(cam_id) == map_calib_cam2imu.end()) {
757  auto *var_calib_ori = new double[4];
758  for (int j = 0; j < 4; j++) {
759  var_calib_ori[j] = params.camera_extrinsics.at(cam_id)(0 + j, 0);
760  }
761  auto *var_calib_pos = new double[3];
762  for (int j = 0; j < 3; j++) {
763  var_calib_pos[j] = params.camera_extrinsics.at(cam_id)(4 + j, 0);
764  }
765  auto ceres_calib_jplquat = new State_JPLQuatLocal();
766  problem.AddParameterBlock(var_calib_ori, 4, ceres_calib_jplquat);
767  problem.AddParameterBlock(var_calib_pos, 3);
768  map_calib_cam2imu.insert({cam_id, (int)ceres_vars_calib_cam2imu_ori.size()});
769  ceres_vars_calib_cam2imu_ori.push_back(var_calib_ori);
770  ceres_vars_calib_cam2imu_pos.push_back(var_calib_pos);
771 
772  // Construct state and prior
773  Eigen::MatrixXd x_lin = Eigen::MatrixXd::Zero(7, 1);
774  for (int j = 0; j < 4; j++) {
775  x_lin(0 + j) = var_calib_ori[j];
776  }
777  for (int j = 0; j < 3; j++) {
778  x_lin(4 + j) = var_calib_pos[j];
779  }
780  Eigen::MatrixXd prior_grad = Eigen::MatrixXd::Zero(6, 1);
781  Eigen::MatrixXd prior_Info = Eigen::MatrixXd::Identity(6, 6);
782  prior_Info.block(0, 0, 3, 3) *= 1.0 / std::pow(0.001, 2);
783  prior_Info.block(3, 3, 3, 3) *= 1.0 / std::pow(0.01, 2);
784 
785  // Construct state type and ceres parameter pointers
786  std::vector<std::string> x_types;
787  std::vector<double *> factor_params;
788  factor_params.push_back(var_calib_ori);
789  x_types.emplace_back("quat");
790  factor_params.push_back(var_calib_pos);
791  x_types.emplace_back("vec3");
792  auto *factor_prior = new Factor_GenericPrior(x_lin, x_types, prior_Info, prior_grad);
793  problem.AddResidualBlock(factor_prior, nullptr, factor_params);
794  if (!params.init_dyn_mle_opt_calib) {
795  problem.SetParameterBlockConstant(var_calib_ori);
796  problem.SetParameterBlockConstant(var_calib_pos);
797  }
798  }
799  if (map_calib_cam.find(cam_id) == map_calib_cam.end()) {
800  auto *var_calib_cam = new double[8];
801  for (int j = 0; j < 8; j++) {
802  var_calib_cam[j] = params.camera_intrinsics.at(cam_id)->get_value()(j, 0);
803  }
804  problem.AddParameterBlock(var_calib_cam, 8);
805  map_calib_cam.insert({cam_id, (int)ceres_vars_calib_cam_intrinsics.size()});
806  ceres_vars_calib_cam_intrinsics.push_back(var_calib_cam);
807 
808  // Construct state and prior
809  Eigen::MatrixXd x_lin = Eigen::MatrixXd::Zero(8, 1);
810  for (int j = 0; j < 8; j++) {
811  x_lin(0 + j) = var_calib_cam[j];
812  }
813  Eigen::MatrixXd prior_grad = Eigen::MatrixXd::Zero(8, 1);
814  Eigen::MatrixXd prior_Info = Eigen::MatrixXd::Identity(8, 8);
815  prior_Info.block(0, 0, 4, 4) *= 1.0 / std::pow(1.0, 2);
816  prior_Info.block(4, 4, 4, 4) *= 1.0 / std::pow(0.005, 2);
817 
818  // Construct state type and ceres parameter pointers
819  std::vector<std::string> x_types;
820  std::vector<double *> factor_params;
821  factor_params.push_back(var_calib_cam);
822  x_types.emplace_back("vec8");
823  auto *factor_prior = new Factor_GenericPrior(x_lin, x_types, prior_Info, prior_grad);
824  problem.AddResidualBlock(factor_prior, nullptr, factor_params);
825  if (!params.init_dyn_mle_opt_calib) {
826  problem.SetParameterBlockConstant(var_calib_cam);
827  }
828  }
829  }
830  assert(map_calib_cam2imu.size() == map_calib_cam.size());
831 
832  // Then, append new feature observations factors seen from all cameras
833  for (auto const &feat : features) {
834  // Skip features that don't have enough measurements
835  if (map_features_num_meas[feat.first] < min_num_meas_to_optimize)
836  continue;
837  // Features can be removed if behind the camera!
838  if (features_inG.find(feat.first) == features_inG.end())
839  continue;
840  // Finally loop through each raw uv observation and append it as a factor
841  for (auto const &camtime : feat.second->timestamps) {
842 
843  // Get our ids and if the camera is a fisheye or not
844  size_t feat_id = feat.first;
845  size_t cam_id = camtime.first;
846  bool is_fisheye = (std::dynamic_pointer_cast<ov_core::CamEqui>(params.camera_intrinsics.at(cam_id)) != nullptr);
847 
848  // Loop through each observation
849  for (size_t i = 0; i < camtime.second.size(); i++) {
850 
851  // Skip measurements we don't have poses for
852  double time = feat.second->timestamps.at(cam_id).at(i);
853  if (map_camera_times.find(time) == map_camera_times.end())
854  continue;
855 
856  // Our measurement
857  Eigen::Vector2d uv_raw = feat.second->uvs.at(cam_id).at(i).block(0, 0, 2, 1).cast<double>();
858 
859  // If we don't have the feature state we should create that parameter block
860  // The initial guess of the features are the scaled feature map from the SFM
861  if (map_features.find(feat_id) == map_features.end()) {
862  auto *var_feat = new double[3];
863  for (int j = 0; j < 3; j++) {
864  var_feat[j] = features_inG.at(feat_id)(j);
865  }
866  problem.AddParameterBlock(var_feat, 3);
867  map_features.insert({feat_id, (int)ceres_vars_feat.size()});
868  ceres_vars_feat.push_back(var_feat);
869  }
870 
871  // Then lets add the factors
872  std::vector<double *> factor_params;
873  factor_params.push_back(ceres_vars_ori.at(map_states.at(time)));
874  factor_params.push_back(ceres_vars_pos.at(map_states.at(time)));
875  factor_params.push_back(ceres_vars_feat.at(map_features.at(feat_id)));
876  factor_params.push_back(ceres_vars_calib_cam2imu_ori.at(map_calib_cam2imu.at(cam_id)));
877  factor_params.push_back(ceres_vars_calib_cam2imu_pos.at(map_calib_cam2imu.at(cam_id)));
878  factor_params.push_back(ceres_vars_calib_cam_intrinsics.at(map_calib_cam.at(cam_id)));
879  auto *factor_pinhole = new Factor_ImageReprojCalib(uv_raw, params.sigma_pix, is_fisheye);
880  // ceres::LossFunction *loss_function = nullptr;
881  ceres::LossFunction *loss_function = new ceres::CauchyLoss(1.0);
882  problem.AddResidualBlock(factor_pinhole, loss_function, factor_params);
883  }
884  }
885  }
886  assert(ceres_vars_ori.size() == ceres_vars_bias_g.size());
887  assert(ceres_vars_ori.size() == ceres_vars_vel.size());
888  assert(ceres_vars_ori.size() == ceres_vars_bias_a.size());
889  assert(ceres_vars_ori.size() == ceres_vars_pos.size());
890  auto rT5 = boost::posix_time::microsec_clock::local_time();
891 
892  // Optimize the ceres graph
893  ceres::Solver::Summary summary;
894  ceres::Solve(options, &problem, &summary);
895  PRINT_INFO("[init-d]: %d iterations | %zu states, %zu feats (%zu valid) | %d param and %d res | cost %.4e => %.4e\n",
896  (int)summary.iterations.size(), map_states.size(), map_features.size(), count_valid_features, summary.num_parameters,
897  summary.num_residuals, summary.initial_cost, summary.final_cost);
898  auto rT6 = boost::posix_time::microsec_clock::local_time();
899 
900  // Return if we have failed!
901  timestamp = newest_cam_time;
902  if (params.init_dyn_mle_max_iter != 0 && summary.termination_type != ceres::CONVERGENCE) {
903  PRINT_WARNING(YELLOW "[init-d]: opt failed: %s!\n" RESET, summary.message.c_str());
904  free_state_memory();
905  return false;
906  }
907  PRINT_DEBUG("[init-d]: %s\n", summary.message.c_str());
908 
909  //======================================================
910  //======================================================
911 
912  // Helper function to get the IMU pose value from our ceres problem
913  auto get_pose = [&](double timestamp) {
914  Eigen::VectorXd state_imu = Eigen::VectorXd::Zero(16);
915  for (int i = 0; i < 4; i++) {
916  state_imu(0 + i) = ceres_vars_ori[map_states[timestamp]][i];
917  }
918  for (int i = 0; i < 3; i++) {
919  state_imu(4 + i) = ceres_vars_pos[map_states[timestamp]][i];
920  state_imu(7 + i) = ceres_vars_vel[map_states[timestamp]][i];
921  state_imu(10 + i) = ceres_vars_bias_g[map_states[timestamp]][i];
922  state_imu(13 + i) = ceres_vars_bias_a[map_states[timestamp]][i];
923  }
924  return state_imu;
925  };
926 
927  // Our most recent state is the IMU state!
928  assert(map_states.find(newest_cam_time) != map_states.end());
929  if (_imu == nullptr) {
930  _imu = std::make_shared<ov_type::IMU>();
931  }
932  Eigen::VectorXd imu_state = get_pose(newest_cam_time);
933  _imu->set_value(imu_state);
934  _imu->set_fej(imu_state);
935 
936  // Append our IMU clones (includes most recent)
937  for (auto const &statepair : map_states) {
938  Eigen::VectorXd pose = get_pose(statepair.first);
939  if (_clones_IMU.find(statepair.first) == _clones_IMU.end()) {
940  auto _pose = std::make_shared<ov_type::PoseJPL>();
941  _pose->set_value(pose.block(0, 0, 7, 1));
942  _pose->set_fej(pose.block(0, 0, 7, 1));
943  _clones_IMU.insert({statepair.first, _pose});
944  } else {
945  _clones_IMU.at(statepair.first)->set_value(pose.block(0, 0, 7, 1));
946  _clones_IMU.at(statepair.first)->set_fej(pose.block(0, 0, 7, 1));
947  }
948  }
949 
950  // Append features as SLAM features!
951  for (auto const &featpair : map_features) {
952  Eigen::Vector3d feature;
953  feature << ceres_vars_feat[featpair.second][0], ceres_vars_feat[featpair.second][1], ceres_vars_feat[featpair.second][2];
954  if (_features_SLAM.find(featpair.first) == _features_SLAM.end()) {
955  auto _feature = std::make_shared<ov_type::Landmark>(3);
956  _feature->_featid = featpair.first;
957  _feature->_feat_representation = LandmarkRepresentation::Representation::GLOBAL_3D;
958  _feature->set_from_xyz(feature, false);
959  _feature->set_from_xyz(feature, true);
960  _features_SLAM.insert({featpair.first, _feature});
961  } else {
962  _features_SLAM.at(featpair.first)->_featid = featpair.first;
963  _features_SLAM.at(featpair.first)->_feat_representation = LandmarkRepresentation::Representation::GLOBAL_3D;
964  _features_SLAM.at(featpair.first)->set_from_xyz(feature, false);
965  _features_SLAM.at(featpair.first)->set_from_xyz(feature, true);
966  }
967  }
968 
969  // If we optimized calibration, we should also save it to our state
970  if (params.init_dyn_mle_opt_calib) {
971  // TODO: append our calibration states too if we are doing calibration!
972  // TODO: (if we are not doing calibration do not calibrate them....)
973  // TODO: std::shared_ptr<ov_type::Vec> _calib_dt_CAMtoIMU,
974  // TODO: std::unordered_map<size_t, std::shared_ptr<ov_type::PoseJPL>> &_calib_IMUtoCAM,
975  // TODO: std::unordered_map<size_t, std::shared_ptr<ov_type::Vec>> &_cam_intrinsics
976  }
977 
978  // Recover the covariance here of the optimized states
979  // NOTE: for now just the IMU state is recovered, but we should be able to do everything
980  // NOTE: maybe having features / clones will make it more stable?
981  std::vector<std::pair<const double *, const double *>> covariance_blocks;
982  int state_index = map_states[newest_cam_time];
983  // diagonals
984  covariance_blocks.push_back(std::make_pair(ceres_vars_ori[state_index], ceres_vars_ori[state_index]));
985  covariance_blocks.push_back(std::make_pair(ceres_vars_pos[state_index], ceres_vars_pos[state_index]));
986  covariance_blocks.push_back(std::make_pair(ceres_vars_vel[state_index], ceres_vars_vel[state_index]));
987  covariance_blocks.push_back(std::make_pair(ceres_vars_bias_g[state_index], ceres_vars_bias_g[state_index]));
988  covariance_blocks.push_back(std::make_pair(ceres_vars_bias_a[state_index], ceres_vars_bias_a[state_index]));
989  // orientation
990  covariance_blocks.push_back(std::make_pair(ceres_vars_ori[state_index], ceres_vars_pos[state_index]));
991  covariance_blocks.push_back(std::make_pair(ceres_vars_ori[state_index], ceres_vars_vel[state_index]));
992  covariance_blocks.push_back(std::make_pair(ceres_vars_ori[state_index], ceres_vars_bias_g[state_index]));
993  covariance_blocks.push_back(std::make_pair(ceres_vars_ori[state_index], ceres_vars_bias_a[state_index]));
994  // position
995  covariance_blocks.push_back(std::make_pair(ceres_vars_pos[state_index], ceres_vars_vel[state_index]));
996  covariance_blocks.push_back(std::make_pair(ceres_vars_pos[state_index], ceres_vars_bias_g[state_index]));
997  covariance_blocks.push_back(std::make_pair(ceres_vars_pos[state_index], ceres_vars_bias_a[state_index]));
998  // velocity
999  covariance_blocks.push_back(std::make_pair(ceres_vars_vel[state_index], ceres_vars_bias_g[state_index]));
1000  covariance_blocks.push_back(std::make_pair(ceres_vars_vel[state_index], ceres_vars_bias_a[state_index]));
1001  // bias_g
1002  covariance_blocks.push_back(std::make_pair(ceres_vars_bias_g[state_index], ceres_vars_bias_a[state_index]));
1003 
1004  // Finally, compute the covariance
1005  ceres::Covariance::Options options_cov;
1006  options_cov.null_space_rank = (!params.init_dyn_mle_opt_calib) * ((int)map_calib_cam2imu.size() * (6 + 8));
1007  options_cov.min_reciprocal_condition_number = params.init_dyn_min_rec_cond;
1008  // options_cov.algorithm_type = ceres::CovarianceAlgorithmType::DENSE_SVD;
1009  options_cov.apply_loss_function = true; // Better consistency if we use this
1010  options_cov.num_threads = params.init_dyn_mle_max_threads;
1011  ceres::Covariance problem_cov(options_cov);
1012  bool success = problem_cov.Compute(covariance_blocks, &problem);
1013  if (!success) {
1014  PRINT_WARNING(YELLOW "[init-d]: covariance recovery failed...\n" RESET);
1015  free_state_memory();
1016  return false;
1017  }
1018 
1019  // construct the covariance we will return
1020  order.clear();
1021  order.push_back(_imu);
1022  covariance = Eigen::MatrixXd::Zero(_imu->size(), _imu->size());
1023  Eigen::Matrix<double, 3, 3, Eigen::RowMajor> covtmp = Eigen::Matrix<double, 3, 3, Eigen::RowMajor>::Zero();
1024 
1025  // block diagonal
1026  CHECK(problem_cov.GetCovarianceBlockInTangentSpace(ceres_vars_ori[state_index], ceres_vars_ori[state_index], covtmp.data()));
1027  covariance.block(0, 0, 3, 3) = covtmp.eval();
1028  CHECK(problem_cov.GetCovarianceBlockInTangentSpace(ceres_vars_pos[state_index], ceres_vars_pos[state_index], covtmp.data()));
1029  covariance.block(3, 3, 3, 3) = covtmp.eval();
1030  CHECK(problem_cov.GetCovarianceBlockInTangentSpace(ceres_vars_vel[state_index], ceres_vars_vel[state_index], covtmp.data()));
1031  covariance.block(6, 6, 3, 3) = covtmp.eval();
1032  CHECK(problem_cov.GetCovarianceBlockInTangentSpace(ceres_vars_bias_g[state_index], ceres_vars_bias_g[state_index], covtmp.data()));
1033  covariance.block(9, 9, 3, 3) = covtmp.eval();
1034  CHECK(problem_cov.GetCovarianceBlockInTangentSpace(ceres_vars_bias_a[state_index], ceres_vars_bias_a[state_index], covtmp.data()));
1035  covariance.block(12, 12, 3, 3) = covtmp.eval();
1036 
1037  // orientation
1038  CHECK(problem_cov.GetCovarianceBlockInTangentSpace(ceres_vars_ori[state_index], ceres_vars_pos[state_index], covtmp.data()));
1039  covariance.block(0, 3, 3, 3) = covtmp.eval();
1040  covariance.block(3, 0, 3, 3) = covtmp.transpose();
1041  CHECK(problem_cov.GetCovarianceBlockInTangentSpace(ceres_vars_ori[state_index], ceres_vars_vel[state_index], covtmp.data()));
1042  covariance.block(0, 6, 3, 3) = covtmp.eval();
1043  covariance.block(6, 0, 3, 3) = covtmp.transpose().eval();
1044  CHECK(problem_cov.GetCovarianceBlockInTangentSpace(ceres_vars_ori[state_index], ceres_vars_bias_g[state_index], covtmp.data()));
1045  covariance.block(0, 9, 3, 3) = covtmp.eval();
1046  covariance.block(9, 0, 3, 3) = covtmp.transpose().eval();
1047  CHECK(problem_cov.GetCovarianceBlockInTangentSpace(ceres_vars_ori[state_index], ceres_vars_bias_a[state_index], covtmp.data()));
1048  covariance.block(0, 12, 3, 3) = covtmp.eval();
1049  covariance.block(12, 0, 3, 3) = covtmp.transpose().eval();
1050 
1051  // position
1052  CHECK(problem_cov.GetCovarianceBlockInTangentSpace(ceres_vars_pos[state_index], ceres_vars_vel[state_index], covtmp.data()));
1053  covariance.block(3, 6, 3, 3) = covtmp.eval();
1054  covariance.block(6, 3, 3, 3) = covtmp.transpose().eval();
1055  CHECK(problem_cov.GetCovarianceBlockInTangentSpace(ceres_vars_pos[state_index], ceres_vars_bias_g[state_index], covtmp.data()));
1056  covariance.block(3, 9, 3, 3) = covtmp.eval();
1057  covariance.block(9, 3, 3, 3) = covtmp.transpose().eval();
1058  CHECK(problem_cov.GetCovarianceBlockInTangentSpace(ceres_vars_pos[state_index], ceres_vars_bias_a[state_index], covtmp.data()));
1059  covariance.block(3, 12, 3, 3) = covtmp.eval();
1060  covariance.block(12, 3, 3, 3) = covtmp.transpose().eval();
1061 
1062  // velocity
1063  CHECK(problem_cov.GetCovarianceBlockInTangentSpace(ceres_vars_vel[state_index], ceres_vars_bias_g[state_index], covtmp.data()));
1064  covariance.block(6, 9, 3, 3) = covtmp.eval();
1065  covariance.block(9, 6, 3, 3) = covtmp.transpose().eval();
1066  CHECK(problem_cov.GetCovarianceBlockInTangentSpace(ceres_vars_vel[state_index], ceres_vars_bias_a[state_index], covtmp.data()));
1067  covariance.block(6, 12, 3, 3) = covtmp.eval();
1068  covariance.block(12, 6, 3, 3) = covtmp.transpose().eval();
1069 
1070  // bias_g
1071  CHECK(problem_cov.GetCovarianceBlockInTangentSpace(ceres_vars_bias_g[state_index], ceres_vars_bias_a[state_index], covtmp.data()));
1072  covariance.block(9, 12, 3, 3) = covtmp.eval();
1073  covariance.block(12, 9, 3, 3) = covtmp.transpose().eval();
1074 
1075  // inflate as needed
1076  covariance.block(0, 0, 3, 3) *= params.init_dyn_inflation_orientation;
1077  covariance.block(6, 6, 3, 3) *= params.init_dyn_inflation_velocity;
1078  covariance.block(9, 9, 3, 3) *= params.init_dyn_inflation_bias_gyro;
1079  covariance.block(12, 12, 3, 3) *= params.init_dyn_inflation_bias_accel;
1080 
1081  // we are done >:D
1082  covariance = 0.5 * (covariance + covariance.transpose());
1083  Eigen::Vector3d sigmas_vel = covariance.block(6, 6, 3, 3).diagonal().transpose().cwiseSqrt();
1084  Eigen::Vector3d sigmas_bg = covariance.block(9, 9, 3, 3).diagonal().transpose().cwiseSqrt();
1085  Eigen::Vector3d sigmas_ba = covariance.block(12, 12, 3, 3).diagonal().transpose().cwiseSqrt();
1086  PRINT_DEBUG("[init-d]: vel priors = %.3f, %.3f, %.3f\n", sigmas_vel(0), sigmas_vel(1), sigmas_vel(2));
1087  PRINT_DEBUG("[init-d]: bg priors = %.3f, %.3f, %.3f\n", sigmas_bg(0), sigmas_bg(1), sigmas_bg(2));
1088  PRINT_DEBUG("[init-d]: ba priors = %.3f, %.3f, %.3f\n", sigmas_ba(0), sigmas_ba(1), sigmas_ba(2));
1089 
1090  // Set our position to be zero
1091  Eigen::MatrixXd x = _imu->value();
1092  x.block(4, 0, 3, 1).setZero();
1093  _imu->set_value(x);
1094  _imu->set_fej(x);
1095 
1096  // Debug timing information about how long it took to initialize!!
1097  auto rT7 = boost::posix_time::microsec_clock::local_time();
1098  PRINT_DEBUG("[TIME]: %.4f sec for prelim tests\n", (rT2 - rT1).total_microseconds() * 1e-6);
1099  PRINT_DEBUG("[TIME]: %.4f sec for linsys setup\n", (rT3 - rT2).total_microseconds() * 1e-6);
1100  PRINT_DEBUG("[TIME]: %.4f sec for linsys\n", (rT4 - rT3).total_microseconds() * 1e-6);
1101  PRINT_DEBUG("[TIME]: %.4f sec for ceres opt setup\n", (rT5 - rT4).total_microseconds() * 1e-6);
1102  PRINT_DEBUG("[TIME]: %.4f sec for ceres opt\n", (rT6 - rT5).total_microseconds() * 1e-6);
1103  PRINT_DEBUG("[TIME]: %.4f sec for ceres covariance\n", (rT7 - rT6).total_microseconds() * 1e-6);
1104  PRINT_DEBUG("[TIME]: %.4f sec total for initialization\n", (rT7 - rT1).total_microseconds() * 1e-6);
1105  free_state_memory();
1106  return true;
1107 }
quat_ops.h
IMU.h
YELLOW
YELLOW
ov_core::quat_multiply
Eigen::Matrix< double, 4, 1 > quat_multiply(const Eigen::Matrix< double, 4, 1 > &q, const Eigen::Matrix< double, 4, 1 > &p)
PRINT_DEBUG
#define PRINT_DEBUG(x...)
sensor_data.h
CpiV1.h
PRINT_ERROR
#define PRINT_ERROR(x...)
Factor_ImuCPIv1.h
ov_init::Factor_GenericPrior
Factor for generic state priors for specific types.
Definition: Factor_GenericPrior.h:72
ov_init::Factor_ImuCPIv1
Factor for IMU continuous preintegration version 1.
Definition: Factor_ImuCPIv1.h:32
d
d
ov_init
State initialization code.
Definition: Factor_GenericPrior.h:27
ov_core::rot_2_quat
Eigen::Matrix< double, 4, 1 > rot_2_quat(const Eigen::Matrix< double, 3, 3 > &rot)
ov_core::quat_2_Rot
Eigen::Matrix< double, 3, 3 > quat_2_Rot(const Eigen::Matrix< double, 4, 1 > &q)
Factor_GenericPrior.h
State_JPLQuatLocal.h
print.h
colors.h
Landmark.h
PRINT_INFO
#define PRINT_INFO(x...)
ov_type
RESET
#define RESET
Feature.h
FeatureDatabase.h
ov_init::Factor_ImageReprojCalib
Factor of feature bearing observation (raw) with calibration.
Definition: Factor_ImageReprojCalib.h:41
ov_init::State_JPLQuatLocal
JPL quaternion CERES state parameterization.
Definition: State_JPLQuatLocal.h:32
Factor_ImageReprojCalib.h
helper.h
ov_core
RED
RED
PRINT_WARNING
#define PRINT_WARNING(x...)
DynamicInitializer.h


ov_init
Author(s): Patrick Geneva , Kevin Eckenhoff , Guoquan Huang
autogenerated on Mon Dec 16 2024 03:06:51