说到Jetson Nano上的激光雷达SLAM我踩过的坑绝对能写成一本小册子。这次要分享的是一套完整可复现的Cartographer建图环境搭建流程主角是镭神智能的N10单线激光雷达平台是Jetson Nano 4GB版。这套组合是很多低成本AGV、服务机器人、教学实验项目里的常见配置但网上资料比较零散尤其是Cartographer在ARM平台上的编译和配置经常让人卡在莫名其妙的地方。这篇文章会从系统刷机开始一路讲到地图保存把每个环节的原理、操作和注意事项都交代清楚适合已经入门ROS、想在Jetson Nano上跑通SLAM建图的开发者参考。1. 项目整体思路与硬件平台分析1.1 镭神N10激光雷达的核心参数与选型逻辑镭神智能N10是一款360度单线机械式激光雷达测距范围通常在0.15米到10米左右扫描频率10Hz角度分辨率约0.36度。这个数据参数意味着什么拿它跟日常场景对比一下10米的测距对于室内房间、走廊、小型仓库是完全够用的但别指望它在室外强光下或者大场景园区里还有好表现。单线雷达本质上只能给出一个平面上的障碍物距离信息所以它适合做室内移动机器人的建图、避障和定位不适合做三维感知。N10的通信方式一般是串口常见的是通过USB转串口模块连接到主控。镭神官方提供ROS驱动包节点名字通常叫lsn10或者类似命名发布的话题是/scan消息类型是sensor_msgs/LaserScan。这里有个容易踩的坑不同批次的N10可能默认波特率不一样我第一次就是没确认波特率在rviz里等了几分钟都没看到点云后来才发现是驱动参数里的波特率跟设备实际值不匹配。建议到手后先看说明书或直接问供应商确认波特率再填进launch文件。选N10还有一个现实考量它是这个价位段里市面上文档和驱动最齐全的单线雷达之一。对于学生项目、早期原型验证来说能不花时间在驱动适配上的雷达就是好雷达。当然如果你预算充足也可以看镭神更高端的型号但建图流程的核心逻辑是完全相通的。1.2 为什么选Jetson Nano而不是树莓派或X86工控机这个项目选Jetson Nano主要看中三点。第一是ARM平台带来的体积和功耗优势整板功耗在5W到10W之间带个18650电池组就能跑很适合做移动机器人原型。第二是Jetson Nano有CUDA GPU虽然跑Cartographer默认用不上GPU加速但后续如果要接YOLO、TensorRT做目标检测这块GPU就值回票价了。第三是社区资料丰富Jetson系列在机器人开发圈子里几乎成了标配遇到问题搜一搜基本能找到答案。相比之下树莓派4B的CPU性能弱一些编译Cartographer这种大型C项目会非常痛苦而且内存只有2GB或4GB跑ROS加Cartographer加可视化经常内存告急。X86工控机性能当然强但体积、功耗和价格都不占优势如果只是做室内小车原型用Jetson Nano性价比更高。不过也要泼盆冷水Jetson Nano的CPU毕竟是ARM架构的性能上限摆在那里。Cartographer在Nano上编译一次需要一两个小时建图过程中的CPU占用率也经常跑满。如果你的项目对实时性要求极高或者场景特别大建议直接上Jetson Orin Nano或者X86平台这套流程的思路完全通用只是编译和运行速度不同。1.3 Cartographer方案选型效果与成本的平衡为什么不用Gmapping或者Hector SLAM非要折腾Cartographer这是我在项目初期反复纠结过的问题。Gmapping在室内小场景里效果确实不错代码量小、配置简单跑起来很稳。但它依赖里程计输入如果你的小车底盘没有好的轮式里程计Gmapping很容易飘。Hector则完全不依赖里程计纯靠激光匹配但雷达频率低或者运动稍微快点地图就容易崩。Cartographer是Google开源的SLAM库它的核心优势是引入了submap和回环检测机制。激光数据到达后先被插入到当前submap后端还有一块独立的优化器在做回环闭合能有效纠正长时间建图累积的漂移。对N10这种10Hz的单线雷达来说Cartographer的rangefinder处理管线和配置项足够丰富可以通过参数调整来适配雷达特性和运动模型。我最终选Cartographer还有一个现实原因它不需要强制依赖里程计即使你推着小车手动走只要激光数据质量还行也能建出不错的图。这对没有底盘或者底盘没有编码器的朋友非常友好。当然有轮式里程计更好把odometry话题接进去地图质量会再上一个台阶。这篇实战主要讲无里程计的场景因为我相信很多人和我一样第一步只是想用雷达把房间地图跑出来。2. 基础环境搭建Jetson Nano系统与ROS安装2.1 刷机与系统初始化JetPack 4.6Jetson Nano的官方系统是通过JetPack镜像烧录的。我这里用的是JetPack 4.6.1版本对应Ubuntu 18.04ROS版本对应Melodic。选择这个组合的唯一理由是稳定JetPack 4.6.x是Jetson Nano生命周期里最成熟的版本驱动的坑最少。烧录过程不复杂到NVIDIA官网下载JetPack 4.6.1的SD卡镜像用Etcher或者dd命令写进一张至少64GB的TF卡。这里强烈建议用Class 10或以上的高速卡因为后面编译Cartographer时会有大量的磁盘读写低速卡会明显拖慢编译速度甚至导致某些进程超时。烧录完成后第一次开机按照提示完成系统设置。有一个设置项是NVIDIA官方建议的Maximize performance模式一定要开否则CPU被限制在低功耗档位后面编译起来会更煎熬。另外建议通过jtop工具查看CPU频率、温度、内存占用。Jetson Nano散热片是标配如果是在机箱里没做好风道编译过程中温度飙到80度以上会触发降频表现就是明明在编译风扇呼呼转但进度纹丝不动。2.2 ROS Melodic安装官方源与一键脚本怎么选ROS安装本身没什么神秘感。我个人的建议是能接受命令行就直接用官方源安装步骤固定出了问题网上答案多。命令也不复杂先设置sources.list和key然后apt update再装ros-melodic-desktop-full最后初始化rosdep并source环境变量。不过很多新手卡在了rosdep init这一步因为网络原因反复失败。这里我多说一句rosdep默认访问的源在国外路径不通畅时确实容易失败。如果确实过不去可以用rosdepc这种国内社区维护的工具替代功能基本等价。社区里鱼香ROS的一键安装脚本也是很多人验证过的方案本质上就是把这些步骤打包自动化了适合不想折腾系统环境的朋友。但我不建议在不理解每一步含义的情况下无脑跑脚本至少装完之后要能自己说清楚ROS装到了哪里、环境变量是在哪个文件里加的。装完ROS后有个细节很容易被忽略把source /opt/ros/melodic/setup.bash写进~/.bashrc之前先确认当前终端已经source过否则你打开新终端会发现ros命令不存在还以为自己安装失败了。这是新手最常见的问题没有之一。2.3 编译前必做的性能优化swap与供电Jetson Nano内存是4GB这在一开始就注定了编译大型项目会吃紧。我用的是4GB版实测编译Cartographer时内存峰值能到3.5GB以上稍微开点别的应用就可能触发OOM内存耗尽。解决办法是扩大swap分区。Jetson Nano刷完系统后默认带一个2GB左右的zram交换空间对编译来说不够。我的做法是创建一个8GB的swapfile放在TF卡上。具体操作是先sudo fallocate -l 8G /var/swapfile然后mkswap格式化、swapon启用再写进/etc/fstab让它开机自动挂载。注意swapfile所在的文件系统必须是ext4。TF卡本身的读写速度是瓶颈但作为兜底方案至少能避免编译进程被杀。另外一个节省内存的技巧是编译时不要开太多终端和图形界面。我习惯用tmux把编译会话挂起来然后只开一个终端看日志这样能省下不少内存。如果条件允许给Jetson Nano接上USB鼠标键盘和HDMI显示器做桌面环境但编译期间尽量别开浏览器Firefox在Jetson Nano上就是个内存怪兽。3. 镭神N10雷达驱动安装与数据验证3.1 硬件连接与串口权限处理N10的硬件连接比较简单雷达通过自带线缆接到USB转串口模块再插到Jetson Nano的USB口。上电后一般会出现/dev/ttyUSB0这个设备节点。先别急着跑驱动用ls -l /dev/ttyUSB*看权限如果当前用户不在dialout组里运行节点时打不开串口会报Permission denied。解决方法是sudo usermod -a -G dialout $USER然后重新登录生效。这一步是很多教程里一带而过、但实际最容易卡住的点我在线下带学生做实验时至少有三分之一的人卡在这个权限问题上。串口设备号也有讲究。如果你插了多个USB转串口设备设备节点可能在ttyUSB0和ttyUSB1之间跳变。更稳妥的做法是给N10写一个udev规则按设备的ID_VENDOR和ID_MODEL固定设备节点比如固定成/dev/ttyN10。这样就算拔插顺序变了也不用每次都去改launch文件。udev规则本身不复杂在/etc/udev/rules.d/下新建一个文件写上KERNEL等匹配条件然后执行sudo udevadm control --reload-rules加载即可。3.2 lsn10驱动编译与参数配置镭神N10的ROS驱动在GitHub上能找到包名一般是lsn10。下载后放到工作空间的src目录catkin_make编译这个过程很快因为只是一个串口读取和激光数据发布的小驱动没有重型依赖。驱动节点的主要参数有这么几个串口设备路径、波特率、雷达的frame_id、话题名。以我用的配置为例launch node namelsn10 pkglsn10 typelsn10_node outputscreen param nameserial_port value/dev/ttyN10 / param namebaudrate value230400 / param nameframe_id valuelaser / param namescan_topic valuescan / /node /launch这里frame_id很重要它决定了雷达在TF树里的坐标系名字后面Cartographer的lua配置和静态TF发布都要跟它保持一致。我用的是laser你也可以用laser_link或者radar_frame关键是全链路统一别在雷达配置里写laser在cartographer里又写lidar那样TF树会直接断掉。3.3 验证雷达数据先别急着上SLAM在跑Cartographer之前强烈建议先单独验证雷达数据。启动驱动节点后新开终端用rostopic list看看/scan话题是否存在再用rostopic echo /scan -n 1打印一条消息观察ranges数组里是否有合理的数据。之后打开rviz添加LaserScan显示话题选/scan固定坐标系选laser确认能看到完整的360度点云。这一步能帮你把问题定位在驱动层还是算法层。如果rviz里看不到点云请检查串口权限、波特率和雷达是否在转动如果能看到点云但形状不对比如圆形的房间变成了一团乱麻大概率是frame_id或者TF没配好。我特别想强调一个经验建图前一定要选一个雷达转速稳定、环境特征丰富的房间来测试。我第一次测试是在一个空旷的走廊里两侧墙面太平整激光打到白墙上反射也不理想Cartographer跑起来地图边缘全是毛刺。后来换到一个有桌椅、纸箱、墙角分明的房间效果立竿见影。这个准备动作的重要性不亚于任何参数调优。4. Cartographer源码编译依赖、避坑与Lua配置实战4.1 依赖库安装对源码编译的执念Cartographer最让人头疼的不是它本身而是它的依赖。官方推荐通过apt安装相关依赖然后在catkin工作空间里编译cartographer_ros。在Jetson Nano这种ARM平台上依赖能否顺利安装是关键。我遇到的第一个坑是abseil-cpp。Cartographer的ROS封装在Melodic下有专门的安装方式需要用wstool拉取特定版本不能直接从pip或者apt装最新版否则会因为API不兼容编译报错。按官方说明操作一般没问题但很多人卡在wstool merge和update时下载太慢这一步只能耐心等。第二个坑是protobuf版本冲突。系统自带的libprotobuf可能和Cartographer要求的版本不一致尤其是在你之前装过其他依赖protobuf的ROS包时。我的做法是严格按照cartographer_ros的debian控制文件里列出的版本安装不要贪图新的protobuf。第三个坑是ceres-solver。Cartographer需要ceres做非线性优化但Ubuntu 18.04自带的ceres版本太老必须从源码编译新版本。编译ceres本身很快但要注意它依赖的eigen版本如果你的系统里既有系统自带的eigen3又有源码装的eigen很可能会出现头文件混乱。我最后是卸载了系统自带的libeigen3-dev只保留源码编译的版本才彻底解决。4.2 编译Cartographer和cartographer_ros的完整流程整个编译过程在Nano上我大概花了接近两个小时。期间最担心的就是内存爆掉。为了避免OOM我用cpupower工具把CPU频率限制在一个比较低的范围虽然慢一点但CPU不至于长时间满负载温度也能控制住。具体编译步骤可以写成这样# 创建并初始化工作空间 mkdir -p ~/carto_ws/src cd ~/carto_ws/src # 拉取cartographer_ros仓库 git clone https://github.com/cartographer-project/cartographer_ros.git # 用wstool拉取同级的cartographer本体及依赖 wstool init . wstool merge cartographer_ros/cartographer_ros.rosinstall wstool update # 安装系统依赖环境 sudo apt-get install -y \ clang cmake g git google-mock \ libboost-all-dev libcairo2-dev libeigen3-dev \ libgflags-dev libgoogle-glog-dev liblua5.2-dev \ libprotobuf-dev libsuitesparse-dev libwebp-dev \ ninja-build protobuf-compiler python-sphinx # 单独编译abseil cd ~/carto_ws/src git clone https://github.com/abseil/abseil-cpp.git cd abseil-cpp mkdir build cd build cmake .. -DCMAKE_POSITION_INDEPENDENT_CODEON make -j2 sudo make install # 编译ceres-solver cd ~/carto_ws/src git clone https://github.com/ceres-solver/ceres-solver.git cd ceres-solver mkdir build cd build cmake .. -DEXAMPLESOFF -DTESTSOFF make -j2 sudo make install # 编译Cartographer本体 cd ~/carto_ws/src/cartographer mkdir build cd build cmake .. -G Ninja -DCMAKE_INSTALL_PREFIX/opt/cartographer ninja sudo ninja install # 回到工作空间编译cartographer_ros cd ~/carto_ws catkin_make这段流程我跑过不止一次每次都有新教训。比如abseil必须开启-fPIC编译否则后面链接cartographer时会报重定位错误ceres如果编译太慢可以把examples和tests关掉上面的命令里已经带上了。还有一个容易忽略的点Cartographer本体用ninja编译时如果中途报内存不足或者编译错误不要盲目从头再来。先看错误日志很多是缺头文件或者版本不匹配单独解决后继续ninja即可ninja会自动续接已完成的部分。4.3 Lua配置文件详解每个参数都是经验Cartographer的灵魂在lua配置。很多朋友编译通过后建图效果一塌糊涂问题几乎都出在这里。我用的配置文件基于官方2D示例修改只保留了与单线雷达相关的选项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, publish_frame_projected_to_2d false, use_odometry false, use_nav_sat false, use_landmarks 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, sub_map_publish_period_sec 0.3, pose_publish_period_sec 5e-3, trajectory_publish_period_sec 30e-3, rangefinder_sampling_ratio 1., odometry_sampling_ratio 1., fixed_frame_pose_sampling_ratio 1., imu_sampling_ratio 1., landmarks_sampling_ratio 1., } MAP_BUILDER.use_trajectory_builder_2d true MAP_BUILDER.num_background_threads 4 MAP_BUILDER.num_submaps_to_keep 3 TRAJECTORY_BUILDER_2D.min_range 0.3 TRAJECTORY_BUILDER_2D.max_range 8. TRAJECTORY_BUILDER_2D.missing_data_ray_length 1. TRAJECTORY_BUILDER_2D.use_imu_data false TRAJECTORY_BUILDER_2D.use_online_correlative_scan_matching true TRAJECTORY_BUILDER_2D.motion_filter.max_angle_rad math.rad(0.1) TRAJECTORY_BUILDER_2D.motion_filter.max_distance_m 0.15 TRAJECTORY_BUILDER_2D.ceres_scan_matcher.translation_weight 10. TRAJECTORY_BUILDER_2D.ceres_scan_matcher.rotation_weight 40.几个重点参数我单独说下经验。第一是max_range。N10标称10米但当你把max_range设成10时建图过程中经常会出现远处的杂点这些点来自玻璃、反光物体或者雷达本身的小概率噪声。我建议设成8米宁可用保守一点的有效距离也不要让噪声点污染地图。min_range设置成0.3米把雷达近场盲区的无效数据滤掉。第二是provide_odom_frame。因为我没有接里程计就是纯激光建图所以保持默认的true让Cartographer自己维护一个map到odom和odom到base_link的TF关系。如果你后面接了轮式里程计把use_odometry改成true同时关闭provide_odom_frame否则TF树会冲突。第三是TRAJECTORY_BUILDER_2D.use_online_correlative_scan_matching。这个选项翻译过来是使用在线相关性扫描匹配可以理解为先做一个粗略匹配找到激光帧的大致位姿再用ceres精细优化。单线雷达在对称场景里容易把方向配错打开这个选项能明显减少方向漂移。不过它比较吃CPU在Jetson Nano上实测打开后地图稳定很多建议保留。5. 建图实操从启动到保存地图5.1 启动前准备TF树和静态变换Cartographer建图依赖完整的TF树至少要有map、odom、base_link、laser这几个坐标系。前面的lua配置里tracking_frame设成了base_link所以雷达坐标系laser必须通过静态变换挂到base_link下。这里我用一个launch文件统一管理雷达驱动、静态TF和Cartographer。静态TF发布用tf2_ros的static_transform_publisher节点假设雷达安装在机器人中心正上方10厘米处yaw朝向正前方node pkgtf2_ros typestatic_transform_publisher namebase_link_to_laser args0 0 0.1 0 0 0 base_link laser /如果你的雷达安装位置不是正中心把三个平移值改成实际尺寸。单位是米角度单位是弧度如果是90度朝向最后一个参数写1.5707963。启动顺序上我习惯先启动雷达驱动和静态TF再用rosrun tf2_tools view_frames生成一张TF树图确认结构无误最后才启动Cartographer。这一步检查花的时间非常值因为TF树断了Cartographer会报找不到transform的错误而这类错误单看日志很容易让人误以为是代码问题。5.2 运行Cartographer建图速度与节奏控制一切准备好后启动Cartographerroslaunch cartographer_ros carto.launchlaunch文件里指定了lua配置文件的路径、参数和输入话题。我的carto.launch大致长这样launch node namecartographer_node pkgcartographer_ros typecartographer_node outputscreen param nameconfiguration_directory value$(find cartographer_n10)/config / param nameconfiguration_basename valuecartographer_n10.lua / remap fromscan toscan / /node node namecartographer_occupancy_grid_node pkgcartographer_ros typecartographer_occupancy_grid_node outputscreen param nameresolution value0.05 / param namepublished_frame valuemap / /node /launch启动后在rviz中添加Map显示话题选/map同时添加RobotModel和LaserScan就能看到实时建图过程。Cartographer默认会周期性发布submap地图是逐步累积的不像Gmapping那样有一个完整刷新的/map话题。这是正常的别以为是没跑起来。第一次建图时推车的速度一定要慢和稳。N10是10Hz的雷达如果推得太快相邻两帧激光之间的位姿变化太大前端匹配容易崩。经验上移动速度控制在0.2米/秒以内转角控制在每秒30度以内建出来的图质量最稳定。转到角落时先停下来让雷达多扫几帧再转向这个习惯能让回环检测更稳定。5.3 保存地图与清理收尾别等进程关了才后悔建图结束后保存地图是很多人容易忽略的一步。Cartographer不像Gmapping那样有个现成的调用方式需要先让cartographer_node保持运行把它生成的最后一帧地图取出来保存rosrun map_server map_saver -f ~/maps/n10_map保存时会生成两个文件n10_map.pgm是栅格地图图片n10_map.yaml是地图元数据文件。以后要做导航把这两个文件交给map_server加载就行。一个值得注意的细节保存地图前务必确认Cartographer相关节点还在运行并且rviz里的地图已经停止更新。我的习惯是在rviz里先放大看一遍地图边缘确认没有明显的错位和重影再执行map_saver。因为一旦进程关掉地图数据就不会再更新保存下来的就是你最后看到的样子。如果你在保存后发现地图有瑕疵只能重新推一遍没有后悔药。地图保存后还可以做一个很实用的小操作把pgm文件用图像软件打开把黑色的边界区域稍微清理一下只保留有效轮廓然后重新加载。这对后续做导航代价地图会友好很多纯黑边的外扩会让膨胀层在边界处产生多余的禁区。当然了如果你只是做建图演示这一步可以跳过。6. 常见问题排查与参数调优6.1 高频问题速查表我整理了一份这几个月收到的高频问题对照表每个问题都是我或身边朋友真实踩过的现象可能原因解决方法无法打开串口用户不在dialout组usermod加组后重新登录rviz看不到点云波特率不对或frame_id错误核对驱动参数查看/scan话题原始数据编译时进程被杀内存不足swap不够扩大swap文件到8GB以上cartographer报TF找不到TF树不完整用view_frames检查补静态TF地图整体偏移没有回环或者运动太快慢速重复走关键区域保证回环地图出现双重边缘雷达数据噪声大或max_range过大调整max_range打开相关性匹配地图保存失败或保存为空白/map话题没有数据或节点已关闭保持node运行先确认rviz能看到/map再保存这个表不追求覆盖所有情况但覆盖了从零搭建到建图完成这条主路径上90%的坑。遇到问题先看表再查日志一般都能解决。6.2 地图飘移与串线问题的调优思路地图飘移和串线是Cartographer新手最容易遇到的困惑。先说串线地图上出现大量交叉线条通常是雷达数据中的动态物体干扰比如有人从雷达旁边走过或者雷达扫到了自己小车上的某个反光部件。解决思路有两个一是从物理上排除雷达视角内的干扰物二是把TRAJECTORY_BUILDER_2D参数里与噪声相关的阈值调严格一些。再说飘移长时间建图后地图局部会出现错位这本质上是回环检测做得不够。Cartographer的回环检测依赖后端优化如果你在Jetson Nano上看到CPU占用接近100%但地图还是飘先检查是否打开了online correlative scan matching再检查submap的发布周期。还有一个小技巧建图过程中某段路径发现问题可以推回漂移起点重新走一遍让回环检测有机会把位姿优化回来。参数调优我遵循的原则是每次只改一个参数。Cartographer参数之间是强耦合的比如你把motion_filter的max_distance_m调大了会减少关键帧数量降低CPU压力但同时会牺牲匹配精度。我一般先用官方默认参数跑一遍记录基础效果然后针对具体问题改一个参数再跑一遍对比。别指望一次调优完美SLAM调参本身就是一个迭代过程。6.3 几条不写进文档里的个人建议最后分享几条这几个月实践下来的独家心得。第一记得给Jetson Nano配一个好的供电。很多人用USB供电或者劣质电源导致雷达转动时电流波动串口数据偶尔丢帧。丢帧在雷达驱动层面可能看不出来但Cartographer前端的匹配精度会悄悄劣化。我后来换了一个5V/4A的DC电源这个问题立刻少了很多。第二建立日志习惯。每次建图前用rosbag record /scan临时录一段数据如果建图效果不好可以离线回放调试参数不用反复推着车跑。rosbag回放的速度还可以用倍速控制调试效率高得多。这也意味着你可以随时复盘某次建图失败的原因。第三如果在Jetson Nano上实在跑不动可以考虑把Cartographer放在远程X86机器上跑Jetson Nano只负责采雷达数据并通过ROS网络转发。N10驱动发布/scan话题远程机器通过ROS网络订阅这样本地负担小地图效果反而会更好。ROS主从机配置不是这篇文章的重点但作为后期优化方向值得你花时间了解一下。