很多刚接触ROS的朋友第一次拿到的雷达基本都是思岚A1,虽然现在市面上也有EAI、乐视、镭神这些选择,但A1确实凭借性价比和官方驱动完善程度成了新手入门的事实标准。这个雷达本身支持到12米的测距半径,采样率在5.5Hz到10Hz之间可调,测距精度在0.5米到8米范围内大概是实际距离的1%,对于室内建图和导航来说完全够用。这篇文章我就把从零开始用思岚A1在ROS里跑通SLAM建图的全过程拆开讲清楚,主要是给那些刚装好ROS、手里有一台A1、然后不知道该从哪里下手的读者。
简单说下我的测试环境:Ubuntu 20.04 + ROS Noetic,这是目前A1驱动支持最成熟的组合。如果你用的是Ubuntu 18.04加ROS Melodic,或者Ubuntu 22.04加ROS2 Humble,后面的步骤需要做少量调整,我会在涉及的地方单独说明。整个流程走下来,你会实现三个目标:第一,让A1在Rviz里正常显示雷达点云;第二,通过键盘遥控或手动推动小车完成一张室内栅格地图的构建;第三,把构建好的地图保存下来,为后续做自主导航做准备。
先给个底,整个流程并不复杂,但牵涉到环境配置、串口权限、驱动编译、SLAM算法选型、tf树检查、地图保存这几个环节,任何一个环节出问题都会导致建图失败或者地图质量很差。我见过的绝大多数新手卡住的地方其实就那么几个:串口权限没给导致雷达不转、缺少serial包导致编译失败、没有正确source工作空间导致找不到launch文件、棋盘格地图发布错位导致点云漂移。这篇文章会把每个环节的坑都提前标记出来。
1. 整体思路拆解:为什么是A1加上gmapping这个组合
1.1 建图方案选型背后的逻辑
SLAM建图说白了就是让机器人一边移动一边回答两个问题:我在哪里?周围长什么样?激光雷达负责提供"周围长什么样"的信息,而里程计(odom)负责提供"我怎么移动的"的估计,SLAM算法则把这两部分数据融合起来,最终输出一张占用栅格地图(occupancy grid map)。
ROS生态里最常用的2D建图算法有gmapping、hector_slam、cartographer、karto_slam这几个。A1作为单线激光雷达,最稳妥、最容易上手的就是gmapping,因为它对计算资源要求低,调参简单,依赖少,而且在室内小场景下精度足够。hector_slam不需要里程计,但要求雷达帧率很高,A1的10Hz上限只能说勉强,跑起来地图容易飘。cartographer是谷歌的方案,精度最高,但配置复杂,新手很容易被它的lua配置文件劝退。
所以这里选择的是gmapping加A1的组合,这也是很多入门教程采用的方案,原因是gmapping对里程计和雷达的外参标定要求不高,只要tf树正确发布,哪怕里程计精度一般也能构建出不错的地图。
1.2 你需要提前准备的东西
硬件方面,一台思岚A1激光雷达、一台运行Ubuntu的电脑(虚拟机也可以,但建议把虚拟机设置里USB兼容性调到USB 3.0,否则可能出现设备识别不稳定或者数据丢帧的情况)、一根自带的USB数据线。如果你想边建图边控制机器人的运动,还需要一个差速底盘或者至少一个能控制电机转动的开发板,能把里程计数据发布出来。没有底盘也没关系,可以手持雷达在房间里走动,但这样建出来的地图精度会差一些,因为你没有一个精确的里程计来估计你的移动。
软件方面,搭载ROS Noetic的Ubuntu 20.04是首选。ROS安装方式现在很多新手都在用鱼香ROS的一键安装脚本,这个工具确实省了不少事,一条命令装好ROS、自动配置环境变量,但装完之后建议手动验证一下环境是否完整,后面我会说怎么验证。装完ROS之后,还需要安装一些后续会用到的基础工具包。
sudo apt update sudo apt install ros-noetic-ros-tutorials ros-noetic-urdf-tutorial ros-noetic-gmapping ros-noetic-map-server ros-noetic-teleop-twist-keyboard这里提前装好gmapping和map_server,后面就不用手动下载源码编译了,直接用官方二进制包最省心。
2. 思岚A1驱动安装与串口权限配置
2.1 驱动源码编译的完整步骤
思岚官方在GitHub上维护了rplidar_ros这个驱动包,它支持A1、A2、A3等型号的雷达,原理是通过串口或USB转串口读取雷达的扫描数据,然后以sensor_msgs/LaserScan的消息格式发布到ROS话题里。编译驱动的第一步是创建工作空间并克隆代码。
mkdir -p ~/catkin_ws/src cd ~/catkin_ws/src git clone https://github.com/Slamtec/rplidar_ros.git cd ~/catkin_ws catkin_make source ~/catkin_ws/devel/setup.bash编译的时候有可能会报错说找不到serial包,这个是因为rplidar_ros中部分版本依赖serial库。解决办法很简单,安装对应的系统包:
sudo apt install ros-noetic-serial装完再执行一次catkin_make,正常情况下会提示构建成功。如果用的是ROS Melodic,把命令中的noetic替换成melodic就行。如果你用的是ROS2和Humble版本,思岚官方也有rplidar_ros2仓库,克隆下来用colcon build编译,但后面的建图流程建议还是用ROS1,因为A1的驱动和周边生态在ROS1下面最成熟。
2.2 串口权限:雷达不转的头号原因
A1通过USB转串口跟电脑通信,在Linux上对应的设备文件通常是/dev/ttyUSB0。默认情况下普通用户没有权限访问这个设备,所以即使雷达插上了、驱动也编译好了,启动节点时也会报错说can not open serial port。解决办法有两种。
第一种是一劳永逸地把当前用户加入dialout用户组:
sudo usermod -a -G dialout $USER执行完重新登录一次系统,让用户组生效。
第二种是直接临时修改设备权限:
sudo chmod 666 /dev/ttyUSB0这种方式重启后失效,适合临时测试。我第一次用A1的时候不知道要给权限,折腾了半天以为是雷达坏了,后来才意识到是权限问题。另外,如果你用的是虚拟机,还需要把USB设备从宿主机切换到虚拟机里,VMware或VirtualBox都有对应的USB设备直通设置,Windows宿主机上如果没做这一步,虚拟机里是看不到/dev/ttyUSB0的。
2.3 启动雷达并验证数据流
在启动驱动的launch文件之前,先插上雷达,然后手动查看设备是否被识别:
ls -l /dev/ttyUSB*如果有输出类似于/dev/ttyUSB0的内容,说明设备识别正常。接着启动rplidar驱动:
roslaunch rplidar_ros view_rplidar_a1.launch这个launch文件里包含了rplidarNode节点和rviz的可视化配置,启动后你应该能看到两个东西:终端里提示RPlidar running,然后弹出Rviz窗口,显示雷达扫描出来的点云。A1的扫描范围是360度,点云会绕成一个圆圈,周围物体的轮廓会清晰可见。如果Rviz里只有一个小红点或者什么都没有,大概率是帧ID配置有问题,这个在后面的常见问题部分会讲。
3. SLAM建图的核心原理和完整实操流程
3.1 gmapping在背后做了什么事
很多新手在用SLAM建图时,只知道运行一个launch文件,然后看着Rviz里地图从空白逐渐变满,但完全不知道这个过程中数据是怎么流转的。其实gmapping的核心思想可以理解为:拿雷达最新的扫描帧和已有地图做匹配,估算出机器人当前最可能的位置,然后把这个扫描帧插入到地图中,更新栅格概率。
这个过程依赖两个输入:一个20Hz到50Hz的激光雷达数据话题/scan,另一个是几Hz到几十Hz的里程计数据话题(通常是/odom)。gmapping订阅这两个话题后,构建出的地图会发布在/map话题上,同时它会维护并发布/tf变换,把odom坐标系和map坐标系关联起来。
为什么机器人必须动起来才能建图?因为gmapping是利用连续雷达帧之间的几何关系来推算位置变化的,如果机器人完全静止,雷达看到的环境完全一致,gmapping无法从静止的观测中恢复出位移信息。另一方面,如果机器人移动太快,前后两帧雷达数据之间的重叠区域太小,匹配算法就容易失败,导致地图出现错位或重影。
3.2 底盘端需要准备什么
要在真实机器人上建图,除了雷达驱动,还必须有一个能向ROS发布里程计的底盘控制节点。这里我不具体绑定某一个品牌的底盘,因为市面上的方案太多了,但你的底盘控制节点至少要做到这几件事:周期性地发布nav_msgs/Odometry消息到/odom话题;发布tf变换,把odom坐标系到base_link坐标准确发布出来;接收/cmd_vel话题上的geometry_msgs/Twist消息,并驱动电机让底盘按指令运动。
如果你暂时没有底盘,但有麦克纳姆轮或普通差速底盘开发板,网上有很多基于Arduino或STM32发布的ROS串口桥接包,原理都是通过串口或CAN总线接收速度指令、返回编码器数据,再在PC端把编码器数据换算出位置增量和速度,发布成里程计。哪怕里程计有较大的累积误差也没关系,因为gmapping会结合雷达观测来修正位姿,这正是SLAM比单纯的航位推演高明的地方。
3.3 建图launch文件逐行解析
我平时使用的建图launch文件很简单,但每一行都有讲究。下面是一个可以直接参考的完整文件:
<launch> <!-- 启动A1激光雷达 --> <node name="rplidar_node" pkg="rplidar_ros" type="rplidarNode" output="screen"> <param name="serial_port" value="/dev/ttyUSB0"/> <param name="serial_baudrate" value="115200"/> <param name="frame_id" value="laser"/> <param name="inverted" value="false"/> <param name="angle_compensate" value="true"/> </node> <!-- 启动gmapping建图算法 --> <node name="gmapping_node" pkg="gmapping" type="slam_gmapping" output="screen"> <param name="base_frame" value="base_link"/> <param name="odom_frame" value="odom"/> <param name="map_frame" value="map"/> <param name="map_update_interval" value="2.0"/> <param name="maxUrange" value="8.0"/> <param name="maxRange" value="12.0"/> <param name="particles" value="40"/> <param name="minimumScore" value="50"/> <param name="linearUpdate" value="0.05"/> <param name="angularUpdate" value="0.1"/> <param name="temporalUpdate" value="0.5"/> <param name="delta" value="0.05"/> </node> <!-- 启动键盘遥控节点 --> <node name="teleop_twist_keyboard" pkg="teleop_twist_keyboard" type="teleop_twist_keyboard.py" output="screen"/> </launch>这里的关键参数我逐个说明一下。base_frame要跟你底盘的base_link坐标系名称一致,如果底盘用的名称不是base_link,比如base_footprint,这里也要对应修改。maxUrange是激光数据被用于匹配的最远距离,设8米表示8米以外的点不参与匹配计算,这样可以减少远处噪声对定位的干扰。linearUpdate和angularUpdate分别表示机器人平移多少米或旋转多少弧度后触发一次新的扫描匹配,设得太小会导致计算频繁,设得太大则地图更新滞后。
map_update_interval是每隔多少秒发布一次完整的地图,我设的2秒表示地图每隔两秒在Rviz里刷新一次。实际建图过程中,你会看到地图不是连续生成一小段然后立刻变完整的,而是隔一段时间整体刷新一次,这是正常的。
3.4 开建了,边走边画的完整过程
确认雷达驱动正常后,把上面的launch文件保存为a1_gmapping.launch,放到一个功能包里(或者直接在任意路径下用roslaunch加载也行,路径写全即可),启动它:
roslaunch a1_gmapping.launch启动完成会出现三个界面状态:终端里雷达数据在持续输出;Rviz如果没有自动打开,手动运行rviz,然后添加一个Map显示并选择话题/map;键盘遥控终端会显示控制说明。
在Rviz中把Fixed Frame设置为map,然后添加LaserScan显示,话题选/scan,再把Map显示也加上,你就能同时看到雷达实时扫描线以及正在构建的地图。此时用键盘控制机器人慢慢移动,速度建议控制在0.2m/s左右,旋转速度控制在0.5rad/s以下。移动速度过快是新手建图最常见的问题,地图很容易出现错位和重影。
建图路径要不要规划?要,但不用走非常精确的路径,只要遵循"慢、匀速、闭环"三个原则。慢是指移动速度低,匀速是指不要走走停停或突然加速,闭环是指尽量把室内空间走一圈并回到起点附近,这样能显著改善地图的全局一致性。走完所有空间后,再让机器人原地旋转一圈到两圈,让雷达能扫描到四周更多的细节特征。
3.5 地图保存:别把辛辛苦苦建好的图丢了
建图完成后,需要把内存中的栅格地图保存为文件。这个功能由map_server包提供,命令如下:
rosrun map_server map_saver -f ~/maps/home_map-f后面跟的是保存路径和文件名前缀,执行完成后会生成两个文件:home_map.pgm和home_map.yaml。pgm文件就是图像格式的栅格地图,yaml文件则包含了地图的分辨率、原点坐标、占据阈值等元信息。这两个文件后续在自主导航中必须同时使用,amcl和move_base都会加载它们。
保存地图前要确保Rviz中的地图已经是最新的最终状态,因为map_saver保存的是/map话题上的最新数据。如果你刚停下车就又立刻保存,地图可能还没完成最后一次刷新,可以等一两秒再执行保存命令。
3.6 加载地图验证结果
保存完地图后,可以用map_server重新加载刚才保存的地图,验证文件是否能正常使用:
roslaunch map_server map_server.launch或者手动启动:
rosrun map_server map_server ~/maps/home_map.yaml然后启动Rviz,把Fixed Frame设为map,添加Map显示并选择话题/map,你应该能看到刚才保存的地图被加载回来了。这一步虽然简单,但能有效验证地图文件没有被损坏,也确认yaml和pgm的搭配正确。如果你看到的地图整体是黑乎乎一片,或者连轮廓都看不清,说明建图过程出了问题,需要回到前面的步骤重新检查。
4. 常见问题速查:我踩过的坑和排查实录
4.1 雷达能转但Rviz里没有点云
这个问题的表现是雷达电机在转、终端也打印了数据,但Rviz里看不到任何点云。排查顺序很重要,不要一上来就怀疑雷达坏了。第一步检查话题是否真的有数据输出:
rostopic echo /scan如果有数据刷屏,说明驱动和串口通信正常,问题出在Rviz显示配置上。大概率是Fixed Frame设置不对,默认的fixed frame是map,但如果你还没跑SLAM,map坐标系是不存在的,应该先把Fixed Frame改成laser或者base_link。第二步检查LaserScan显示的话题名称是否选对了,A1驱动默认是/scan,如果你之前启动过其他包,可能有多个scan话题。
4.2 编译rplidar_ros时报错找不到serial
这个问题在全新环境中很常见,因为rplidar_ros的某些代码版本在CMakeLists.txt中声明了对serial库的依赖,但你的系统还没有安装它。解决办法:
sudo apt install ros-noetic-serial cd ~/catkin_ws catkin_make如果你用的是源码安装的serial库,在CMakeLists中可能还需要加一句find_package(serial REQUIRED)并链接。建议优先用apt安装,省事。
4.3 建图时地图出现错位或重影
这是最让人头疼的建图问题,地图上同一个墙壁会出现双重轮廓,扫过的走廊越走越宽。原因通常是以下几种:雷达安装位置跟base_link不在同一轴线上,但没有通过静态变换发布外参;或雷达数据有延迟,驱动的时间戳不同步;或机器人移动速度过快导致gmapping帧间匹配失败;或雷达本身的测距数据有偏差。
先从最简单的排查开始:把机器人的移动速度降下来,线速度降到0.1m/s,角速度降到0.3rad/s以下再试一次。如果错位依旧,检查雷达坐标系的静态变换是否发布了,在Rviz中把Fixed Frame设为base_link,看LaserScan是否能正确显示在雷达应该所在的位置。
还有一种情况是A1在充电时使用电源适配器供电,部分电源适配器的纹波干扰会导致测距数据出现周期性的偏差,地图上表现为墙边出现锯齿状毛刺。建议用移动电源或电池给雷达供电测试排除。
4.4 tf树报错:frame_id不匹配
在跑建图launch文件的时候,终端偶尔会刷出"Could not get transform from..."或者"No transform between frames"的警告。这通常是tf树断裂了,也就是从map到base_link这一段变换链缺了一环。
要完整跑通gmapping,必须保证tf树中有这么一条链路:map -> odom -> base_link -> laser。gmapping会自己发布map到odom的变换;底盘驱动负责发布odom到base_link的变换;雷达驱动发布的是laser相对于base_link的变换,如果雷达不是直接安装在base_link的原点上,需要额外发布一个静态变换来声明两者的相对位置关系:
rosrun tf2_ros static_transform_publisher 0 0 0 0 0 0 base_link laser比如雷达安装在底盘中心正上方10厘米处,就把z参数改成0.1。很多新手只启动雷达和gmapping,忘掉了底盘端的tf,结果gmapping完全不工作,地图一直空白。
4.5 雷达数据整体偏移或方向不对
A1默认的扫描方向是逆时针,如果你在Rviz中看到点云的排列方向跟实际环境相反,可能是雷达固定方式导致扫描方向跟base_link坐标系的Y轴方向不一致。调整方法有两个:一个是在物理上调整雷达的安装方向,另一个是在驱动参数中设置inverted为true,或者在静态变换中调整yaw角度来纠正。
4.6 Rviz里点云显示出来了但很模糊
A1在测量透明玻璃、黑色物体、高反射表面(如镜面、瓷砖)时容易产生噪声,这是所有激光雷达的物理特性决定的,不是驱动问题。建图时尽量避开镜面反射强的区域,或者把这些区域在后续的地图处理中手动清理。另一个原因可能是串口波特率设置错误,A1默认波特率是115200,如果改成其他数值会导致数据解析异常。
5. 建图质量的三个底层因素和实操心得
5.1 雷达帧率、角分辨率与建图分辨率的关系
A1的角分辨率并不是固定的,它在不同转速下有不同表现,转速越快角分辨率越差。在建图这种精度优先的场景下,建议把雷达转速调到最低档,也就是采样率10Hz时的配置,这样单圈扫描的采样点数更多,点云更密集。驱动launch文件里angle_compensate参数设为true时,A1会自动对角度进行补偿校正,把扫描点分配得更均匀,这个参数建议保持打开。
gmapping的地图分辨率由delta参数控制,默认0.05表示每格5厘米。如果想获得更精细的地图,可以改成0.02,但地图会更吃内存和计算量。一般来说,家用环境5厘米分辨率够用了,走廊和房间边界已经很清晰。
5.2 环境复杂度决定了gmapping的上限
gmapping属于滤波类SLAM,它的核心假设是当前帧的位姿只跟上一帧的位姿有关,如果环境中有大量几何上重复的区域(比如很长的走廊、完全一样的仓库货架、空旷的厂房),gmapping会逐渐累积误差,最后地图可能出现扭曲或闭环不闭合。
遇到这种情况,可以尝试两条优化路径:一是建图时主动走特征明显的路径,比如沿墙走、在转角处稍微停顿让雷达多扫几帧;二是切换到cartographer,它在回环检测方面要强得多,但配置成本高一些。A1在室内家庭场景下用gmapping就够,在工厂车间或地下车库这种大面积场景,建议直接上cartographer。
5.3 从建图到导航的地图后处理技巧
gmapping保存的地图文件打开后会看到一些杂点和噪声,这些噪声如果直接喂给定位系统,可能导致之后自主导航时的误匹配。最简单的后处理方式是用图像编辑软件把地图上的孤立噪点手动擦除,把明显是动态物体的轮廓(比如建图时一不小心误入的人腿)处理掉。处理完成后重新保存为pgm文件,yaml文件中的分辨率和原点信息维持不变就行。
但要注意,不要大幅修改地图中墙体轮廓的宽度,因为yaml文件里的分辨率已经绑定了像素和物理距离的换算关系,改动墙体厚度会直接影响导航时的膨胀半径计算。
6. 进阶方向:从静态地图到自主导航
地图建好只是完成了第一步,后续的自主导航需要在静态地图的基础上加入定位模块和路径规划模块。定位通常用amcl,它会拿雷达实时扫描跟静态地图做匹配,实时估算机器人在map坐标系下的位置。路径规划用move_base,它负责在机器人当前位置和目标位置之间规划出一条安全的路径。
这个方向的坑比建图还要多,但从建图跨到导航是一个很自然的学习路径。我的建议是先把你建好的地图保存好,然后去了解amcl的粒子滤波原理、代价地图(costmap)的膨胀半径配置、以及move_base的全局规划和局部规划参数。等到导航跑通了,再考虑是否引入更多传感器(比如IMU)来做更高精度的建图,或者切换到ROS2和cartographer组合。
回到A1这个雷达本身,它的定位是入门级,在室内环境下应付绝大多数场景没有问题。但有个物理短板需要心里有数:A1的测距在某些暗色物体和细长物体(如桌腿)上表现一般,这些物体的反射信号弱或者太小,扫描点会稀疏。建图时遇到桌腿特别多的情况,地图上会出现一圈圆点而不是清晰的轮廓线,这是正常的,后期手动清理一下即可。
我在实际使用中的体会是,A1加gmapping这套组合虽然不算惊艳,但它是理解SLAM原理最好的入门路径。它足够简单,让新手能明确看到每一个模块是怎么协作的;它的错误信息也足够直接,排查起来不费劲。先把这条路走通,再去看cartographer或者LIO-SAM的代码,你会发现很多基础概念早就已经在实践中理解透了。最后再分享一个小技巧:每次开建之前,先让机器人原地旋转两圈再出发,能给gmapping一个比较好的初始匹配样本,地图初始阶段的稳定性会有明显提升。