在Ubuntu 20.04与ROS环境下利用ORBSLAM2实现视觉SLAM:构建map→odom
与odom→base_link
转换关系的详尽解析
在移动机器人导航系统中,视觉同步定位与地图构建(Visual SLAM)是实现自主定位与路径规划的关键技术。ORBSLAM2作为一种高效的视觉SLAM算法,能够在仅依赖视觉传感器(如RGB-D相机)的情况下,实时构建环境地图并估计机器人位姿。然而,ORBSLAM2默认仅提供map→camera_link
的坐标转换关系。为了在ROS(机器人操作系统)中实现更为复杂的导航功能,如利用里程计信息进行融合与优化,需要进一步推导并构建map→odom
与odom→base_link
的转换关系。本文将以Ubuntu 20.04为操作系统,详细阐述在已知base_link→camera_link
转换关系的前提下,如何通过数学推导与ROS工具实现这一目标。
目录
- 引言
- 系统架构与坐标系定义
- 已知条件与目标转换关系
- 数学推导与转换关系构建
- 4.1. 从
map→camera_link
到map→odom
- 4.2. 通过视觉里程计获取
odom→base_link
- 4.3. 综合构建
map→odom
- 4.1. 从
- ROS中TF框架的应用
- 5.1. 发布静态转换
base_link→camera_link
- 5.2. 发布动态转换
odom→base_link
- 5.3. 发布计算得到的
map→odom
- 5.1. 发布静态转换
- 详细实现步骤
- 6.1. 环境配置与依赖安装
- 6.2. ORBSLAM2与ROS的集成
- 6.3. 自定义ROS节点的开发
- 6.4. 启动与调试
- 示例代码与解释
- 7.1.
map_odom_broadcaster
节点代码 - 7.2.
odom_base_link_broadcaster
节点代码 - 7.3. 静态转换发布命令
- 7.1.
- 潜在问题与解决方案
- 8.1. 时间同步与延迟
- 8.2. 坐标系精度与误差积累
- 8.3. TF广播频率优化
- 总结
- 附录:参数配置示例
1. 引言
在移动机器人导航中,准确的定位与地图构建是实现自主移动与环境感知的基础。视觉SLAM技术通过摄像头捕获的图像序列,实时估计机器人的位姿并构建环境地图。ORBSLAM2作为一种广泛应用的视觉SLAM算法,提供了高效的定位与地图构建能力。然而,在复杂的ROS系统中,如何将ORBSLAM2的输出与里程计信息进行有效整合,进而构建map→odom
与odom→base_link
的转换关系,成为实现精准导航的关键。
2. 系统架构与坐标系定义
在ROS中,不同的组件和传感器通常位于不同的坐标系下。理解并正确构建这些坐标系之间的转换关系,是实现系统各部分协调工作的前提。
- map: 全局固定坐标系,表示SLAM系统构建的环境地图。用于全局定位与导航。
- odom: 里程计坐标系,表示从机器人的初始位置开始的连续位姿估计。通常由里程计或视觉里程计提供。
- base_link: 机器人基座坐标系,位于机器人中心,通常与机器人运动机构(如轮子)固定。
- camera_link: 相机坐标系,表示RGB-D相机的位置与方向。
坐标系关系图
map
|
| map→odom
|
odom
|
| odom→base_link
|
base_link
|
| base_link→camera_link
|
camera_link
3. 已知条件与目标转换关系
已知条件:
- ORBSLAM2输出:
- 提供
map→camera_link
的位姿转换关系。
- 提供
- 测量数据:
- 已知
base_link→camera_link
的静态转换关系,通过机械标定或手动测量获得。
- 已知
- 视觉里程计:
- 仅使用RGB-D相机,提供动态的
odom→base_link
位姿估计。
- 仅使用RGB-D相机,提供动态的
目标转换关系:
- 构建并获取
map→odom
和odom→base_link
的转换关系,以实现系统中各坐标系的正确对齐与融合。
4. 数学推导与转换关系构建
为了实现从已知的map→camera_link
和base_link→camera_link
,以及动态的odom→base_link
,推导出map→odom
和odom→base_link
的转换关系,需要通过坐标变换的链式法则进行数学推导。
4.1. 从map→camera_link
到map→odom
已知:
map→camera_link
:由ORBSLAM2提供,表示相机在地图坐标系中的位姿。base_link→camera_link
:通过测量获得,表示相机相对于机器人基座的固定变换。
目标:
- 构建
map→odom
转换关系。
步骤:
-
表达
map→base_link
:通过坐标变换的链式法则,
map→base_link
可以表示为:[
map→base_link = map→camera_link \times camera_link→base_link
]其中:
[
camera_link→base_link = (base_link→camera_link)^{-1}
]因为
camera_link→base_link
是base_link→camera_link
的逆变换。 -
定义
map→odom
:map→odom
的定义基于闭环坐标变换关系:[
map→odom = map→base_link \times (odom→base_link)^{-1}
]代入
map→base_link
的表达式:[
map→odom = (map→camera_link \times camera_link→base_link) \times (odom→base_link)^{-1}
]简化为:
[
map→odom = map→camera_link \times (base_link→camera_link)^{-1} \times (odom→base_link)^{-1}
]
4.2. 通过视觉里程计获取odom→base_link
视觉里程计基于RGB-D相机提供动态的odom→base_link
位姿估计。这一转换关系反映了机器人在里程计坐标系中的实时位姿变化。
4.3. 综合构建map→odom
结合以上推导,最终的map→odom
转换关系为:
[
map→odom = map→camera_link \times (base_link→camera_link)^{-1} \times (odom→base_link)^{-1}
]
这一表达式确保了odom
坐标系能够与map
坐标系准确对齐,形成闭环,防止误差的积累与漂移。
5. ROS中TF框架的应用
ROS中的TF(Transform)框架负责在不同坐标系之间传递和管理变换关系。正确发布各转换关系,确保系统中各节点能够准确获取所需的坐标变换,是实现精准定位与导航的基础。
5.1. 发布静态转换base_link→camera_link
base_link→camera_link
是一个固定的转换关系,可以通过static_transform_publisher
进行发布,无需自定义节点。
发布命令示例:
rosrun tf2_ros static_transform_publisher 0.1 0.0 0.2 0 0 0 1 base_link camera_link
参数说明:
0.1 0.0 0.2
:相机相对于机器人基座的平移(x, y, z)。0 0 0 1
:相机相对于机器人基座的旋转(四元数形式)。base_link
:父坐标系。camera_link
:子坐标系。
5.2. 发布动态转换odom→base_link
odom→base_link
是由视觉里程计提供的动态转换关系,需要通过自定义节点或集成的视觉里程计节点持续发布。
发布方法:
- 使用视觉里程计节点直接发布
odom→base_link
的变换。 - 确保变换的时间戳与其他变换一致,以保持系统的时间同步。
5.3. 发布计算得到的map→odom
map→odom
是通过数学推导得出的动态转换关系,需要通过自定义ROS节点进行计算与发布。
发布方法:
- 创建一个自定义ROS节点(如
map_odom_broadcaster
)。 - 订阅
map→camera_link
与odom→base_link
的变换信息。 - 计算
map→odom
的变换关系。 - 通过TF广播器发布
map→odom
转换。
6. 详细实现步骤
以下将详细介绍在Ubuntu 20.04与ROS环境下,如何实现上述转换关系的构建与发布。
6.1. 环境配置与依赖安装
-
安装Ubuntu 20.04:
- 确保系统已安装并更新至最新状态。
-
安装ROS Noetic:
按照ROS官方指南进行安装。
sudo apt update sudo apt install ros-noetic-desktop-full
初始化ROS环境:
echo "source /opt/ros/noetic/setup.bash" >> ~/.bashrc source ~/.bashrc
-
安装ORBSLAM2:
克隆ORBSLAM2仓库并按照其官方安装指南进行编译。
git clone https://github.com/raulmur/ORB_SLAM2.git cd ORB_SLAM2 chmod +x build.sh ./build.sh
确保安装所有必要的依赖,如OpenCV、Pangolin等。
-
安装必要的ROS包:
sudo apt install ros-noetic-tf2-ros ros-noetic-geometry-msgs ros-noetic-roscpp
6.2. ORBSLAM2与ROS的集成
-
启动ORBSLAM2节点:
配置ORBSLAM2以ROS节点的形式运行,确保其能够发布
map→camera_link
的位姿信息。可以使用现有的ROS接口,或自行编写桥接节点,将ORBSLAM2的位姿输出转换为TF变换发布。
-
配置视觉里程计:
使用RGB-D相机作为视觉里程计,确保其能够发布
odom→base_link
的位姿变换。如果使用的是现成的视觉里程计包,如RTAB-Map,可以直接利用其输出。
6.3. 自定义ROS节点的开发
为了计算并发布map→odom
转换关系,需要开发一个自定义ROS节点(如map_odom_broadcaster
),其主要功能包括:
- 订阅
map→camera_link
与odom→base_link
的变换信息。 - 计算
map→odom
转换。 - 发布
map→odom
转换。
开发步骤:
-
创建ROS工作空间:
mkdir -p ~/catkin_ws/src cd ~/catkin_ws/ catkin_make source devel/setup.bash
-
创建自定义包:
cd ~/catkin_ws/src catkin_create_pkg map_odom_broadcaster roscpp tf2_ros tf2_geometry_msgs
-
编写节点代码:
在
map_odom_broadcaster
包中,创建src/map_odom_broadcaster.cpp
文件,并实现节点功能。 -
编译工作空间:
cd ~/catkin_ws catkin_make source devel/setup.bash
6.4. 启动与调试
-
启动所有相关节点:
- ORBSLAM2节点
- 视觉里程计节点
- 自定义的
map_odom_broadcaster
节点 - 静态转换发布器(
base_link→camera_link
)
-
使用
rviz
进行可视化:启动
rviz
,配置显示所有相关坐标系,验证转换关系的正确性。rosrun rviz rviz
-
监控TF变换:
使用
tf_echo
或tf_monitor
工具监控各转换关系的发布状态与延迟。rosrun tf tf_echo map odom rosrun tf tf_echo odom base_link
7. 示例代码与解释
以下提供自定义ROS节点map_odom_broadcaster
的示例代码,用于计算并发布map→odom
转换关系。
7.1. map_odom_broadcaster
节点代码
// File: src/map_odom_broadcaster.cpp
#include <ros/ros.h>
#include <tf2_ros/transform_broadcaster.h>
#include <tf2_ros/transform_listener.h>
#include <geometry_msgs/TransformStamped.h>
#include <tf2/LinearMath/Transform.h>
#include <tf2_geometry_msgs/tf2_geometry_msgs.h>
int main(int argc, char** argv){
ros::init(argc, argv, "map_odom_broadcaster");
ros::NodeHandle nh;
tf2_ros::TransformBroadcaster br;
tf2_ros::Buffer tfBuffer;
tf2_ros::TransformListener tfListener(tfBuffer);
ros::Rate rate(30.0); // 发布频率为30Hz
// 读取参数服务器上的base_link→camera_link静态变换
double x, y, z, qx, qy, qz, qw;
if (!nh.getParam("base_to_camera_x", x) ||
!nh.getParam("base_to_camera_y", y) ||
!nh.getParam("base_to_camera_z", z) ||
!nh.getParam("base_to_camera_qx", qx) ||
!nh.getParam("base_to_camera_qy", qy) ||
!nh.getParam("base_to_camera_qz", qz) ||
!nh.getParam("base_to_camera_qw", qw)) {
ROS_ERROR("Failed to get base_to_camera transform parameters");
return -1;
}
tf2::Transform base_to_camera;
base_to_camera.setOrigin(tf2::Vector3(x, y, z));
tf2::Quaternion q;
q.setX(qx);
q.setY(qy);
q.setZ(qz);
q.setW(qw);
base_to_camera.setRotation(q);
// 计算camera_link→base_link的逆变换
tf2::Transform camera_to_base = base_to_camera.inverse();
while (ros::ok()){
geometry_msgs::TransformStamped map_to_camera;
geometry_msgs::TransformStamped odom_to_base;
try{
// 获取map→camera_link变换
map_to_camera = tfBuffer.lookupTransform("map", "camera_link", ros::Time(0), ros::Duration(0.1));
// 获取odom→base_link变换
odom_to_base = tfBuffer.lookupTransform("odom", "base_link", ros::Time(0), ros::Duration(0.1));
}
catch (tf2::TransformException &ex) {
ROS_WARN("Could not get transform: %s", ex.what());
ros::Duration(0.1).sleep();
continue;
}
// 将geometry_msgs::TransformStamped转换为tf2::Transform
tf2::Transform tf_map_camera;
tf2::fromMsg(map_to_camera.transform, tf_map_camera);
tf2::Transform tf_odom_base;
tf2::fromMsg(odom_to_base.transform, tf_odom_base);
// 计算map→base_link = map→camera_link * camera_link→base_link
tf2::Transform tf_map_base = tf_map_camera * camera_to_base;
// 计算map→odom = map→base_link * (odom→base_link)^-1
tf2::Transform tf_map_odom = tf_map_base * tf_odom_base.inverse();
// 将tf2::Transform转换回geometry_msgs::TransformStamped
geometry_msgs::TransformStamped map_to_odom;
map_to_odom.header.stamp = ros::Time::now();
map_to_odom.header.frame_id = "map";
map_to_odom.child_frame_id = "odom";
map_to_odom.transform = tf2::toMsg(tf_map_odom);
// 发布map→odom转换
br.sendTransform(map_to_odom);
rate.sleep();
}
return 0;
}
代码解释:
-
初始化ROS节点:
ros::init(argc, argv, "map_odom_broadcaster"); ros::NodeHandle nh;
-
设置TF广播与监听:
tf2_ros::TransformBroadcaster br; tf2_ros::Buffer tfBuffer; tf2_ros::TransformListener tfListener(tfBuffer);
-
读取
base_link→camera_link
静态变换:从参数服务器获取平移与旋转参数,并构建
tf2::Transform
对象。nh.getParam("base_to_camera_x", x); // 依此类推获取其他参数 base_to_camera.setOrigin(tf2::Vector3(x, y, z)); tf2::Quaternion q; q.setX(qx); // 设置旋转 base_to_camera.setRotation(q);
-
计算逆变换
camera_link→base_link
:tf2::Transform camera_to_base = base_to_camera.inverse();
-
主循环中获取并计算转换关系:
- 获取
map→camera_link
与odom→base_link
的当前变换。 - 计算
map→base_link
。 - 计算
map→odom
。 - 发布
map→odom
。
map_to_camera = tfBuffer.lookupTransform("map", "camera_link", ros::Time(0), ros::Duration(0.1)); odom_to_base = tfBuffer.lookupTransform("odom", "base_link", ros::Time(0), ros::Duration(0.1)); tf_map_base = tf_map_camera * camera_to_base; tf_map_odom = tf_map_base * tf_odom_base.inverse(); br.sendTransform(map_to_odom);
- 获取
7.2. odom_base_link_broadcaster
节点代码
视觉里程计节点通常会直接发布odom→base_link
的变换。然而,为了完整性,以下提供一个简化的自定义发布节点示例。
// File: src/odom_base_link_broadcaster.cpp
#include <ros/ros.h>
#include <tf2_ros/transform_broadcaster.h>
#include <geometry_msgs/TransformStamped.h>
#include <geometry_msgs/PoseStamped.h>
int main(int argc, char** argv){
ros::init(argc, argv, "odom_base_link_broadcaster");
ros::NodeHandle nh;
tf2_ros::TransformBroadcaster br;
ros::Rate rate(30.0); // 发布频率为30Hz
while (ros::ok()){
geometry_msgs::TransformStamped odom_to_base;
odom_to_base.header.stamp = ros::Time::now();
odom_to_base.header.frame_id = "odom";
odom_to_base.child_frame_id = "base_link";
// 假设有一个订阅到的位姿信息,这里使用假数据示例
// 实际应用中应从视觉里程计的输出获取
odom_to_base.transform.translation.x = 1.0; // 示例值
odom_to_base.transform.translation.y = 0.0;
odom_to_base.transform.translation.z = 0.0;
odom_to_base.transform.rotation.x = 0.0;
odom_to_base.transform.rotation.y = 0.0;
odom_to_base.transform.rotation.z = 0.0;
odom_to_base.transform.rotation.w = 1.0;
br.sendTransform(odom_to_base);
rate.sleep();
}
return 0;
}
注意:
- 在实际应用中,应将位姿信息从视觉里程计的输出(如ROS话题)订阅进来,并将其转换为
odom→base_link
的TF变换。 - 以上代码中的位姿值为示例,需替换为实际数据。
7.3. 静态转换发布命令
使用static_transform_publisher
发布base_link→camera_link
的静态转换。
命令示例:
rosrun tf2_ros static_transform_publisher 0.1 0.0 0.2 0 0 0 1 base_link camera_link
参数说明:
0.1 0.0 0.2
:平移(x, y, z)。0 0 0 1
:旋转(四元数)。base_link
:父坐标系。camera_link
:子坐标系。
8. 潜在问题与解决方案
在实际实施过程中,可能会遇到以下问题及其对应解决方案:
8.1. 时间同步与延迟
问题:不同传感器或节点的数据可能存在时间戳不同步,导致计算出的转换关系不准确。
解决方案:
- 使用时间同步工具:如
message_filters
中的TimeSynchronizer
,确保订阅到的数据具有相近的时间戳。 - 调整缓冲区大小:在TF监听器中设置合适的缓冲区大小,以容纳可能的时间延迟。
- 统一时间源:确保所有传感器和节点使用统一的时间源,如ROS时钟。
8.2. 坐标系精度与误差积累
问题:由于传感器噪声、标定误差或数值计算误差,坐标变换可能逐渐积累误差,影响定位精度。
解决方案:
- 高精度标定:确保
base_link→camera_link
的标定精度,使用精确的标定工具和方法。 - 滤波与优化:采用滤波算法(如卡尔曼滤波)或图优化方法,减少误差积累。
- 多传感器融合:尽管本案例仅使用视觉里程计,结合其他传感器(如IMU)能进一步提高精度与鲁棒性。
8.3. TF广播频率优化
问题:过高的TF广播频率可能导致计算资源浪费,过低的频率则可能影响系统的实时性。
解决方案:
- 合理设置频率:根据系统需求和硬件性能,选择合适的TF广播频率(如30Hz)。
- 优化代码性能:确保自定义节点高效运行,避免不必要的计算与延迟。
- 分离高频与低频任务:将高频任务与低频任务分离,合理分配资源。
9. 总结
在Ubuntu 20.04与ROS环境下,利用ORBSLAM2实现视觉SLAM时,通过数学推导与ROS TF框架的有效应用,可以构建出map→odom
与odom→base_link
的转换关系。该过程涉及:
- 理解坐标系关系:明确
map
、odom
、base_link
与camera_link
之间的关系。 - 数学推导:利用坐标变换的链式法则,推导出所需的转换关系。
- ROS TF框架应用:通过静态与动态转换的发布,确保系统中各节点能够正确获取坐标变换。
- 开发与集成:编写自定义ROS节点,实现转换关系的计算与发布。
- 问题解决:应对时间同步、精度误差与广播频率等潜在问题,确保系统的稳定性与精确性。
通过本文提供的详细步骤与代码示例,开发者能够在实际项目中实现稳定、精确的视觉SLAM定位与导航系统,进一步推动移动机器人技术的发展。
10. 附录:参数配置示例
为了简化节点的启动与配置,以下提供一个ROS启动文件示例(map_odom_broadcaster.launch
),展示如何同时启动各相关节点与参数。
<!-- File: launch/map_odom_broadcaster.launch -->
<launch>
<!-- 发布 base_link -> camera_link 的静态转换 -->
<node pkg="tf2_ros" type="static_transform_publisher" name="base_to_camera_broadcaster" args="0.1 0.0 0.2 0 0 0 1 base_link camera_link" />
<!-- 启动自定义的 map_odom_broadcaster 节点 -->
<node pkg="map_odom_broadcaster" type="map_odom_broadcaster" name="map_odom_broadcaster" output="screen">
<!-- 可选:传入 base_to_camera 的参数,如需动态配置 -->
<!--
<param name="base_to_camera_x" value="0.1" />
<param name="base_to_camera_y" value="0.0" />
<param name="base_to_camera_z" value="0.2" />
<param name="base_to_camera_qx" value="0.0" />
<param name="base_to_camera_qy" value="0.0" />
<param name="base_to_camera_qz" value="0.0" />
<param name="base_to_camera_qw" value="1.0" />
-->
</node>
<!-- 启动视觉里程计节点,此处以示例节点名称代替 -->
<node pkg="your_visual_odometry_package" type="visual_odometry_node" name="visual_odometry_node" output="screen" />
</launch>
说明:
- 静态转换发布器:发布
base_link→camera_link
的静态转换关系。 - 自定义节点:启动
map_odom_broadcaster
节点,负责计算并发布map→odom
转换。 - 视觉里程计节点:启动视觉里程计节点,发布
odom→base_link
转换。需替换为实际使用的视觉里程计包与节点名称。
启动命令:
roslaunch map_odom_broadcaster map_odom_broadcaster.launch
通过合理配置启动文件,确保各节点的正确启动与参数传递,是系统成功运行的基础。
参考文献:
- Mur-Artal, R., Montiel, J. M. M., & Tardós, J. D. (2015). ORB-SLAM: a Versatile and Accurate Monocular SLAM System. IEEE Transactions on Robotics.
- ROS Wiki. (n.d.). tf2_ros. Retrieved from http://wiki.ros.org/tf2_ros
- ROS Wiki. (n.d.). static_transform_publisher. Retrieved from http://wiki.ros.org/static_transform_publisher