launch 和 lua 配置文件都是在install_isolated/share/cartographer_ros/launch/
和 install_isolated/share/cartographer_ros/configuration_files/
下,所以如果只修改了cartographer_ros软件包中的launch 和 lua 文件之后是需要重新编译的,编译之后才能将修改的文件安装到install_isolated文件中。
从cartographer启动时输出的信息可知,我们自己的lua加载的其他lua有:map_builder.lua
, pose_graph.lua
, trajectory_builder.lua
, trajectory_builder_2d.lua
. 它们的路径都在/usr/local/share/cartographer/configuration_files
。如果我们需要改这些基本lua的参数,可以在自己lua里覆盖地修改,直接改原文件不太好,它们的关系如下:
map_builder.lua
和trajectory_build.lua
起着总领的作用,前者包括的pose_graph.lua
为后端优化的具体参数配置; 后者是前端的参数配置,分为2d和3d两个文件。
lua_parameter_dictionary.cc
是用于lua参数的,如果这里有报错,基本是lua参数的语法问题
1 | [/cartographer_node] [ScopedRosLogSink::send] line_63 F0402 21:57:53.000000 20519 lua_parameter_dictionary.cc:399] Check failed: HasKey(key) Key 'use_nav_sat' not in dictionary: |
在lua文件里必须设置use_nav_sat
参数,同样报错的参数都要添加
自定义lua的参数
我用的是home.lua
, 先看options的内容,这是通用的参数。options块中定义的值定义了cartographer前端应当如何与机器人进行交互。 定义在options段后的值用于调试cartographer的内部信息
开头的map_builder = MAP_BUILDER
和trajectory_builder = TRAJECTORY_BUILDER
万年不变:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33map_builder = MAP_BUILDER,
trajectory_builder = TRAJECTORY_BUILDER,
map_frame = "map",
tracking_frame = "imu",
published_frame = "odom",
odom_frame = "odom",
provide_odom_frame = false,
publish_frame_projected_to_2d = false,
publish_tracked_pose = false
use_odometry = true,
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,
submap_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.,
# 补充
collate_fixed_frame = true 是否将数据放入阻塞队列
collate_landmarks = false -- 一般都是false
坐标系组
map_frame: 用于发布submaps的ROS坐标系,也是位姿的父坐标系,通常是map
tracking_frame: 通常,如果使用IMU,就是
imu_link
; 如果不用IMU,可以用laser
。博物馆的lua写的base_link
有时遇到这样的报错:1
Check failed: sensor_to_tracking->translation().norm() < 1e-5 The IMU frame must be colocated with the tracking frame. Transforming linear acceleration into the tracking frame will otherwise be imprecise
这是因为在使用IMU的情况下,tracking frame
不是imu的link
published_frame: 位姿子坐标系的ROS坐标系,例如”odom”坐标系,如果一个odom坐标系由系统的不同部分提供,在这种情况下,map_frame中的
odom
姿势将被发布。 否则,将其设置为base_link
可能是合适的provide_odom_frame: true 适用于机器人本身没有odom坐标系的情况,如果启用,将发布局部、非闭环、持续的位姿,也就是odom_frame在map_frame中的坐标
- odom_frame: 在provide_odom_frame为true才启用,坐标系在published_frame和map_frame之间用于发布局部SLAM结果,通常是”odom”
- use_odometry: 如果启用,会订阅关于odom话题(或者叫其他名字)的
nav_msgs/Odometry
消息。应当提供里程信息,这些信息包含在SLAM里 - use_nav_sat: 是否使用gps数据。如果启用,将订阅
sensor_msgs/NavSatFix
的话题,比如fix. 应当提供导航数据,将用于全局SLAM use_landmarks: 如果启用,订阅
cartographer_ros_msgs/LandmarkList
的话题,比如landmarks. 应当提供Landmarks信息,这些信息包含在SLAM里use_pose_extrapolator
: 默认false. 发布tf时是使用 pose_extrapolator推断的位姿还是前端计算出来的位姿
1 | stamped_transform.header.stamp = |
旧版本没有这个参数,直接使用了now
。这个参数认为位姿估计器很精确,但前提是雷达频率高,时间间隔短。最好是使用前端的时间戳
- publish_frame_projected_to_2d: 默认false. 发布TF变换时,是否将tracking在Local坐标系中的坐标投影到2D平面上,这只是个显示功能,不影响SLAM。
- publish_tracked_pose: 是否发布话题
tracked_pose
,也就是tracking坐标系在map坐标系的位姿,朝向与imu_link的x轴重合
num组
Cartographer
可以订阅的主题有三种,我们一般用scan
,还可以用echoes
或points2
,这三个是互斥的。分别对应lua中的num_laser_scans
, num_multi_echo_laser_scans
,num_point_clouds
. 这三个参数必须都是 >= 0 且三者之和 >=1 代码在ComputeRepeatedTopicNames
lua中将num_laser_scans
设置为1,则scan
将用作SLAM的输入,如果num_laser_scans
大于1,则多个编号的扫描主题(即scan_1,scan_2,scan_3,……直到并包括num_laser_scans)将用作SLAM的输入.
当num_point_clouds
为1时,默认订阅points2
话题的数据
同理,如果将num_multi_echo_laser_scans
设置为1,则echoes
做输入,但仅使用第一个回声,如果大于1,则多个编号的回声主题(即echoes_1,echoes_2,echoes_3,……直到并包括num_multi_echo_laser_scans)将用作SLAM的输入。echoes
的消息类型为sensor_msgs/MultiEchoLaserScan
,不同之处在于sensor_msgs/LaserEcho[] ranges
和sensor_msgs/LaserEcho[] intensities
points2
也是这样,消息类型sensor_msgs/PointCloud2
,num_point_clouds
适用于多线雷达,对于单线,设置为0
- num_subdivisions_per_laser_scan: 将雷达一帧的数据拆分成几次发出来,必须>=1,默认10。在
SensorBridge::HandleLaserScan
中调用, 对点云points细分。比如雷达scan有200束激光,本参数为10,那么转换成点云后,对点云取很多小段处理:(0,20)、(20,40)……(190,200)。 对于普通雷达来说,此处为1即可
period 组
都在Node
的构造函数里,是ROS定时器的发布间隔
- lookup_transform_timeout_sec: 使用tf2查找
tracking_frame
和雷达坐标系变换的超时秒数,在函数TfBridge::LookupToTracking
- submap_publish_period_sec: 发布话题
submap_list
的间隔 - posepublish_period_sec: 对应`publish_local_trajectory_data_timer
和
Node::PublishLocalTrajectoryData`. 例如5e-3,对应tf发布频率为200Hz - trajectory_publish_period_sec: 发布轨迹节点和Landmark的时间间隔,对应
PublishTrajectoryNodeList
和PublishLandmarkPosesList
ratio 组
5种观测的权重比
rangefinder_sampling_ratio: Fixed ratio sampling for range finders messages.
odometry_sampling_ratio: Fixed ratio sampling for odometry messages. 如odom的数据非常不准,可以设置为0.3,以减小odom对整体优化的影响
fixed_frame_sampling_ratio: Fixed ratio sampling for fixed frame messages.
imu_sampling_ratio: Fixed ratio sampling for IMU messages.
landmarks_sampling_ratio: Fixed ratio sampling for landmarks messages.
options结束后,一般有一句:MAP_BUILDER.use_trajectory_builder_2d = true
,这是因为map_builder.lua
中的这个参数设置为false了,我们在这里可以重新赋值,选择使用2D还是3D
不使用机器人的IMU和Odom
1 | map_frame = "map", |
进行2d slam时,carto的默认配置是使用imu的,所以如果没有imu就要TRAJECTORY_BUILDER_2D.use_imu_data = false
,否则会一直等待imu的数据而进行不下去。而3D slam 必须使用imu,所以不必修改这个参数。
- 为什么3D必须有IMU,而2D可以不用?
在2D中,Cartographer支持运行相关的扫描匹配器,用于局部SLAM中寻找循环关闭的约束条件。它的计算代价很大,但通常会使odometry或IMU数据的加入变得不必要。2D也有假设平坦世界的好处,也就是说,上升是隐式定义的。
在三维中,IMU主要用于测量重力。重力在测量中是一个很有用的量,因为它不漂移,而且是一个非常强的信号,而且通常包含了大部分所要测量的加速度。
Node::LaunchSubscribers
函数中有一段:1
2
3
4if (node_options_.map_builder_options.use_trajectory_builder_3d() ||
(node_options_.map_builder_options.use_trajectory_builder_2d() &&
options.trajectory_builder_options.trajectory_builder_2d_options()
.use_imu_data() ) )
显然源码里已经做了规定
- 屏蔽机器人的里程计
如果我们不想使用轮式里程计, cartographer的lua无法屏蔽里程计,只能自己修改机器人的程序,不让它发布odom话题和odom--->base_footprint
的tf变换。如果不修改,使用时会发现tf变换一会儿由 cartographer发布,一会儿由机器人程序发布,千万不能由两个节点发布同一个tf变换,从rviz看上去,RobotModel和LaserScan摇晃地很厉害。
这里有一个技巧, 观察一个tf变换是否由两个节点发布,用rosrun tf view_frame是看不出来的,应该用rosrun rqt_tf_tree rqt_tf_tree,然后用刷新按钮观察。
如果不用IMU,Odom + Lidar在局部存在纠偏失败的可能。 但是在里程计与imu不准的情况下,使用imu和odom反而建图效果会更差使用机器人的odom和imu,lua做如下设置:
1 | map_frame = "map", |
一开始,我增加了参数TRAJECTORY_BUILDER_2D.num_accumulated_range_data = 10
,结果出现了scan一直在转的情况,使用tf_echo
map和laser坐标系的关系,发现y位移一直在增大,但x z基本不变。去掉这个参数后,不再一直转了,但是过了几分钟还是有所偏,地图还出现了重影,这说明是机器人的里程计误差问题,需要校准,这个参数应该是放大了这种现象。
如果不想每过一段时间就校准,那么odom
到base_link
的tf就用cartographer的,因为它有重定位,累计误差比较小,而且可以得到修正
对是否使用odom的两套程序如下:
查看节点cartographer_node
是否订阅了IMU和Odom,检查是否用到了它们
问题
使用IMU后,有时启动时会报警:
出问题的代码在cartographer::mapping::ImuTracker::AddImuLinearAccelerationObservation()
。启动一下imu程序,如果rostopic echo /xqserial_server/IMU/linear_acceleration
就会发现,imu线加速度开始为0,过了一秒左右才有数据,这应该是节点通信的问题。所以cartographer读取到的线加速度全是0,在程序里就会报错。此时再重启就没事了建图时地图围绕原点转圈,画出来的是一团黑,可能是用到的IMU的加速度有问题,需要校准IMU。还有可能是ceres的平移和旋转的权重没有进行重新配置。
如果出现下面的输出,一般是
scan
话题没有订阅到:1
[ WARN][/cartographer_node] [ScopedRosLogSink::send] line_55 W0711 20:05:58.000000 12245 ordered_multi_queue.cc:155] Queue waiting for data: (0, scan)
正确的是这样:
1
[ INFO][/cartographer_node] [ScopedRosLogSink::send] line_51 I0711 20:14:56.000000 25541 ordered_multi_queue.cc:172] All sensor data for trajectory 0 is available starting at '637300664959145564'.
常常启动后出现这个报警:
这其实是tf坐标系的问题,cartographer源码里lookupTransform写的不好,不管也可以,详细分析可见
tf报警:Lookup would require extrapolation into the past
- 机器人在地图中的位置不稳定,有轻微摆动,tf_echo如下:
yaw有跳变, 判断是里程计累计误差
- cartographer对雷达频率要求比较高,20Hz以上比较好,当频率达到30hz时,做纯旋转运动时不会产生地图的偏移。因为只有频率越高,2帧间的时间越短,误差才能越小。
如果雷达比较差,地图边界会比较粗糙,用robosense雷达明显效果好
map_builder.lua 和 trajectory_builder.lua
内容只有几行:1
2
3
4
5
6
7
8include "pose_graph.lua"
MAP_BUILDER = {
use_trajectory_builder_2d = false,
use_trajectory_builder_3d = false,
num_background_threads = 4,
pose_graph = POSE_GRAPH,
}
map_builder.lua内容太简单了,配置是使用trajectory_build_2d
还是trajectory_build_3d
,以及后端使用的线程数。 use_trajectory_builder_2d
在上面讲过改为true,其他一般不动,如果CPU性能好,可以增大num_background_threads
1 | include "trajectory_builder_2d.lua" |
trajectory_builder.lua
更简单,只有一个pure_localization
区分是否使用carto用于机器人纯定位