如果你手里正好有一台镭神智能N10激光雷达,又有一块Jetson Nano开发板,想跑通Cartographer建图却不知道从哪下手,那这篇就是为你准备的。文章会带你从头搭一套可运行的ROS建图环境:雷达驱动安装、串口配置、Cartographer配置与调整、地图保存,全部按实际操作流程记录。适合准备入门SLAM的学生、做服务机器人或AGV样机验证的工程师,以及想在低成本硬件上体验激光雷达建图的朋友。
先说下我为什么写这篇。上个月帮朋友验证一台室内巡检小车的SLAM方案,手头正好是这块N10和Nano。原本以为半天搞定,结果串口权限、TF树、时间戳、纯雷达退化场景一个个冒出来,折腾了差不多两天才稳定出图。这篇文章就是把那些坑重新填平,让你按步骤走就能少走弯路。
1. 项目整体设计思路与方案选型
1.1 这套组合要解决什么问题
移动机器人最核心的一件事,是先搞清楚“我在哪、周围是什么”,这本质上就是SLAM问题。激光雷达因为不受光照影响、点云直观、测距精度高,成为室内SLAM最常用的传感器之一。镭神智能N10是一款360度扫描的单线激光雷达,最大测距10米,对大多数室内场景足够用,价格也相对友好,很适合样机验证和小型服务机器人项目。
这次项目要解决的具体问题是:在一台Jetson Nano上,把N10雷达接入ROS,用Cartographer完成室内地图构建,并输出可复用的栅格地图。栅格地图是后续做自主导航、路径规划、避障的基础。地图本身画得好不好,直接影响导航能不能跑起来,所以建图环境搭建这一步必须扎实。
1.2 为什么选Cartographer而不是Gmapping
很多新手第一次接触SLAM,最先听说的是Gmapping。Gmapping基于粒子滤波,实现简单、起步快,但它比较依赖轮式里程计,纯雷达手持建图时很容易飘。Cartographer是Google开源的图优化SLAM框架,核心思路是匹配雷达帧、积累位姿图,并在后端做回环检测。当机器人走回之前到过的地方时,Cartographer能识别出“这里来过”,把整个位姿图拉回正确位置,地图明显更干净。
从实际效果看,我用同样一组N10数据分别跑过Gmapping和Cartographer,Gmapping在走廊和房间转角处经常出现错位,Cartographer几乎没有。下面这张表可以比较直观地看出差别:
| 对比维度 | Gmapping | Cartographer |
|---|---|---|
| 核心方法 | 粒子滤波 | 图优化 + 回环检测 |
| 对里程计依赖 | 高,缺里程计容易飘 | 低,纯激光也能跑 |
| 地图干净程度 | 一般 | 高 |
| 回环优化能力 | 弱 | 强 |
| 硬件消耗 | 较低 | 稍高,但单线雷达完全带得动 |
在Jetson Nano上跑Cartographer 2D建图,CPU占用并不夸张。N10只有10Hz扫描频率,单线点云数据量不大,Nano的4核ARM处理器完全能扛住。这也是我最终选Cartographer的核心原因。
1.3 为什么选Jetson Nano而不直接上普通PC
Jetson Nano的优势是低功耗、体积小、价格低,特别适合嵌入到小车底盘或者手持设备里做边缘端SLAM。对比一台桌面电脑加独立显卡,Nano整机功耗只有5W到10W,用充电宝就能带起来,这在移动机器人场景里非常重要。
当然Nano也有明显短板,4GB内存是最大瓶颈。编译大型包或者同时开多个ROS节点时,内存吃紧。解决办法也不复杂,先给系统加Swap交换空间,编译时限制并行任务数,后面我会贴具体命令。如果你用的是更新一代的Orin Nano或者Orin NX,内存和算力都会更好,配置思路完全一致,只是Ubuntu版本和ROS版本要跟着升级。
1.4 整体架构与数据流
整套系统的数据流可以这样理解:N10激光雷达通过串口把原始扫描数据发给Jetson Nano,lsn10驱动节点解析并发布成ROS标准话题/scan,Cartographer节点订阅/scan,实时估计雷达在空间中的位姿并构建栅格地图,最终输出/map话题和完整的TF坐标树。Rviz用来可视化点云和地图,map_saver用来把地图保存成图片文件。
这里我想特别强调TF坐标树。Cartographer对TF非常敏感,它需要map -> odom -> base_link -> laser这条完整的变换链。很多人建图失败,不是Cartographer参数问题,而是TF树断了或者设错了,这个点后面会反复提到。
2. 硬件与软件环境准备
2.1 硬件清单与N10关键参数
先把东西备齐,避免做到一半发现缺线、缺模块。我的推荐清单如下:
- 镭神智能N10激光雷达一台
- USB转串口模块(TTL电平,常见的是CP2102或CH340方案)
- Jetson Nano开发板,4GB版本
- 系统TF卡或SSD,建议64GB以上
- 5V/2A以上稳定电源,雷达尽量独立供电
- 杜邦线或专用连接线若干
- 可选:小车底盘、遥控器、手柄,用来移动雷达
N10的几个关键参数需要心里有数。最大测距10米,360度机械扫描,扫描频率10Hz,通过串口输出数据,ROS驱动里默认的frame_id通常是laser。N10的电机启动瞬间电流比正常工作时大不少,我踩过坑,直接用Nano的5V引脚给雷达供电,结果Nano重启了好几次。建议给雷达单独供电,共地即可。
2.2 系统与ROS版本怎么搭配
Jetson Nano最经典的组合是JetPack 4.6.1系统,对应Ubuntu 18.04,配ROS Melodic版本。这套组合资料最多、坑最少,强烈推荐新手照这个配。如果你用的是Jetson Orin Nano这类新板子,系统一般是Ubuntu 20.04或22.04,对应ROS Noetic或ROS2,流程相似但细节会有差异,N10这种老型号驱动对ROS2支持不完善,所以我建议ROS1起步。
安装系统这一步不展开细说,烧录镜像、开机、联网这些基本操作你大概率已经会了。重点是装完系统后,第一件事不是急着装ROS,而是先把软件源换成国内镜像,然后执行系统全量更新。这一步能避免后面大量依赖下载超时的问题。
2.3 鱼香ROS一键安装实操
ROS安装是很多新手的第一道坎,手动添加源、导入密钥、装几十个依赖包,每一步都可能出问题。我这里直接用“鱼香ROS一键安装”工具,它是我测试下来最省事的方案。在终端里执行:
wget http://fishros.com/install -O fishros && . fishros脚本会进入交互菜单,选择安装ROS,版本选Melodic,类型建议选桌面版,因为桌面版自带Rviz、map_server、tf工具等常用包,省得后面再补。安装时间取决于网络和系统状态,十几分钟到半小时都正常,耐心等就好。
安装完成后再执行:
echo "source /opt/ros/melodic/setup.bash" >> ~/.bashrc source ~/.bashrc验证一下是否成功:
rosversion -d如果输出melodic,说明ROS核心已经就位。注意每开一个新的终端,都要确保.bashrc里的source生效,这是初学者最容易忽略的操作。
2.4 给Jetson Nano加Swap,编译不再卡死
Nano只有4GB内存,开几个ROS节点加上桌面环境,内存就已经吃掉大半。尤其是后面编译lsn10驱动和Cartographer运行时,很容易出现内存耗尽导致进程被系统杀掉的惨案。所以建图环境搭建前,我强烈建议先加Swap。
在终端执行下面这组命令,创建一个4GB的Swap文件并设为开机自动挂载:
sudo fallocate -l 4G /var/swapfile sudo chmod 600 /var/swapfile sudo mkswap /var/swapfile sudo swapon /var/swapfile echo '/var/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab执行完可以用free -h查看Swap是否生效。如果这里显示Swap只有0,说明没开成功,回头检查一下步骤。加了4G Swap之后,编译Cartographer源码也不至于OOM了,效果非常明显。
3. 镭神智能N10驱动接入与点云验证
3.1 下载并编译lsn10驱动
驱动是雷达和ROS之间的翻译官。镭神官方提供了lsn10的ROS驱动包,安装方式很标准。先创建工作空间并拉取代码:
mkdir -p ~/catkin_ws/src cd ~/catkin_ws/src git clone https://github.com/LeiShenRobotics/lsn10.git如果仓库地址有变动或者网络不通,也可以去镭神智能官网的SDK下载页面拿到源码包,解压放到~/catkin_ws/src下,效果一样。然后回到工作空间根目录编译:
cd ~/catkin_ws catkin_make echo "source ~/catkin_ws/devel/setup.bash" >> ~/.bashrc source ~/.bashrc编译过程中如果提示缺少依赖,比如serial库,先安装:
sudo apt install ros-melodic-serial然后再重新catkin_make。驱动本身很小,编译只要一两分钟,Jetson Nano完全没压力。
3.2 串口权限与设备号处理
N10通过USB转串口模块连接到Nano后,系统里会生成一个/dev/ttyUSB0这样的设备节点。默认情况下普通用户没有读写权限,直接启动驱动会报Permission denied。临时解决方式是每次启动前执行:
sudo chmod 666 /dev/ttyUSB0但这样太麻烦,而且一旦重新插拔USB,权限又会丢失。我建议直接写一条udev规则,永久解决:
echo 'KERNEL=="ttyUSB*", MODE="0666"' | sudo tee /etc/udev/rules.d/60-usb-serial.rules sudo udevadm control --reload-rules另外要提醒的是,设备号不一定永远是ttyUSB0。如果你同时插了其他USB串口设备,N10可能变成ttyUSB1。启动驱动前先执行ls -l /dev/ttyUSB*确认一下,在launch文件里把port参数改成实际值。
3.3 启动驱动,验证/scan话题
准备工作都做完后,进入第一个高潮时刻。启动驱动节点:
roslaunch lsn10 lsn10.launch如果一切正常,终端会持续输出雷达扫描数据解析日志。新开一个终端,用下面的命令确认话题和频率:
rostopic list rostopic hz /scan rostopic echo /scan -n1rostopic hz /scan应该稳定在10Hz左右,这是N10的标称扫描频率。如果频率忽高忽低甚至为0,优先检查串口波特率和设备号。有的驱动版本默认波特率和雷达固件不匹配,需要在launch文件里把baud_rate改成对应值,常见的是230400或460800,逐个试一下就能确认。
3.4 在Rviz里亲眼看雷达点云
数据在终端里只能看到数字,不直观。启动Rviz:
rosrun rviz rviz添加一个LaserScan显示,把话题选择为/scan,固定坐标系设置为laser或base_link。这时候你应当看到一圈由点云组成的环境轮廓,雷达正对的方向有点云密集区域,远处墙壁形成一条清晰的弧线或直线。
如果看到点云围绕中心旋转,别慌,这只是Fixed Frame没设对。点云本身没问题,把坐标系改成laser就好。我习惯在这一步多花几分钟转一转雷达方向、遮挡一下雷达视场角,确认点云响应正确后,再进Cartographer。雷达数据不干净,后面建图全是坑,这一步省不得。
4. Cartographer建图环境搭建与实操
4.1 安装方式:二进制优先,源码按需
Cartographer在ROS1下有现成的二进制包,Jetson Nano的Ubuntu 18.04可以直接安装:
sudo apt install ros-melodic-cartographer-ros为什么我不建议一上来就源码编译?一是Cartographer依赖很多旧版本库,在ARM架构上编译容易踩坑;二是编译时间很长,Nano编译可能要三四十分钟甚至更久,期间稍有环境变动就前功尽弃。二进制版本对N10这种常规单线雷达完全够用,功能没有缩水,官方更新也不算激进。
安装完成后确认一下:
rospack find cartographer_ros能输出功能包路径就说明装好了。只有当你需要修改Cartographer内部的算法逻辑或者使用最新代码时,再去源码编译。对大多数建图需求来说,apt安装是性价比最高的选择。
4.2 创建N10专属功能包:launch和lua配置
Cartographer在ROS里的启动方式是通过一个launch文件加载一个lua配置文件。我先创建一个专属功能包,避免把配置堆在其他包里面:
cd ~/catkin_ws/src catkin_create_pkg cartographer_n10 roscpp rospy mkdir -p cartographer_n10/launch cartographer_n10/config然后在launch目录创建n10_cartographer.launch:
<launch> <node name="cartographer_node" pkg="cartographer_ros" type="cartographer_node" output="screen"> <param name="configuration_directory" value="$(find cartographer_n10)/config" /> <param name="configuration_basename" value="n10_cartographer.lua" /> </node> <node name="cartographer_occupancy_grid_node" pkg="cartographer_ros" type="cartographer_occupancy_grid_node" output="screen"> <param name="resolution" value="0.05" /> </node> <node pkg="tf2_ros" type="static_transform_publisher" name="base_link_to_laser" args="0 0 0.1 0 0 0 base_link laser" /> <node name="rviz" pkg="rviz" type="rviz" output="screen" /> </launch>接着在config目录创建n10_cartographer.lua:
include "map_builder.lua" include "trajectory_builder.lua" options = { map_builder = MAP_BUILDER, trajectory_builder = TRAJECTORY_BUILDER, map_frame = "map", tracking_frame = "base_link", published_frame = "base_link", odom_frame = "odom", provide_odom_frame = true, use_odometry = false, num_laser_scans = 1, num_multi_echo_laser_scans = 0, num_subdivisions_per_laser_scan = 1, num_point_clouds = 0, lookup_transform_timeout_sec = 0.2, submap_publish_period_sec = 0.3, pose_publish_period_sec = 5e-3, } TRAJECTORY_BUILDER_2D.use_online_correlative_scan_matching = true TRAJECTORY_BUILDER_2D.min_range = 0.15 TRAJECTORY_BUILDER_2D.max_range = 10.0 TRAJECTORY_BUILDER_2D.missing_data_ray_length = 5.0 MAP_BUILDER.use_trajectory_builder_2d = true保存后回到工作空间编译一次:
cd ~/catkin_ws && catkin_make4.3 关键配置参数逐个拆解
很多人在这个环节直接复制网上配置,出了问题不知道怎么改。我挑几个重点参数说说它的作用。
tracking_frame和published_frame都设成base_link,意思是Cartographer把base_link当作机器人的主体坐标系。雷达到base_link之间的固定变换,由launch里的static_transform_publisher发布。这里的高度我设了0.1米,实际值要按你雷达的安装高度填,错了会导致地图整体偏移。
provide_odom_frame = true是个重要开关。因为我们没有轮式里程计,纯雷达建图,这个选项让Cartographer自己维护一条odom -> base_link的变换。如果设为false,系统会去订阅外部里程计话题,没有则直接报错。
use_online_correlative_scan_matching = true,这项就是常说的CSM在线相关性扫描匹配。它对雷达在对称场景、弱几何环境下更容易兜住位姿,防止跑飞。代价是计算量稍大,但单线雷达在Nano上跑没压力。
min_range和max_range要匹配N10的实际量程。N10最近测距大概0.15米,最远10米,所以这里设置的0.15和10.0刚好对应。如果max_range设得太小,远距离点云会被截断,影响建图范围;设得太大,又容易把噪声点纳进来。
4.4 启动建图,移动雷达,保存地图
确认雷达驱动还在运行,然后另开终端启动Cartographer:
roslaunch cartographer_n10 n10_cartographer.launch如果一切正常,Rviz会自动打开,并看到一张灰黑色的网格地图随着雷达移动逐渐扩展开来。此时手持雷达或者遥控小车在室内慢慢走一圈,注意移动速度要均匀、不要太快,转角处尤其要慢,走到之前经过的区域时尽量原路重复一遍,这样回环检测有机会把累积误差拉平。
地图构建到满意程度后,开一个新终端保存地图:
mkdir -p ~/maps rosrun map_server map_saver -f ~/maps/room1保存完成后会生成room1.pgm和room1.yaml两个文件。pgm是灰度地图图片,yaml记录了地图分辨率、原点坐标等元信息。用图像查看器打开pgm,能看到黑色表示障碍物、白色表示空闲区域、灰色表示未知区域。到此,整套Cartographer建图环境就算正式跑通了。
5. 常见问题排查与调优实录
5.1 雷达无数据,点云纹丝不动
这个问题占了新手求助的一半以上。症状是rostopic list里看不到/scan,或者有/scan但rostopic hz输出为0。按下面顺序排查:
第一步,确认USB转串口是否被识别,lsusb能看到设备,ls -l /dev/ttyUSB*能看到节点。第二步,确认权限,ls -l /dev/ttyUSB0如果是crw-rw----,那普通用户没权限,按前面udev规则处理。第三步,确认波特率,把launch文件里的baud_rate换几个常见值试试。第四步,确认雷达供电,雷达指示灯正常亮不代表电压足够,启动瞬间电流不够会导致驱动反复初始化。
如果用的是网口版本的雷达,还要检查网卡IP是否与雷达在同一网段,这是网络类雷达常见的坑。
5.2 建图漂移、重影,怎么调都压不住
地图出现重影、走不了直线,是Cartographer调参中最让我头疼的问题。先说结论:绝大多数重影不是Cartographer参数不够好,而是雷达数据源本身有抖动或者TF不稳定。
我的处理顺序是这样的。先固定雷达,确保安装支架不晃动,机械抖动会造成点云畸变。再看rostopic hz /scan,如果频率不稳定,回查串口和供电。最后才动手改Cartographer参数。
如果数据源没问题,优先调整这几项:把TRAJECTORY_BUILDER_2D.min_score从默认0.1调到0.3左右,提高帧匹配的置信度;把TRAJECTORY_BUILDER_2D.loop_closure_min_score适当调高,让回环检测更保守;如果激光雷达点云比较稀,可以把submaps_num_range_data从默认值调小一点。
还有一类漂浮发生在长走廊等对称场景。雷达看到的左右墙壁几乎一样,位姿估计在走廊方向容易退化,地图走着走着就偏了。这种情况下最有效的方案是接上轮式里程计,哪怕是很粗糙的编码器数据,也能给Cartographer一个初始猜测。纯雷达方案在退化场景里,物理极限就在那里,靠参数硬拉效果有限。
5.3 Jetson Nano编译卡顿、进程被杀死
Nano编译时内存爆掉,通常表现为终端里冒出Killed或者internal compiler error。解决办法就是加Swap,加完之后用free -h确认。编译时控制并行任务数,不要用默认的全部核心:
cd ~/catwin_ws # 注意这里是catkin_ws catkin_make -j2-j2表示同时只编译两个任务,能明显降低峰值内存。如果你用的是源码编译Cartographer,同样在cmake或catkin_make命令后面加-j2。跑建图时,建议把浏览器、IDE这些吃内存的软件全关掉,把宝贵的内存留给SLAM进程。用htop实时看内存占用,如果一直贴着上限,考虑把地图分辨率调低一点。
5.4 TF树不完整,Cartographer反复报错
Cartographer常见的报错是Could not compute submap pose或者Frame [laser] does not exist。这个基本就是TF树断裂。在Rviz里可以打开TF面板看完整坐标树,正常结构是map->odom->base_link->laser。
缺少哪一环就补哪一环。如果报的是base_link到laser缺失,说明launch里的static_transform_publisher没生效,检查节点是否启动、参数对不对。如果odom到base_link缺失,说明provide_odom_frame没设对,看lua文件里有没有这行。
还有一类问题跟时间戳有关。Cartographer要求激光帧的时间戳是严格递增的,不能倒退。如果你rostopic echo /scan -n3看到时间戳跳跃或者倒退,说明串口丢包或者驱动的时基不稳定。可以先重启雷达,再检查USB转串口模块质量,便宜的CH340在高速率下偶尔会丢字节,换CP2102通常能解决。
5.5 常见问题速查表
最后整理一张表格,遇到问题直接对着查。
| 问题现象 | 可能原因 | 解决办法 |
|---|---|---|
| /scan话题不存在 | 驱动未启动或串口未识别 | 检查驱动节点、lsusb、设备路径 |
| /scan频率为0 | 串口权限、波特率不匹配 | chmod 666、调整baud_rate |
| 点云在Rviz中旋转 | Fixed Frame设置错误 | 将Fixed Frame设为laser或base_link |
| 地图整体偏移 | 静态TF高度或方向错误 | 检查base_link到laser的static_transform_publisher |
| 地图重影、漂移 | 雷达抖动、运动过快、退化场景 | 固定雷达、放慢速度、调min_score、加里程计 |
| 回环检测不生效 | loop_closure_min_score过高 | 适当调低该参数,多走重复路径 |
| 编译时进程被Killed | 内存不足 | 加Swap、catkin_make -j2 |
| 地图保存失败 | map_server未安装 | apt install ros-melodic-map-server |
我在实际跑建图时还有一个习惯,就是边走边盯着Rviz里的地图边缘。如果新扫出来的点云和旧地图墙体重合得很整齐,说明当前状态健康。一旦发现明显分叉,立刻停下,回到分叉点重新走一遍,让回环去纠正。用这个方法,我基本能在两三次尝试内拿到一张比较干净的地图。
另外一个容易被忽略的细节是,保存地图前最好让雷达静止几秒钟,让Cartographer把当前位姿收敛稳定,这样最后几帧不会在已保存的地图里留下尾巴。保存完地图后,打开pgm文件看一眼边缘有没有黑点,如果太多,回头检查雷达安装高度和移动速度是否合适。
如果你后续打算接轮式里程计,只需要把use_odometry改为true,并确保/odom话题发布的是nav_msgs/Odometry消息。想转ROS2的话,思路是类似的,但需要先确认N10驱动和Cartographer在ROS2环境下有对应版本,不要贸然迁移,先在ROS1把SLAM流程吃透,再考虑跨版本平移。这套环境搭好之后,你可以继续研究定位、导航、路径规划,建出来的地图就是所有上层功能的地基。