手持激光雷达 TG30 基于 Cartographer 的建图实操记录
记录了使用 TG30 手持激光雷达在 ROS2 Humble 环境下配合 Cartographer 进行 SLAM 建图的完整流程。内容包括环境配置、系统架构分析、启动步骤、RViz 可视化设置、地图保存及核心参数调优。针对无里程计(odom)的手持模式,详细说明了 TF 坐标链构建、Cartographer 配置文件修改要点以及常见报错排查方法。实测验证了软件链路可行性,为后续底盘正式建图奠定基础。

记录了使用 TG30 手持激光雷达在 ROS2 Humble 环境下配合 Cartographer 进行 SLAM 建图的完整流程。内容包括环境配置、系统架构分析、启动步骤、RViz 可视化设置、地图保存及核心参数调优。针对无里程计(odom)的手持模式,详细说明了 TF 坐标链构建、Cartographer 配置文件修改要点以及常见报错排查方法。实测验证了软件链路可行性,为后续底盘正式建图奠定基础。

手持 TG30 激光雷达进行 Cartographer 建图测试,验证 SLAM 软件链路是否跑通。
与官方教程的区别:
| 项目 | 官方教程 | 我们的方案 |
|---|---|---|
| SLAM 算法 | Gmapping(粒子滤波) | Cartographer(图优化) |
| 里程计 | 有底盘 odom | 无 odom,纯激光匹配 |
| 雷达型号 | 4ROS(三角测距型) | TG30(TOF 飞行时间型) |
| 安装方式 | 编译源码 | apt 一键安装 |
| 启动方式 | ros2 launch slam_gmapping ... | ros2 launch yahboomcar_nav map_cartographer_test_launch.py |
官方教程使用 Gmapping,因 TG30 每圈 2020 点超过 Gmapping 的 1440 点上限,改用 Cartographer。
| 条件 | 状态 |
|---|---|
| ROS2 Humble 已安装 | 已确认 |
| YDLidar SDK 已安装 | 已确认(v1.0.6) |
| 雷达驱动已编译 | 已确认(ydlidar_ros2_driver) |
| 雷达串口已绑定 | 已确认(/dev/ydlidar -> ttyUSB0) |
| Cartographer 已安装 | sudo apt install -y ros-humble-cartographer ros-humble-cartographer-ros |
| Nav2 地图服务器已安装 | sudo apt install -y ros-humble-nav2-map-server |
| yahboomcar_nav 已编译 | 已确认(含自定义 launch 和配置) |
| LIDAR_TYPE 环境变量 | 已设置 export LIDAR_TYPE=4ros(写入 ~/.bashrc) |
TG30 雷达 → /scan(2020 点/圈,10Hz) ↓ Cartographer(纯激光匹配,无 odom) ↓ /map(栅格地图) → RViz 可视化
与有底盘的正式建图不同,手持模式下 Cartographer 配置了
use_odometry = false,完全靠前后两帧激光的扫描匹配来推算位移。
/ydlidar_ros2_driver_node ──→ /scan ──→ /cartographer_node ──→ /map ↓ /submap_list /scan_matched_points2 /trajectory_node_list /constraint_list /static_tf_footprint_to_base ──→ /tf_static(base_footprint → base_link) /static_tf_base_to_laser ──→ /tf_static(base_link → laser) /cartographer_node ──→ /tf(map → odom → base_link,由 Cartographer 自动生成) /cartographer_occupancy_grid_node ←── /submap_list ──→ /map
实际运行的节点列表:
| 节点 | 作用 |
|---|---|
/ydlidar_ros2_driver_node | 雷达驱动,发布 /scan |
/cartographer_node | SLAM 建图核心 |
/occupancy_grid_node | 将子图转换为栅格地图发布到 /map |
/static_tf_base_to_laser | 发布 base_link → laser 静态变换 |
/static_tf_footprint_to_base | 发布 base_footprint → base_link 静态变换 |
/rviz | 可视化 |
map → odom → base_link → laser ↑ ↑ ↑ ↑ └──────┘ │ │ Cartographer 静态 TF 静态 TF 自动生成 z=0.0815 x=0.0435 z=0.11 yaw=π
对比官方教程的 TF 树(
map → odom → laser → base_link),我们的坐标链更符合 REP-105 标准:base_link 是机器人本体,laser 作为传感器挂在 base_link 下面。
# 确保没有残留的雷达进程
pkill -f ydlidar 2>/dev/null
pkill -f cartographer 2>/dev/null
sleep 2
# 启动建图(一条命令启动所有节点)
source ~/ydlidar_ws/install/setup.bash
ros2 launch yahboomcar_nav map_cartographer_test_launch.py
启动成功的标志性日志:
[ydlidar_ros2_driver_node] LiDAR successfully connected [ydlidar_ros2_driver_node] [YDLIDAR]:Lidar running correctly ! The health status: good [ydlidar_ros2_driver_node] Model: TG30 [ydlidar_ros2_driver_node] [YDLIDAR] Fixed Size: 2020 [ydlidar_ros2_driver_node] [YDLIDAR INFO] Now YDLIDAR is scanning ...... [cartographer_node] All sensor data for trajectory 0 is available [cartographer_node] Inserted submap (0, 0).
常见问题:如果雷达报
cannot retrieve YDLidar health code,说明串口被之前的进程占用。先pkill -f ydlidar,等 2 秒再启动。
在另一个终端中:
rviz2
RViz 配置步骤:
map/map → 展开 → 选 Map → 点 OK/scan → 展开 → 选 LaserScan → 点 OK/scanFlat Squares0.05(官方用 0.1,但 TG30 点更密可以用更小值)雷达必须保持水平:
水平拿(正确): 激光扫描面 ─────────────── 扫到墙壁、家具轮廓 桌子 椅子 门 歪着拿(错误): 激光扫描面 ╲ ╲─────────── 扫到天花板/地板边缘 地图出现虚假障碍物
TG30 是 2D 激光雷达,只发射一个水平面的扫描线。倾斜会导致:
手持建图操作要点:
| 要点 | 说明 |
|---|---|
| 保持水平 | 小幅晃动(< 5°)可接受,持续倾斜不行 |
| 移动要慢 | 每秒不超过 0.3m,给 Cartographer 足够时间做帧间匹配 |
| 转弯要慢 | 角速度不能太快,否则相邻帧差异太大匹配失败 |
| 路径要有重叠 | 走过的区域要有重叠覆盖,方便特征匹配 |
| 走闭环 | 走一圈回到起点,触发回环检测,地图会自动校正 |
建议:把雷达放在平板(书、硬纸板)上端着走,更容易保持水平。
建图满意后,不要关闭建图终端,在新终端中执行:
source ~/ydlidar_ws/install/setup.bash
ros2 launch yahboomcar_nav save_map_launch.py
保存的文件:
~/ydlidar_ws/src/yahboomcar_nav/maps/ ├── yahboomcar.pgm # 栅格地图图片(灰度图) └── yahboomcar.yaml # 地图元数据
地图 YAML 文件格式:
image: yahboomcar.pgm mode: trinary resolution: 0.05 # 分辨率:0.05 米/像素 origin: [-5.95, -3.26, 0] # 地图左下角坐标 (x, y, yaw) negate: 0 occupied_thresh: 0.65 # 占用概率 > 0.65 → 障碍物(黑色) free_thresh: 0.25 # 占用概率 < 0.25 → 自由空间(白色)
灰色区域为未探索区域。
如果对当前地图不满意,想从零开始重建:
# 第一步:杀掉当前建图进程
pkill -f cartographer
pkill -f ydlidar
pkill -f occupancy_grid
# 第二步:等串口释放
sleep 2
# 第三步:重新启动(全新地图,不会叠加上一次数据)
source ~/ydlidar_ws/install/setup.bash
ros2 launch yahboomcar_nav map_cartographer_test_launch.py
RViz 不用关,重启后地图会自动清空。每次启动 Cartographer 都是全新建图。
# 方法 1:在启动建图的终端按 Ctrl+C
# 方法 2:命令行杀进程
pkill -f cartographer
pkill -f ydlidar
pkill -f occupancy_grid
文件:~/ydlidar_ws/src/yahboomcar_nav/params/tg30_2d_no_odom.lua
-- 关键参数
options = {
tracking_frame = "base_link", -- 无 odom,直接跟踪 base_link
published_frame = "base_link",
provide_odom_frame = true, -- Cartographer 自行生成 odom frame
use_odometry = false, -- 不使用里程计
}
-- TG30 雷达参数(手持调优版)
TRAJECTORY_BUILDER_2D.min_range = 0.1
TRAJECTORY_BUILDER_2D.max_range = 8.0 -- 缩小到 8m,减少远距离噪声干扰匹配
TRAJECTORY_BUILDER_2D.use_online_correlative_scan_matching = true -- 扫描匹配搜索窗口(关键!无 odom 时加大搜索范围防止匹配丢失)
TRAJECTORY_BUILDER_2D.real_time_correlative_scan_matcher.linear_search_window = 0.15
TRAJECTORY_BUILDER_2D.real_time_correlative_scan_matcher.angular_search_window = math.rad(30.)
TRAJECTORY_BUILDER_2D.real_time_correlative_scan_matcher.translation_delta_cost_weight = 1e-1
TRAJECTORY_BUILDER_2D.real_time_correlative_scan_matcher.rotation_delta_cost_weight = 1e-1
-- Ceres 精细匹配
TRAJECTORY_BUILDER_2D.ceres_scan_matcher.translation_weight = 10.
TRAJECTORY_BUILDER_2D.ceres_scan_matcher.rotation_weight = 40.
-- 运动滤波:更频繁地接受扫描帧
TRAJECTORY_BUILDER_2D.motion_filter.max_distance_meters = 0.1
TRAJECTORY_BUILDER_2D.motion_filter.max_angle_radians = math.rad(0.3)
TRAJECTORY_BUILDER_2D.motion_filter.max_time_seconds = 3.
-- 无 odom 模式下的特殊调整
TRAJECTORY_BUILDER_2D.submaps.num_range_data = 30 -- 更小的子图,减少单个子图内漂移
POSE_GRAPH.optimize_every_n_nodes = 15 -- 每 15 个节点就优化一次
POSE_GRAPH.constraint_builder.min_score = 0.55 -- 放宽匹配阈值
调优说明:首次测试使用
max_range=12、submaps.num_range_data=40时地图严重拉伸变形。 调优后将max_range缩小到 8m 减少噪声,加大扫描匹配搜索窗口(angular_search_window=30°)防止转弯丢失,缩小子图帧数到 30、全局优化频率提高到每 15 个节点,地图质量显著改善。
文件:~/ydlidar_ws/src/yahboomcar_nav/launch/map_cartographer_test_launch.py
启动内容:
| 序号 | 组件 | 说明 |
|---|---|---|
| 1 | 4ros_ydlidar_launch.py | 雷达驱动 |
| 2 | static_transform_publisher | base_link → laser(x=0.0435, z=0.11, yaw=π) |
| 3 | static_transform_publisher | base_footprint → base_link(z=0.0815) |
| 4 | cartographer_node | SLAM 建图(使用 tg30_2d_no_odom.lua) |
| 5 | cartographer_occupancy_grid_node | 栅格地图发布(分辨率 0.05m) |
报错 1:cannot retrieve YDLidar health code: ffffffff
原因:串口被之前的进程占用
解决:
pkill -f ydlidar
sleep 2 # 重新启动
报错 2:cannot bind to the specified serial port[/dev/ydlidar]
原因:上一轮进程未完全释放串口,或 /dev/ydlidar 设备消失
解决:
# 先确认设备是否存在
ls -la /dev/ydlidar # 如果设备不存在,物理重新插拔雷达 USB 线
# 插拔后等 udev 规则生效,再检查
ls -la /dev/ydlidar # 应显示:/dev/ydlidar -> ttyUSB0
[RTPS_TRANSPORT_SHM Error] Failed init_port fastrtps_port7429: open_and_lock_file failed
结论:无害警告,不影响任何功能。是 DDS 共享内存传输的非关键错误,可忽略。
[rviz2]: Stereo is NOT SUPPORTED
结论:无害提示,显卡不支持立体显示,不影响 2D 地图可视化。
报错:ros2 crashed with BrokenPipeError in run_executable()
原因:系统提示 ros-humble-ros2cli 等包版本过旧
解决(可选):
sudo apt update && sudo apt upgrade -y
此崩溃只影响
ros2 run tf2_ros tf2_echo等调试命令,不影响建图功能。
检查清单:
map?/map)?ros2 topic echo /map --onceros2 run tf2_ros tf2_echo map base_link原因:移动过快、雷达倾斜、或特征不足的空旷区域
解决:
# 查看节点通讯图(需要安装 rqt)
ros2 run rqt_graph rqt_graph
# 查看 TF 树(生成 frames.pdf)
ros2 run tf2_tools view_frames
| 局限 | 说明 |
|---|---|
| 精度较低 | 没有里程计,纯靠激光匹配,累积误差较大 |
| 对操作要求高 | 必须慢、稳、水平,否则地图质量差 |
| 不适合大场景 | 无 odom 的漂移在大空间中会更明显 |
| 仅供测试验证 | 正式地图需等底盘到了以后用有 odom 的模式重建 |
本次手持建图的目的:验证 Cartographer 软件链路跑通,熟悉建图操作流程,为底盘到后正式建图做准备。
底盘到后,切换到正式建图模式:
# 使用有 odom 的配置和 launch
ros2 launch yahboomcar_nav map_cartographer_tg30_launch.py
| 项目 | 手持模式(当前) | 正式模式(底盘到后) |
|---|---|---|
| 配置文件 | tg30_2d_no_odom.lua | tg30_2d.lua |
| Launch 文件 | map_cartographer_test_launch.py | map_cartographer_tg30_launch.py |
| 里程计 | 无 | 底盘编码器 + EKF |
| tracking_frame | base_link | base_footprint |
| use_odometry | false | true |
| provide_odom_frame | true | false |
| 建图精度 | 低 | 高 |
| 操作方式 | 手持慢走 | 遥控/自动巡逻 |
现象:启动 launch 时报 executable 'occupancy_grid_node' not found on the libexec directory
原因:ros-humble-cartographer-ros 中的可执行文件实际名为 cartographer_occupancy_grid_node,而非源码 launch 中写的 occupancy_grid_node
修复:在所有 launch 文件中将 executable='occupancy_grid_node' 改为 executable='cartographer_occupancy_grid_node'
已修复的文件:
map_cartographer_test_launch.pymap_cartographer_tg30_launch.pyoccupancy_grid_launch.py现象:手持走了一圈,地图呈窄长条形,与实际房间结构完全不符
原因:初始配置参数不适合无 odom 手持场景
max_range = 12.0 过大,远距离噪声干扰扫描匹配解决:调优 tg30_2d_no_odom.lua(详见第 5.1 节),核心改动:
max_range 从 12 缩到 8angular_search_window = 30°submaps.num_range_data 从 40 降到 30optimize_every_n_nodes 从 20 降到 15效果:调优后重新建图,地图质量显著改善,能清晰看到房间结构、墙壁和门口轮廓
现象:第二次启动时雷达报 cannot bind to the specified serial port 或 cannot retrieve YDLidar health code
原因:上一轮进程未完全退出,仍占用 /dev/ttyUSB0
解决:启动前必须先确保旧进程全部杀干净,等待 2 秒让串口释放
pkill -f ydlidar && pkill -f cartographer && sleep 2
极端情况下需要物理重新插拔 USB

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online
基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online
解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online