CT-LIO(Continuous-Time LiDAR-Inertial Odometry)是一套连续时间 LiDAR-IMU 紧耦合里程计:用 ct-icp 的 LiDAR 约束与 IMU 数据通过 ESKF(松耦合)融合,对快速运动鲁棒;同时提供 ct-icp 的解析求导与自动求导两种实现,以及简单的退化检测。算法对 lidar–imu 不严格时间同步的设备也能工作良好。
本仓库是在上游 chengwei0427/ct-lio 基础上的 Docker 化 + 工程适配 fork,主要面向 Robosense RS16「staircase」数据集调通。下文以 Docker 工作流为主线;若想在宿主机原生编译,见文末「附:原生编译」。
-
重力对齐初始化(修复地图歪斜) 上游 ESKF 初始化时把初始姿态
R_留为单位阵,世界系直接等于初始 IMU 体坐标系——传感器有安装倾角时整张地图就是歪的。本仓库在src/algo/eskf.hpp的SetInitialConditions中,用静止段估计的重力方向构造初始旋转,使世界系 Z 轴竖直向上、重力固定为(0,0,-|g|)。初始化成功时会打印对齐后的yaw/pitch/roll与世界系重力,便于核对。 -
完整 Docker 环境 + 三个脚本(见下),改源码后秒级增量编译,无需重建镜像。
-
面向 Robosense RS16 staircase 的参数与配置(
config/mapping.yaml),lidar/imu topic、外参、体素尺寸等已按该数据集设好。
源码以 volume 实时挂载进常驻容器,因此改完 src/ 只需在容器内增量 catkin_make(秒级),不必重建镜像。
| 脚本 | 作用 | 何时运行 |
|---|---|---|
scripts/build_env.sh |
构建环境镜像(ROS Noetic + Ceres 2.1.0 等重依赖,约 10–20 分钟) | 首次;或改了 Dockerfile / 根 CMakeLists.txt / cmake/ / 依赖 |
scripts/compile.sh |
常驻容器内增量编译 src/(秒级) |
每次改了 src/ 下的 .cpp / .h |
scripts/run.sh |
启动算法节点 + RViz,并播放 rosbag | 跑数据时 |
| 项目 | 要求 |
|---|---|
| 宿主机系统 | Ubuntu 20.04 / 22.04 |
| Docker | ≥ 24.0 |
| NVIDIA 驱动 | ≥ 525 |
| NVIDIA Container Toolkit | 已安装并配置 |
| 显示环境 | X11(用于 RViz) |
# 1) 首次:构建镜像(仅一次,或依赖/Dockerfile 变更时)
./scripts/build_env.sh
# 2) 改完 src/ 源码后:秒级增量编译(不重建镜像)
./scripts/compile.sh
# 3) 把 rosbag 放进 bags/ 后运行(容器内对应 /bags/)
./scripts/run.sh # 默认播放 bags/2022-08-30-20-33-52_0.bag
./scripts/run.sh other.bag # 播放 bags/other.bag
./scripts/run.sh other.bag -r 0.5 # 半速播放(额外参数透传给 rosbag play)run.sh 会自动:xhost +local:docker 授权 X11 → 拉起常驻容器 → 后台 roslaunch(算法节点 + RViz + 静态 TF,roslaunch 自动拉起 roscore,日志在容器内 /tmp/launch.log)→ 前台播放 rosbag(Ctrl-C 结束)。
rosbag 播放控制:空格暂停/继续,s 单帧步进,-r 0.5 半速。
docker compose exec ct-lio bash # 进容器手动操作
docker compose exec ct-lio pkill -f roslaunch # 停止后台算法节点/RViz
docker compose down # 停止并删除容器
docker compose exec ct-lio grep -c 'Low convincing' /tmp/launch.log # 统计退化帧ct-lio/
├── bags/ # rosbag 数据(只读挂载进容器,不打包进镜像)
├── config/
│ └── mapping.yaml # 算法参数(挂载进容器,改后无需编译)
├── docker/
│ ├── entrypoint.sh # 容器入口,source ROS 环境
│ └── patch_cmake.py # 构建时去除 packages.cmake 里的硬编码路径
├── docs/
│ └── 使用说明.md # 详细中文使用说明
├── launch/
│ ├── run_eskf.launch
│ └── rviz.rviz
├── scripts/
│ ├── build_env.sh # 构建镜像
│ ├── compile.sh # 增量编译
│ └── run.sh # 运行
├── src/ # C++ 源码(挂载进容器,改后用 compile.sh 编译)
├── Dockerfile
└── docker-compose.yml
配置文件挂载进容器,改后无需编译,重新 ./scripts/run.sh 即可生效。
preprocess:
lidar_type: 4 # 1=Livox AVIA, 2=Velodyne, 3=Ouster, 4=Robosense, 5=Pandar
point_filter_num: 1 # 每 N 个点取 1 个(预过滤)
blind: 0.01 # 剔除雷达原点附近 blind 米内的点
common:
imu_topic: /imu/data # IMU topic
lid_topic: /rslidar_points # LiDAR topic
mapping:
extrinsic_est_en: true # true=在线估计外参
extrinsic_T: [0.065, 0.0, -0.05] # LiDAR→IMU 平移(米)
extrinsic_R: [1,0,0, 0,-1,0, 0,0,-1] # LiDAR→IMU 旋转矩阵(行优先)
odometry:
surf_res: 0.2 # 降采样体素大小(米),越小点越密
size_voxel_map: 0.2 # 地图体素大小(米)
max_dist_to_plane_icp: 0.1 # 点到平面最大匹配距离(米)
min_number_neighbors: 20 # 邻域有效所需最少邻居数(稀疏雷达可调小,如 RS16 → 6)
max_num_residuals: 3000 # 每帧最多使用残差数
min_num_residuals: 300 # 残差少于此数仅告警(不影响求解)
max_num_iteration: 15 # ICP 外层最大迭代次数
motion_compensation: CONSTANT_VELOCITY # NONE / CONSTANT_VELOCITY / ITERATIVE / CONTINUOUSmotion_compensation:NONE 不补偿;CONSTANT_VELOCITY 匀速假设(不依赖 IMU);ITERATIVE 迭代补偿;CONTINUOUS 连续时间补偿(精度最高,依赖 IMU)。
-
查看 topic:
docker compose exec ct-lio rosbag info /bags/your.bag -
改
config/mapping.yaml的imu_topic、lid_topic、lidar_type。 -
按实际安装填
extrinsic_T、extrinsic_R。 -
时间戳模式(
src/apps/main_eskf.cpp第 82 行)——改完需./scripts/compile.sh:// 正常数据集(header 时间戳为帧起始) lio->pushData(cloud_out, std::make_pair(msg->header.stamp.toSec(), convert->getTimeSpan())); // 部分数据集(header 时间戳为帧结束,如 staircase) lio->pushData(cloud_out, std::make_pair(msg->header.stamp.toSec() - convert->getTimeSpan(), convert->getTimeSpan()));
-
切换解析求导/自动求导:
src/liw/lio/lidarodom.cpp的#define USE_ANALYTICAL_DERIVATE。
- 外参
extrinsic_R极其关键。 旋转矩阵错误会导致 IMU 轴向与 LiDAR 不一致、轨迹严重漂移,务必以标定结果为准。地图歪斜先排查重力对齐(见本仓库改动 1),再排查外参。 Failed to find match for field 'time'.表示 rosbag 中缺少每点时间戳,会影响前/后向传播,需确认雷达驱动输出逐点时间。- 退化告警
Low convincing result出现在几何特征不足处(长走廊、空旷区)。少量(<10%)正常;大量出现说明外参或参数需调整。 not enough keypoints selected in ct-icp只是告警:代码仍会照常求解,仅当残差数< min_num_residuals时打印。稀疏雷达(如 RS16)残差偏少时可调小min_number_neighbors(20 → 6 左右)以真正增加残差,或调低min_num_residuals。- NCLT 数据集 运行前需调整
src/preprocess/cloud_convert/cloud_convert.cc中的时间尺度static double tm_scale(默认1e9,NCLT 用1e6),改后需./scripts/compile.sh。 bags/与config/均为只读挂载,容器内不能写入;如需临时改 config,先cp到/tmp/再加载。- 挂载范围:compose 只挂
src/、config/、launch/,不挂cmake/(否则会覆盖镜像里已 patch 的packages.cmake)。改cmake/或Dockerfile需./scripts/build_env.sh。 - RViz 无法弹窗 时,确认宿主机已执行
xhost +local:docker、DISPLAY正确、NVIDIA Container Toolkit 已生效。
- 基于
osrf/ros:noetic-desktop-full。 - Ceres 2.1.0 源码编译:Ubuntu 20.04 的 apt 仅提供 Ceres 1.14,而本项目要求 Ceres 2,故在镜像内从源码编译。
- livox_ros_driver 与 ct-lio 放在同一 catkin workspace,先编译它生成消息头,再编译整个 workspace。
docker/patch_cmake.py:上游cmake/packages.cmake含两处硬编码开发者路径(Ceres、g2o),构建时用 Python 字符串替换去除(用 Python 而非 sed,避免 shell 展开 cmake 的${}变量)。- docker-compose.yml:
runtime: nvidia+ 挂载/tmp/.X11-unix实现 RViz 的 GPU 硬件加速渲染;network_mode: host;容器以sleep infinity常驻,便于exec进去增量编译与运行。
更详细的中文说明见 docs/使用说明.md。
| 数据集 | 说明 |
|---|---|
| Velodyne 32, NCLT | ![]() |
| Ouster-32, multi-layer office(左:ours,右:fast-lio2) | ![]() |
| Robosense RS16, staircase_crazy_rotation(左:PV-LIO,右:ours) | ![]() |
RS16 staircase 数据可从 Baidu Pan (password:4kpf) 或 Google Drive 下载。
cd ~/your_ros_ws/src
git clone <this-repo> ct-lio
cd ..
# 编译前确认依赖正确(可在 ./cmake/packages.cmake 调整 Ceres / g2o 路径)
catkin_make
source devel/setup.bash
roslaunch ct_lio run_eskf.launch
rosbag play your.bag依赖:Ubuntu ≥ 18.04、ROS ≥ Melodic、PCL ≥ 1.8、Eigen ≥ 3.3.4、Ceres 2、g2o。
时间分析:运行后 ./log/ 下会生成耗时日志,python3 ./scripts/all_time.py 可绘图。
- 上游:chengwei0427/ct-lio
- ct_icp:Continuous-Time LiDAR Odometry
- slam_in_autonomous_driving:SLAM in Autonomous Driving(ESKF 实现参考)
- semi_elastic_lio:Semi-Elastic LiDAR-Inertial Odometry
- hm-lio:hash-map based tight-coupled LIO



