KISS-ICP代码逐行解析:从点云去畸变到体素地图更新,看懂纯激光SLAM核心
2026/6/15 5:24:43 网站建设 项目流程

KISS-ICP代码逐行解析:从点云去畸变到体素地图更新

激光SLAM领域近年来涌现出许多优秀的开源算法,其中KISS-ICP以其简洁高效的特点备受关注。本文将深入解析其核心代码实现,揭示这个"保持简单直接"(Keep It Simple and Straightforward)的激光里程计如何在保证精度的同时实现惊人的运行效率。

1. 点云预处理与运动补偿

激光雷达在运动过程中采集数据时,由于自身运动会导致点云产生畸变。KISS-ICP采用等速模型进行运动补偿,这一过程主要在Deskew.cpp中实现。

std::vector<Eigen::Vector3d> DeSkewScan( const std::vector<Eigen::Vector3d>& frame, const std::vector<double>& timestamps, const Sophus::SE3d& start_pose, const Sophus::SE3d& finish_pose) { const auto delta_pose = (start_pose.inverse() * finish_pose).log(); std::vector<Eigen::Vector3d> corrected_frame(frame.size()); tbb::parallel_for(size_t(0), frame.size(), [&](size_t i) { const auto motion = Sophus::SE3d::exp( (timestamps[i] - mid_pose_timestamp) * delta_pose); corrected_frame[i] = motion * frame[i]; }); return corrected_frame; }

这段代码的关键点在于:

  • 并行处理:使用TBB库实现多线程加速,显著提升处理速度
  • 位姿插值:通过Sophus::SE3d::exp实现李群上的线性插值
  • 时间对齐mid_pose_timestamp(0.5)假设扫描从0开始到1结束

预处理阶段还包括距离滤波和体素下采样,在Preprocessing.cpp中实现:

std::vector<Eigen::Vector3d> Preprocess( const std::vector<Eigen::Vector3d>& frame, double max_range, double min_range) { std::vector<Eigen::Vector3d> inliers; std::copy_if(frame.cbegin(), frame.cend(), std::back_inserter(inliers), [&](const auto& pt) { const double norm = pt.norm(); return norm < max_range && norm > min_range; }); return inliers; }

预处理优化技巧

  • 移除距离过近和过远的点(典型设置:min_range=1.0m,max_range=100.0m)
  • 使用std::copy_if避免不必要的内存分配
  • 后续体素下采样可减少80%以上的点数

2. 体素哈希地图实现

KISS-ICP的高效性很大程度上源于其创新的体素哈希地图设计,核心代码位于VoxelHashMap.cpp

2.1 数据结构设计

struct VoxelBlock { std::vector<Eigen::Vector3d> points; size_t max_points; void AddPoint(const Eigen::Vector3d& point) { if (points.size() < max_points) { points.emplace_back(point); } else { // 替换最旧的点 points[points.size() % max_points] = point; } } }; tsl::robin_map<Voxel, VoxelBlock, VoxelHash> map_;

这种设计实现了:

  • 内存高效:每个体素最多存储20个点(可配置)
  • 快速查询:使用robin_map哈希表实现O(1)复杂度查找
  • 滑动窗口:新点替换旧点,保持地图更新

2.2 对应点搜索算法

Vector3dVectorTuple GetCorrespondences( const Vector3dVector& points, double max_correspondance_distance) const { auto GetClosestNeighboor = [&](const Eigen::Vector3d& point) { // 计算3x3x3邻域体素 std::vector<Voxel> voxels; for (int i = -1; i <= 1; ++i) { for (int j = -1; j <= 1; ++j) { for (int k = -1; k <= 1; ++k) { voxels.emplace_back( static_cast<int>(point[0]/voxel_size_) + i, static_cast<int>(point[1]/voxel_size_) + j, static_cast<int>(point[2]/voxel_size_) + k); } } } // 在邻域内寻找最近点 Eigen::Vector3d closest_neighbor; double min_distance = std::numeric_limits<double>::max(); for (const auto& voxel : voxels) { if (map_.contains(voxel)) { for (const auto& neighbor : map_.find(voxel)->second.points) { double distance = (neighbor - point).squaredNorm(); if (distance < min_distance) { closest_neighbor = neighbor; min_distance = distance; } } } } return closest_neighbor; }; // 并行处理所有点 return tbb::parallel_reduce(...); }

搜索优化策略

  • 局部性原理:只检查3x3x3邻域体素,而非全局搜索
  • 并行计算:使用TBB实现多线程加速
  • 早期终止:找到满足距离阈值的点即停止

3. ICP配准核心算法

KISS-ICP的配准算法在Registration.cpp中实现,采用点对点ICP变种。

3.1 误差函数与优化

Sophus::SE3d AlignClouds( const std::vector<Eigen::Vector3d>& source, const std::vector<Eigen::Vector3d>& target, double th) { auto compute_jacobian_and_residual = [&](auto i) { const Eigen::Vector3d residual = source[i] - target[i]; Eigen::Matrix3_6d J_r; J_r.block<3,3>(0,0) = Eigen::Matrix3d::Identity(); J_r.block<3,3>(0,3) = -1.0 * Sophus::SO3d::hat(source[i]); return std::make_tuple(J_r, residual); }; // 并行计算雅可比矩阵和残差 const auto& [JTJ, JTr] = tbb::parallel_reduce(...); // 求解线性系统 const Eigen::Vector6d x = JTJ.ldlt().solve(-JTr); return Sophus::SE3d::exp(x); }

算法特点

  • 加权最小二乘:使用Cauchy核函数降低外点影响
  • 李代数表示:直接优化SE(3)上的位姿
  • 并行加速:雅可比矩阵计算完全并行化

3.2 自适应阈值机制

KISS-ICP的创新点之一是自适应阈值,代码位于Threshold.cpp

double AdaptiveThreshold::ComputeThreshold() { double model_error = ComputeModelError(model_deviation_, max_range_); if (model_error > min_motion_th_) { model_error_sse2_ += model_error * model_error; num_samples_++; } return std::sqrt(model_error_sse2_ / num_samples_); }

自适应策略

  • 初始使用固定阈值(默认2.0m)
  • 当检测到足够运动时(>min_motion_th),开始计算移动平均
  • 阈值随运动不确定性动态调整

4. 系统集成与性能优化

KISS-ICP的主流程在KissICP.cpp中实现,展示了各模块的协同工作。

4.1 数据处理流水线

Vector3dVectorTuple KissICP::RegisterFrame( const std::vector<Eigen::Vector3d>& frame) { // 1. 预处理 const auto& cropped_frame = Preprocess(frame, config_.max_range, config_.min_range); // 2. 体素下采样 const auto& [source, frame_downsample] = Voxelize(cropped_frame); // 3. 获取自适应阈值 const double sigma = GetAdaptiveThreshold(); // 4. 运动预测 const auto prediction = GetPredictionModel(); // 5. ICP配准 const Sophus::SE3d new_pose = RegisterFrame(source, local_map_, initial_guess, 3.0*sigma, sigma/3.0); // 6. 更新地图和位姿 local_map_.Update(frame_downsample, new_pose); poses_.push_back(new_pose); return {frame, source}; }

4.2 关键性能优化点

内存管理优化

  • 预分配向量空间避免重复分配
  • 使用移动语义减少拷贝
  • 对象复用降低构造开销

计算优化

  • 并行化所有可并行阶段
  • 使用SIMD友好数据结构
  • 减少锁竞争(无锁哈希表)

精度优化

  • 两级体素下采样保留更多结构
  • 运动补偿减少畸变影响
  • 自适应阈值平衡收敛性和精度

5. 实际应用建议

基于代码分析,给出以下实用建议:

参数调优指南

参数默认值调整建议影响
voxel_size1.00.5-2.0下采样粒度
max_points_per_voxel2010-50地图密度
initial_threshold2.01.0-5.0初始收敛性
min_motion_th0.10.05-0.2自适应灵敏度

性能瓶颈分析

  1. 点云去畸变:在高速运动场景消耗约15%时间
  2. 体素下采样:约占20%处理时间
  3. 对应点搜索:最耗时的阶段,占40%以上
  4. ICP优化:约占25%时间

扩展改进方向

  • 集成IMU进行运动补偿
  • 添加回环检测模块
  • 支持多激光雷达融合
  • 开发ROS2版本

KISS-ICP的成功在于其简洁而高效的设计哲学,通过深入理解这些代码实现,开发者可以更好地应用和扩展这一算法,为各种机器人定位建图场景提供可靠解决方案。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询