Skip to content

Commit db03558

Browse files
authored
Merge pull request #72 from Cardinal-Space-Mining/functionality
Battle hardened odometry
2 parents e4294ce + f31c446 commit db03558

File tree

7 files changed

+227
-135
lines changed

7 files changed

+227
-135
lines changed

include/modules/impl/lidar_odom_impl.hpp

Lines changed: 51 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,9 @@ void LidarOdometry<PT>::initState()
321321
this->gicp.setSearchMethodSource(temp, true);
322322
this->gicp.setSearchMethodTarget(temp, true);
323323

324+
// this->gicp_s2s.setDebugPrint(true);
325+
// this->gicp.setDebugPrint(true);
326+
324327
this->vf_scan.setLeafSize(
325328
this->param.vf_scan_res_,
326329
this->param.vf_scan_res_,
@@ -342,14 +345,14 @@ bool LidarOdometry<PT>::setInitial(const util::geom::Pose3f& pose)
342345
// set known position
343346
this->state.T.template block<3, 1>(0, 3) =
344347
this->state.T_s2s.template block<3, 1>(0, 3) =
345-
this->state.T_s2s_prev.template block<3, 1>(0, 3) =
348+
this->state.T_prev.template block<3, 1>(0, 3) =
346349
this->state.translation = pose.vec;
347350

348351
// set known orientation
349352
this->state.last_rotq = this->state.rotq = pose.quat;
350353
this->state.T.template block<3, 3>(0, 0) =
351354
this->state.T_s2s.template block<3, 3>(0, 0) =
352-
this->state.T_s2s_prev.template block<3, 3>(0, 0) =
355+
this->state.T_prev.template block<3, 3>(0, 0) =
353356
this->state.rotq.toRotationMatrix();
354357

355358
return true;
@@ -369,7 +372,8 @@ typename LidarOdometry<PT>::IterationStatus LidarOdometry<PT>::processScan(
369372
if (stamp <= this->state.prev_frame_stamp)
370373
{
371374
std::cout << "[ODOMETRY]: Scan timestamp is older than the previous by "
372-
<< (this->state.prev_frame_stamp - stamp) << "s!" << std::endl;
375+
<< (this->state.prev_frame_stamp - stamp) << "s!"
376+
<< std::endl;
373377
return {};
374378
}
375379

@@ -414,7 +418,10 @@ typename LidarOdometry<PT>::IterationStatus LidarOdometry<PT>::processScan(
414418
this->setInputSources();
415419

416420
// Get the next pose via IMU + S2S + S2M
417-
this->getNextPose(align_estimate);
421+
if(!this->getNextPose(align_estimate))
422+
{
423+
return {};
424+
}
418425

419426
// Transform point cloud
420427
this->transformCurrentScan();
@@ -508,6 +515,15 @@ bool LidarOdometry<PT>::preprocessPoints(const PointCloudT& scan)
508515
return false;
509516
}
510517

518+
// for(const auto& pt : scan.points)
519+
// {
520+
// if(pt.getArray3fMap().isNaN().any())
521+
// {
522+
// std::cout << "[ODOMETRY]: Input cloud had NaN input!" << std::endl;
523+
// return false;
524+
// }
525+
// }
526+
511527
// Find new voxel size before applying filter
512528
if (this->param.adaptive_params_use_)
513529
{
@@ -710,7 +726,7 @@ void LidarOdometry<PT>::setInputSources()
710726
}
711727

712728
template<typename PT>
713-
void LidarOdometry<PT>::getNextPose(const std::optional<Mat4f>& align_estimate)
729+
bool LidarOdometry<PT>::getNextPose(const std::optional<Mat4f>& align_estimate)
714730
{
715731
//
716732
// FRAME-TO-FRAME PROCEDURE
@@ -732,16 +748,20 @@ void LidarOdometry<PT>::getNextPose(const std::optional<Mat4f>& align_estimate)
732748
}
733749

734750
// Get the local S2S transform
735-
Mat4f T_S2S = this->gicp_s2s.getFinalTransformation();
751+
const Mat4f T_s2s_local = this->gicp_s2s.getFinalTransformation();
752+
753+
if (T_s2s_local.array().isNaN().any())
754+
{
755+
std::cout << "[ODOMETRY]: S2S transform contained NaN values!"
756+
<< std::endl;
757+
return false;
758+
}
736759

737760
// Get the global S2S transform
738-
this->propagateS2S(T_S2S);
761+
this->propagateS2S(T_s2s_local);
739762

740763
// reuse covariances from s2s for s2m
741-
this->gicp.source_covs_ = this->gicp_s2s.source_covs_;
742-
743-
// Swap source and target (which also swaps KdTrees internally) for next S2S
744-
this->gicp_s2s.swapSourceAndTarget();
764+
this->gicp.source_covs_ = this->gicp_s2s.target_covs_;
745765

746766
//
747767
// FRAME-TO-SUBMAP
@@ -768,24 +788,39 @@ void LidarOdometry<PT>::getNextPose(const std::optional<Mat4f>& align_estimate)
768788
}
769789

770790
// Get final transformation in global frame
771-
this->state.T = this->gicp.getFinalTransformation();
791+
const Mat4f T = this->gicp.getFinalTransformation();
792+
793+
if (T.array().isNaN().any())
794+
{
795+
std::cout << "[ODOMETRY]: S2M transform contained NaN values!"
796+
<< std::endl;
797+
return false;
798+
}
799+
800+
this->state.T = std::move(T);
801+
802+
// Swap source and target (which also swaps KdTrees internally) for next S2S
803+
// (don't do this until we know it is safe to do so)
804+
this->gicp_s2s.swapSourceAndTarget();
772805

773806
// Update the S2S transform for next propagation
774-
this->state.T_s2s_prev = this->state.T;
807+
this->state.T_prev = this->state.T;
775808

776809
// Update next global pose
777810
// Both source and target clouds are in the global frame now, so tranformation is global
778811
this->propagateS2M();
779812

780813
// Set next target cloud as current source cloud
781814
*this->target_cloud = *this->current_scan;
815+
816+
return true;
782817
}
783818

784819
template<typename PT>
785-
void LidarOdometry<PT>::propagateS2S(const Mat4f& T)
820+
void LidarOdometry<PT>::propagateS2S(const Mat4f& T_rel)
786821
{
787-
this->state.T_s2s = this->state.T_s2s_prev * T;
788-
this->state.T_s2s_prev = this->state.T_s2s;
822+
this->state.T_s2s = this->state.T_prev * T_rel;
823+
// this->state.T_prev = this->state.T_s2s;
789824
}
790825

791826
template<typename PT>

include/modules/imu_integrator.hpp

Lines changed: 88 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ class ImuIntegrator
8484
public:
8585
void addSample(const ImuMsg& imu);
8686
void trimSamples(TimeFloatT trim_ts);
87+
void setAutoTrimWindow(TimeFloatT window_s = 0.f);
8788
bool recalibrate(TimeFloatT dt, bool force = false);
8889

8990
Vec3 estimateGravity(
@@ -121,6 +122,7 @@ class ImuIntegrator
121122
std::atomic<bool> use_orientation;
122123

123124
const double calib_time;
125+
double trim_window{600.0};
124126
};
125127

126128

@@ -156,10 +158,13 @@ void ImuIntegrator<F>::addSample(const ImuMsg& imu)
156158
stamp,
157159
q);
158160

159-
// 10 min max, orientation is likely still valid but at this point we probably have worse problems
160-
util::tsq::trimToStamp(
161-
this->orient_buffer,
162-
(util::tsq::newestStamp(this->orient_buffer) - 600.));
161+
if (this->trim_window > 0)
162+
{
163+
util::tsq::trimToStamp(
164+
this->orient_buffer,
165+
(util::tsq::newestStamp(this->orient_buffer) -
166+
this->trim_window));
167+
}
163168
}
164169

165170
{
@@ -179,10 +184,12 @@ void ImuIntegrator<F>::addSample(const ImuMsg& imu)
179184
this->recalibrateRange(0, this->raw_buffer.size());
180185
}
181186

182-
// 5 min max, integration is definitely deviated after this for most imus
183-
util::tsq::trimToStamp(
184-
this->raw_buffer,
185-
(util::tsq::newestStamp(this->raw_buffer) - 300.));
187+
if (this->trim_window > 0)
188+
{
189+
util::tsq::trimToStamp(
190+
this->raw_buffer,
191+
(util::tsq::newestStamp(this->raw_buffer) - this->trim_window));
192+
}
186193
}
187194
}
188195

@@ -195,6 +202,14 @@ void ImuIntegrator<F>::trimSamples(TimeFloatT trim_ts)
195202
util::tsq::trimToStamp(this->raw_buffer, trim_ts);
196203
}
197204

205+
template<typename F>
206+
void ImuIntegrator<F>::setAutoTrimWindow(TimeFloatT window_s)
207+
{
208+
std::unique_lock imu_lock{this->mtx};
209+
210+
this->trim_window = window_s;
211+
}
212+
198213
template<typename F>
199214
bool ImuIntegrator<F>::recalibrate(TimeFloatT dt, bool force)
200215
{
@@ -265,6 +280,8 @@ typename ImuIntegrator<F>::Quat ImuIntegrator<F>::getDelta(
265280
TimeFloatT start,
266281
TimeFloatT end) const
267282
{
283+
using namespace util::geom::cvt::ops;
284+
268285
Quat q = Quat::Identity();
269286
std::unique_lock imu_lock{this->mtx};
270287

@@ -282,72 +299,89 @@ typename ImuIntegrator<F>::Quat ImuIntegrator<F>::getDelta(
282299
newest--;
283300
}
284301

285-
if (newest != oldest)
302+
if (newest < oldest)
286303
{
287304
const auto& a = this->orient_buffer[oldest];
288305
const auto& b = this->orient_buffer[oldest - 1];
289306
const auto& c = this->orient_buffer[newest + 1];
290307
const auto& d = this->orient_buffer[newest];
291308

292-
Quat prev = a.second.slerp(
309+
const Quat prev = a.second.slerp(
293310
(start - a.first) / (b.first - a.first),
294311
b.second);
295-
Quat curr =
312+
const Quat curr =
296313
c.second.slerp((end - c.first) / (d.first - c.first), d.second);
297314

298315
q = prev.inverse() * curr;
299316
}
300317
}
301318
else
302319
{
303-
assert(!"IMU angular velocity integration is not implemented!");
304-
#if 0 // TODO
305-
const size_t
306-
init_idx = util::tsq::binarySearchIdx(this->imu_buffer, start),
307-
end_idx = util::tsq::binarySearchIdx(this->imu_buffer, end);
308-
309-
// Relative IMU integration of gyro and accelerometer
310-
double curr_imu_stamp = 0.;
311-
double prev_imu_stamp = 0.;
312-
double dt;
313-
314-
for(size_t i = init_idx; i >= end_idx; i--)
320+
size_t oldest = util::tsq::binarySearchIdx(this->raw_buffer, start);
321+
size_t newest = util::tsq::binarySearchIdx(this->raw_buffer, end);
322+
323+
if (oldest == this->raw_buffer.size() && oldest > 0)
324+
{
325+
oldest--;
326+
}
327+
if (newest > 0)
315328
{
316-
const auto& imu_sample = this->raw_buffer[i];
329+
newest--;
330+
}
317331

318-
if(prev_imu_stamp == 0.)
332+
if (newest < oldest)
333+
{
334+
const auto& a = this->raw_buffer[oldest];
335+
const auto& b = this->raw_buffer[oldest - 1];
336+
const auto& c = this->raw_buffer[newest + 1];
337+
const auto& d = this->raw_buffer[newest];
338+
339+
const Vec3 head =
340+
a.second.ang_vel + (b.second.ang_vel - a.second.ang_vel) *
341+
(start - a.first) / (b.first - a.first);
342+
const Vec3 tail =
343+
c.second.ang_vel + (d.second.ang_vel - c.second.ang_vel) *
344+
(end - c.first) / (d.first - c.first);
345+
346+
for (size_t i = oldest; i > newest; i--)
319347
{
320-
prev_imu_stamp = imu_sample.first;
321-
continue;
348+
const Vec3 *from, *to;
349+
double from_t, to_t;
350+
if (i == oldest)
351+
{
352+
from = &head;
353+
from_t = start;
354+
}
355+
else
356+
{
357+
from = &(this->raw_buffer[i].second.ang_vel);
358+
from_t = this->raw_buffer[i].first;
359+
}
360+
361+
if (i - 1 == newest)
362+
{
363+
to = &tail;
364+
to_t = end;
365+
}
366+
else
367+
{
368+
to = &(this->raw_buffer[i - 1].second.ang_vel);
369+
to_t = this->raw_buffer[i - 1].first;
370+
}
371+
372+
const Vec3 avg_w = (*from + *to) * 0.5f;
373+
const FloatT dt = static_cast<FloatT>(to_t - from_t);
374+
const Vec3 theta = avg_w * dt;
375+
const FloatT angle = theta.norm();
376+
377+
if (angle > 1e-8)
378+
{
379+
const Vec3 axis = theta / angle;
380+
q = (q * Quat{Eigen::AngleAxis<FloatT>(angle, axis)})
381+
.normalized();
382+
}
322383
}
323-
324-
// Calculate difference in imu measurement times IN SECONDS
325-
curr_imu_stamp = imu_sample.first;
326-
dt = curr_imu_stamp - prev_imu_stamp;
327-
prev_imu_stamp = curr_imu_stamp;
328-
329-
// Relative gyro propagation quaternion dynamics
330-
Quatd qq = q;
331-
q.w() -= 0.5 * dt *
332-
(qq.x() * imu_sample.second.ang_vel.x()
333-
+ qq.y() * imu_sample.second.ang_vel.y()
334-
+ qq.z() * imu_sample.second.ang_vel.z() );
335-
q.x() += 0.5 * dt *
336-
(qq.w() * imu_sample.second.ang_vel.x()
337-
- qq.z() * imu_sample.second.ang_vel.y()
338-
+ qq.y() * imu_sample.second.ang_vel.z() );
339-
q.y() += 0.5 * dt *
340-
(qq.z() * imu_sample.second.ang_vel.x()
341-
+ qq.w() * imu_sample.second.ang_vel.y()
342-
- qq.x() * imu_sample.second.ang_vel.z() );
343-
q.z() += 0.5 * dt *
344-
(qq.x() * imu_sample.second.ang_vel.y()
345-
- qq.y() * imu_sample.second.ang_vel.x()
346-
+ qq.w() * imu_sample.second.ang_vel.z() );
347384
}
348-
349-
q.normalize();
350-
#endif
351385
}
352386

353387
return q;

include/modules/lidar_odom.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ class LidarOdometry
151151
void initializeInputTarget();
152152
void setInputSources();
153153

154-
void getNextPose(const std::optional<Mat4f>& align_estimate = std::nullopt);
154+
bool getNextPose(const std::optional<Mat4f>& align_estimate = std::nullopt);
155155

156156
void propagateS2S(const Mat4f& T);
157157
void propagateS2M();
@@ -211,7 +211,7 @@ class LidarOdometry
211211

212212
Mat4f T{Mat4f::Identity()};
213213
Mat4f T_s2s{Mat4f::Identity()};
214-
Mat4f T_s2s_prev{Mat4f::Identity()};
214+
Mat4f T_prev{Mat4f::Identity()};
215215

216216
std::mutex mtx;
217217
} state;

0 commit comments

Comments
 (0)