上个月有个做机器人底盘的朋友跟我聊起一个很现实的问题:手头有一台robosense16 多线雷达,想用它来做室内建图,但手边没有轮式里程计,也没有IMU,问我能不能直接用hector建图。我的回答是“能”。但你要理解,这个“能”是有前提的——直接把16线点云丢给hector_slam,大概率只能得到一堆乱线和满屏报错。把robosense16 多线雷达与hector建图结合,真正考验的是你对点云的理解、话题转换的处理以及hector那套匹配参数的手感。
这篇笔记是我花了一个多星期在这套组合上反复试验后的整理,适合三类人看:一是手上正好有16线雷达但暂时没有单线雷达的开发者,二是想把低成本多线雷达用在小车、小机器人平台上的玩家,三是已经跑通过hector但总觉得地图质量不理想、想找调参方向的朋友。我会从方案选型、点云转2D扫描、tf配置、参数调优一路写到排坑实录,尽量把“为什么这么做”也讲清楚。
1. 先拆解项目核心:为什么这个组合会成立
1.1 看似不匹配,其实各取所需
先说结论:hector_slam是一个只依赖激光扫描、不需要里程计的2D建图算法。它通过高斯牛顿法对连续两帧扫描进行匹配,不断优化机器人在二维平面内的位姿。它的核心输入是一个标准的LaserScan消息,输出是栅格地图和odom到map的坐标变换。要让它跑起来,你只需要有一个激光扫描话题和一个基本的tf树。
而robosense16 多线雷达提供的是3D点云,包含16条激光线的数据,每根线扫描一圈能拿到大量距离点。从“多线”到“单线扫描”,需要你自己想办法提取或者融合出适合hector使用的二维扫描帧。这听起来多了一步操作,但也带来一个单线雷达没有的优势:16根线提供了不同俯仰角度的测量信息,哪怕机器人有颠簸、上下坡,你也能从中挑选出接近水平的那根线来建图,不容易因为车体倾斜而直接扫空——这是单线雷达做不到的。
1.2 这套组合适合什么场景
经过实际测试,我觉得最合适的应用场景有三类:
- 无里程计的小型移动平台。比如实验室的四轮小车、带麦克纳姆轮的底盘,没有装编码器或编码器精度太差,gmapping这类依赖里程计的方案就无从下手,而hector恰好不挑这个。
- 快速搭建的测图任务。不需要花时间标定轮径、不需要等轮子打滑数据校准,只要雷达驱动能出点云、电脑能跑rviz,Heictor就能在十几分钟内出图。
- 学习SLAM原理的场景。hector的匹配过程直观、代码结构简单,配合16线雷达处理后的扫描数据,能清楚看到一帧帧匹配、地图逐渐展开的过程,比直接上cartographer更容易理解。
1.3 这个组合的边界在哪里
必须说实话,hector建图有自己的短板,尤其对扫描数据的质量和频率比较敏感。它没有闭环检测,也没有地图回环修正,如果在小范围转悠还好,一旦在走廊、长通道这类自相似性很强的环境里跑,会逐渐累积漂移,最后地图变形。
另外,16线雷达默认点云帧率通常在10Hz左右,而hector_slam在低帧率下匹配质量会下降,因为相邻两帧之间机器人位姿变化太大,扫描匹配容易跳到局部极值。实测中需要把雷达的点云话题转成LaserScan后发布,并且尽量保持话题频率在8Hz以上。如果你把雷达降频到3Hz、4Hz,hector基本是救不回来的。
所以这套组合真正适合的是“快速、轻量、无里程计”的建图需求。如果你想要长时间、大范围的稳定建图效果,应当考虑cartographer或者loam系方案,后面我会给出对比理由。
2. 跑通前的准备:方案对比与工具链选型
2.1 为什么是hector而不是gmapping或者cartographer
面对16线雷达,很多人第一反应是直接用cartographer或者aloam。我的选型过程是这样的:
先说gmapping。它是很多2D建图的首选,质量稳定,但它严重依赖里程计输入。我记得很清楚,第一次把这套雷达接到一个没有编码器的开发板上之后,gmapping的粒子滤波根本没办法初始化,地图一直空白。gmapping需要里程计来给出位姿预测,再用激光修正,没了轮式信息等于让人蒙着眼走路。
然后是cartographer。这个方案很强大,能融合IMU、里程计,还有回环检测,地图精度确实高。但配置复杂度也高,尤其是激光雷达坐标系与基座坐标系的标定、IMU的噪声参数、子图更新频率等,调起来需要不少功底。对于“只想先出一张能看的图”的快速验证阶段来说,有点杀鸡用牛刀了。
所以最终选择hector的理由非常具体:它不需要里程计,不需要IMU,只需要一个够好的2D扫描流。而16线雷达经过提取之后给出的扫描帧,分辨率、测距精度都足够满足它的匹配要求。这就像你有一个高清摄像头,但当前只需要一个单目输出的逻辑——数据源更富余,反而给了你更多选择空间。
2.2 robosense16雷达的关键参数
项目用到的robosense16,它的具体参数不同版本略有差异,我这里按常见的RS-LiDAR-16来说明:
- 线数:16线,垂直视场角约-15°到+15°;
- 测距能力:一般标称最远120m,实测在室内反向反射物上能稳定测到30m以上;
- 水平视场角:360°;
- 扫描频率:可配置,常见的有5Hz、10Hz、20Hz;
- 点频:约每秒30万点以上,16线的数据量在点云里算适中。
关键点在于,这16根线里面,总有几根线的俯仰角接近0°,也就是基本水平。这些线扫描出来的点,就是接近2D平面数据的存在。我们要做的,就是从点云里把这些点挑出来,组成LaserScan。这一点我会在第3章详细展开。
2.3 环境准备与工具选型
在开始之前,请先确认:
- ROS版本:我使用的是ROS Noetic,Ubuntu 20.04。Melodic也能跑,但编译依赖略有差异,建议以Noetic为准。
- 雷达驱动包:robosense官方驱动或社区维护的驱动包都可以,只要能发布
sensor_msgs/PointCloud2就行。如果你使用的是非官方驱动,务必注意点云的时间戳与坐标系是否正常。 - 工具包:
pointcloud_to_laserscan是这一整套方案里非常好用的转换工具,但需要自行编译或apt安装;另外hector_slam可以直接用apt安装:sudo apt install ros-noetic-hector-slam。 - 可视化与调试工具:
rviz用于观察点云与地图,rqt_reconfigure用于在线调节hector参数,rqt_tf_tree用于排查tf问题。
硬性条件下,建议电脑至少i5处理器、8GB内存,因为robosense16满速点云在rviz里渲染时会占用不小CPU,而hector还要做实时匹配。如果电脑配置太低,适当降低点云帧率而不是降低点云分辨率是更合理的做法,因为降分辨率会让扫描的角分辨率变差,hector匹配精度会下降。
3. 点云预处理:从多线激光到2D扫描帧
3.1 为什么不直接把PointCloud2喂给hector
这是一个很多新手容易忽视的核心问题。hector_slam的输入是LaserScan,不是PointCloud2。LaserScan是单线雷达的抽象数据结构——它描述的是“某一平面范围内等角度间隔的距离测量”,其核心是angle_min、angle_max、angle_increment和ranges数组。
如果你强行把3D点云发到一个LaserScan话题上,你会收到类似“expected laser scan message”之类的类型错误,更不用说hector内部做扫描匹配时读取的ranges数组长度和角度信息都是围绕二维扫描设计的。所以第一步必须是点云到2D扫描的转换。
3.2 三种转换方案以及我为什么这样选
我试过三种方式把robosense16转成LaserScan,各自优缺点很鲜明。
方案一:直接使用pointcloud_to_laserscan工具包。
这个方式最省事。它是一个现成的ROS节点,订阅PointCloud2,然后根据你配置的min_height、max_height将点云裁剪到一个高度带内,再投影到激光雷达所在平面的二维极坐标中,输出LaserScan。它允许你设置角度范围,比如只取前方180度或全向360度。
缺点是它默认会对高度范围内的所有点做距离最小化处理——也就是说,如果在这个高度范围内有一根柱子、一张桌子或者一面墙,它会取最近的点作为该角度的距离。这样处理的好处是能感知到一定高度范围内的障碍物,坏处是如果你选择的高度带太宽,会把一些本不该出现在2D平面上的物体(比如悬空的支架、柜子上沿)也投影进来,造成地图里的“假墙”。
方案二:自己写节点,固定提取某一根线。
这个方法最精准。robosense16每一根线都有对应的垂直角度,比如-15°、-13°、-1°、1.33°之类的分布。我们可以订阅点云,遍历每个点,根据点的垂直角度或坐标把接近0°的那根线提取出来,然后把该角度上的距离按固定角度增量塞进LaserScan。
好处是输出的扫描与单线雷达几乎没有差别,建图效果最接近hector的“舒适区”。缺点是代码量稍大,而且如果雷达安装有倾斜角,你需要额外注意补偿。
方案三:对多根水平线做距离融合。
把俯仰角在-3°到+3°之间(比如多取两三根线)的所有点合在一起,每个角度上保存最小距离值。这个思路介于方案一与方案二之间,能利用更多点减小测量噪声,但代价是可能有融合误差。在走廊里效果还行,在复杂室内会显得“毛刺”多。
我最终选择了方案一作为基础,再叠加高度裁剪来规避假墙问题,因为该项目要求快速验证,我不想把时间耗在写一个从零开始的转换节点上。后面我会给出实际使用的launch配置和说明,这是我调过很多版之后比较稳定的参数。
3.3 实际操作:用pointcloud_to_laserscan完成转换
这里给出一个我实际在用的launch片段,配置项都有注释,方便你直接抄。
<launch> <!-- 加载点云转scan的参数 --> <node pkg="pointcloud_to_laserscan" type="pointcloud_to_laserscan" name="pointcloud_to_laserscan" output="screen"> <!-- 输入点云话题,来自robosense16驱动 --> <remap from="cloud_in" to="/rslidar_points"/> <!-- 输出scan话题,供hector使用 --> <remap from="scan" to="/scan"/> <param name="target_frame" value="rslidar"/> <param name="transform_tolerance" value="0.01"/> <!-- 高度裁剪:只取雷达下方0.1米到雷达上方0.5米范围内 --> <param name="min_height" value="-0.1"/> <param name="max_height" value="0.5"/> <!-- 角度范围:取全向360度 --> <param name="angle_min" value="-3.1415926"/> <param name="angle_max" value="3.1415926"/> <param name="angle_increment" value="0.005"/> <!-- 扫描距离范围 --> <param name="range_min" value="0.3"/> <param name="range_max" value="30.0"/> <param name="use_inf" value="true"/> <param name="inf_epsilon" value="1.0"/> </node> </launch>说一下几个参数的取值理由。
min_height=-0.1,max_height=0.5:radar通常安装在底盘上方0.3到0.5米的位置,我取这个高度范围,是为了把水平线周围有效扫描点纳入进来,同时避免把天花板、桌面等明显高于雷达的点投影到平面里。
angle_increment=0.005约等于0.286度,这个分辨率对hector来说足够,而且ranges数组长度约为1257点,计算压力不大。如果你想要更密,可以改成0.001,但要注意CPU占用会成倍上升。
range_min=0.3这个值很关键。robosense16在极近距离上会有一定的盲区,距离太近的点可能散布大噪声。0.3米以内的点全部忽略,可以让扫描更干净。
使用这个节点前,需要保证tf树上存在rslidar到其他坐标系的变换,否则transform_tolerance会触发警告,扫描话题可能不输出。
注意:如果你用的是官方驱动,默认点云坐标系可能是
rslidar,也可能是laser_frame,启动之前务必用tf2_echo或者rviz确认一下坐标系的命名,不然后面hector的tf配置全都会对不上。
4. hector_slam建图的参数调整与实操流程
4.1 hector的核心配置项解析
hector_slam的配置集中在hector_mapping节点中,不要小看这些参数,它们直接决定地图质量。下面是我在robosense16加pointcloud_to_laserscan这套链路下调得比较稳的一组参数:
hector_mapping: map_size: 2048 map_resolution: 0.05 map_start_x: 0.5 map_start_y: 0.5 map_multi_res_levels: 3 update_factor_free: 0.4 update_factor_occupied: 0.9 laser_min_dist: 0.3 laser_max_dist: 30.0 use_tf_scan_transformation: true use_tf_pose_start_estimate: false pub_map_odom_transform: false pub_odometry: false scan_subscriber_queue_size: 50 map_pub_period: 2.0逐个说明:
map_size和map_resolution是地图大小与分辨率。2048栅格,每格0.05米,表示地图覆盖约102米×102米的范围。在室内跑足够用。如果你发现机器人在某处走了半天但地图边界还是黑色,说明它已经走出地图范围,应扩大地图尺寸。map_multi_res_levels是hector经典的多分辨率匹配策略参数。它通过在不同分辨率的栅格地图上由粗到精搜索匹配,能有效提高匹配鲁棒性。这个值建议保持默认3层,不必改动。update_factor_free=0.4与update_factor_occupied=0.9分别控制地图中空闲与占据概率的更新权重。占据值高意味着扫描到的障碍物会更鲜明地刻进地图,也能更快地规避重影。对于robosense16这种点云经投影后的扫描,障碍物点的信噪比不错,所以occupied=0.9比较合适。laser_min_dist=0.3必须和pointcloud_to_laserscan里的range_min保持一致,否则两者不一致时会在地图中心产生毛刺。
4.2 坐标系与tf配置
hector对tf的要求是:存在一个/map到/base_footprint(或/base_link)的变换。它内部维护了odom到base的推算,但注意它的位姿估计是基于扫描匹配得到,不是从轮式里程计读取。
常见tf树如下:
map -> odom -> base_footprint -> base_link -> rslidar但在hector中,通常可以简化成map -> base_link -> rslidar,也就是odom这一层可以不要。原因在于hector直接把地图坐标系当作里程计坐标系,不需要单独的odom转换。实际launch里我写了这样的静态变换:
<node pkg="tf2_ros" type="static_transform_publisher" name="base_to_rslidar" args="0 0 0.5 0 0 0 base_link rslidar"/>这里把雷达装在底盘正上方0.5米处,无旋转。如果你的雷达安装在车头或带仰角,需要把XYZ和RPY按实际情况填进去。
有一个更细节的点:hector默认会监听/scan话题上的激光坐标系到/base_link的tf变换。如果你的target_frame设置了rslidar,那么必须在tf树里保证存在从base_link到rslidar的变换。很多人只配置了base到rslidar,却让pointcloud_to_laserscan以rslidar为target_frame,结果发布scan时一直报“waiting for transform”,就是这个原因。
4.3 完整建图流程与效果观察
整套流程启动顺序是:
- 启动roscore(launch会自动启动);
- 启动robosense16驱动,确认
/rslidar_points有数据,rviz中能看到3D点云; - 启动pointcloud_to_laserscan,确认
/scan话题正常输出,频率在8-10Hz左右; - 启动hector_slam(包含hector_mapping和hector_slam的rviz配置);
- 在rviz中添加
Map显示并选择话题/map,同时显示LaserScan查看扫描帧与地图的贴合情况。
如果你用我上面的launch片段,那么你还需要一个总体的launch把这三个节点串起来。下面给一个简化的示例:
<launch> <!-- 雷达驱动(示意) --> <include file="$(find rslidar_sdk)/launch/rslidar.launch"/> <!-- 点云转scan --> <include file="$(find your_pkg)/launch/cloud_to_scan.launch"/> <!-- hector slam --> <include file="$(find hector_slam_launch)/launch/hector_mapping.launch"/> </launch>实际操作时,我的启动顺序基本就是先驱动、再转换、最后hector。如果你发现启动hector后地图一片空白,优先检查/scan的频率,用rostopic hz /scan看发布频率;然后再用rqt_tf_tree看tf是否完整——这两个是hector不吐地图时最常见的病根。
手动控制机器人走动时,尽量保持速度平缓,尤其是转角处不要猛打方向。hector没有预测模型,帧间位移太大旋转太大,匹配很容易跟丢。很多人抱怨hector精度差,其实有一大半是因为机器人开太快。16线雷达转出来的scan虽然点数多,但本质上和单线雷达一样受帧间位移限制,这是算法特性,只能通过控制速度来适应它。
5. 实战中踩过的坑与排查笔记
5.1 扫描线选择不对造成的“幽灵墙”
第一次把16线点云转成scan后,我在地图上看到很多本不存在的墙。排查后发现,pointcloud_to_laserscan的高度范围设置得太宽。robosense16的四根线分布在上下较广范围,如果min_height和max_height之间包含了天花板下方的横梁、货架上层,就会被投影为2D障碍。
解决方式有两种:
- 把高度范围缩窄,只保留雷达上下0.3米内的扫描点;
- 改为提取某一固定垂直角度线,物理上保证“同一平面”。
从那以后我把max_height从1.0调整到0.5,地图立刻清爽了。如果你发现自己扫描里有明显的“额外墙体”,几乎所有情况都是高度裁剪范围太宽导致的,而不是hector参数问题。
5.2 地图断裂与漂移
用hector跑长走廊时,地图会渐渐变形,甚至出现两边的墙交叉。这是hector在“环境自相似+无回环检测”下的必然结果,不是参数能根治的。
我的处理技巧是:
- 提高地图分辨率,用0.02而不是0.05,这样匹配时的栅格粒度更细,能减少一定程度的累积误差;
- 降低机器人转弯速度,让扫描帧重叠率更高;
- 若允许,在关键位置停一下,让hector多匹配几帧,能略微缓解漂移。
如果漂移严重到走廊完全错位,建议直接改用cartographer。hector在小区域表现得心应手,但在“中途不断重复经过相似区域”的场景下确实不够从容。
5.3 CPU占用过高导致scan频率下降
robosense16的驱动节点本身会占用不少CPU,如果再叠加pointcloud_to_laserscan的坐标变换、hector的栅格更新,树莓派或低端工控机很容易满载。一个直接后果是hector的匹配线程抢不到资源,scan处理延迟,然后地图出现跳跃。
我的处理经验是:
- 在驱动配置中把点云帧率从10Hz降到8Hz,肉眼感觉几乎没有影响;
- 把pointcloud_to_laserscan的
angle_increment放宽到0.01,大约1.1度一个点,能明显减少投影计算量; - 对rviz的可视化频率做限制,不要每帧都渲染全部点云,只在需要看时开启点云显示。
这样调整后,CPU占用能下降30%左右,hector匹配周期稳定,地图连续性明显提升。
5.4 原点附近的毛刺与噪点
第一次用这套系统建图时,地图中心(机器人起点)周围总有一圈不规则的黑色噪点。查出来的原因是点云中存在近距离的少量飞点,而pointcloud_to_laserscan的range_min设置的是0.1米,雷达在0.3米内的点本身不稳定。
把这些近距离点滤掉后,问题直接消失。所以如果你也看到地图原点附近“开花”,不要动hector参数,先去改转换节点里的范围裁剪。
5.5 注意时间戳同步
还有一个不太起眼但能折腾半天的坑:点云和scan的时间戳不同步。robosense16的驱动有时默认时间戳来自雷达内部时钟,而本机系统时钟不同步时,pointcloud_to_laserscan会报“transform failed”,尽管tf树是完整的。
解决办法很简单,在驱动或转换节点里设置use_sim_time(如果你使用仿真或者回放bag)时统一使用一个时间源;或者直接把驱动的时间戳改为系统时间。具体做法因驱动版本而异,但你要有这个概念:hector对时间戳的要求比对精度更高,时间戳错误会直接导致扫描不输出。
6. 经验总结与后续优化方向
6.1 个人实操体会
这一套“robosense16 多线雷达 + hector建图”组合,调试的难点并不在hector本身,而在点云到2D扫描之间的桥接。hector的匹配策略成熟、参数直观,适合快速上手;多线雷达又提供了足够丰富的数据来源。但二者的结合意味着你要额外管理坐标系、高度裁剪、扫描帧率转换——这些杂活恰恰是建图质量好坏的决定因素。
从实际效果上说,只要把点云转换参数和运动速度控制好,在地面平整、环境特征足够的室内场景下,这张地图完全可以用作后续导航的参考。但我也要直言,如果拿到一张看起来不错的hector地图就直接去跑自主导航,仍然要慎重。因为hector没有回环修正,可能在一些走廊连接处存在肉眼不易察觉的累计误差,导航路径规划时会在某些区域撞墙。如果要做更严谨的定位导航,至少要再叠加amcl做位置追踪,并在局部代价地图中多保留一些传感器余量。
6.2 如果还想继续折腾
后续有几个明显的升级方向,按难度排序分别是:
- 把固定提取单线改成多线融合,同时加入简单的异常点剔除,能提高扫描的稳定性。
- 给底盘加上编码器或者IMU,引入extended kalman filter做传感器融合,构建一个更完整的tf树,这样也能改善连续转弯时的漂移。
- 如果愿意接受更高复杂度,可以把2D hector换成3D SLAM框架,利用robosense16的全部点云做同时建图,那样精度和环境适应性会强很多,但代价是计算量与调试复杂度成倍上升。
6.3 几个马上能用的建议
- 如果你只是临时验证,直接用pointcloud_to_laserscan,别自己写转换代码;
- 把
angle_increment设置为0.005到0.01之间,别追求极致分辨率,hector不吃这一套; - hector参数不需要大调,默认值加合理的laser_min_dist即可用;
- 手动遥控机器人时,转弯速度一定慢,匀速直线为主,地图成功率会大幅提升;
- 所有launch参数务必先做10分钟小范围测试,再去跑大区域,能避免很多无谓的时间浪费。
这个项目最吸引人的地方,其实是“旧算法 + 新传感器”的组合思路。一个大家觉得已经过时的2D建图算法,在一台16线雷达的加持下,依然能在很多场景里快速产出可靠地图。技术选型不一定要追新,把现有工具的性能边界摸透,有时候已经很够用了。