4. 加载机器人模型:使用 RobotModelLoader 加载 URDF
好,咱们进入第四章。前面几章我们把 MoveIt 的架构和基本概念捋了一遍,从这章开始,要真正动手操作了。
加载机器人模型,是所有运动规划的前提。你想想看,MoveIt 连你的机器人长什么样、有几个关节、连杆之间怎么连的都不知道,它怎么给你算轨迹?所以这一章,我们就来搞定这件事。
4.1 为什么需要 RobotModelLoader?
ROS 里描述机器人用的标准格式是 URDF(Unified Robot Description Format)。但 URDF 文件本身只是一堆 XML 标签,MoveIt 需要把它解析成能在内存里高效查询的数据结构。这个解析工作,就交给 RobotModelLoader。
我个人习惯把它叫做「模型加载器」。它的核心任务就两个:
- 从参数服务器上读取 URDF 和 SRDF
- 构建出
robot_model::RobotModel对象
说白了,它就是一把钥匙,帮你打开机器人模型的大门。
4.2 加载流程:从 URDF 到 RobotModel
加载过程其实不复杂,我画了一张流程图,你看一眼就明白了。
流程很清晰:参数服务器提供原始数据 → RobotModelLoader 解析 → 生成 RobotModel → 进一步拆解出 JointModelGroup 和 KinematicTree。
4.3 代码实战:加载并打印模型信息
光说不练假把式。我们直接写一段 C++ 代码,把模型加载出来,然后打印它的关节和连杆树。
#include <moveit/robot_model_loader/robot_model_loader.h>
#include <moveit/robot_model/robot_model.h>
#include <moveit/robot_state/robot_state.h>
int main(int argc, char** argv)
{
// 初始化 ROS 节点
ros::init(argc, argv, "load_robot_model_demo");
ros::NodeHandle nh;
// 创建 RobotModelLoader
robot_model_loader::RobotModelLoader loader("robot_description");
// 加载模型
robot_model::RobotModelPtr model = loader.getModel();
if (!model)
{
ROS_ERROR("模型加载失败!");
return -1;
}
ROS_INFO("模型加载成功!");
ROS_INFO("模型名称: %s", model->getName().c_str());
ROS_INFO("关节数量: %zu", model->getJointModels().size());
ROS_INFO("连杆数量: %zu", model->getLinkModels().size());
// 打印所有关节名称
ROS_INFO("=== 关节列表 ===");
for (const auto& joint : model->getJointModels())
{
ROS_INFO("关节: %s, 类型: %d",
joint->getName().c_str(),
joint->getType());
}
// 打印所有连杆名称
ROS_INFO("=== 连杆列表 ===");
for (const auto& link : model->getLinkModels())
{
ROS_INFO("连杆: %s", link->getName().c_str());
}
// 获取根连杆
const auto* root_link = model->getRootLink();
if (root_link)
{
ROS_INFO("根连杆: %s", root_link->getName().c_str());
}
return 0;
}
loader.getModel() 返回的是 RobotModelPtr,它是一个共享指针。只要这个指针还在作用域内,模型数据就不会被释放。
4.4 理解关节与连杆树
模型加载完之后,我们得知道它内部是怎么组织的。机器人模型本质上是一棵树——连杆是节点,关节是边。
举个例子,一个简单的六轴机械臂,它的树结构大概长这样:
每个关节连接两个连杆,从根连杆开始,一层层往下延伸。MoveIt 内部用 KinematicTree 来管理这个结构,你可以通过 getParent() 和 getChild() 方法在树中上下遍历。
4.5 关节类型速查表
MoveIt 支持多种关节类型,每种类型的运动学含义不同。我整理了一个表格,方便你对照:
| 关节类型 | 枚举值 | 自由度 | 典型用途 |
|---|---|---|---|
| REVOLUTE | 1 | 1(旋转) | 机械臂关节,有限角度 |
| PRISMATIC | 2 | 1(平移) | 直线导轨、伸缩臂 |
| FLOATING | 3 | 6 | 移动基座、无人机 |
| PLANAR | 4 | 3 | AGV 在平面内运动 |
| FIXED | 5 | 0 | 焊接点、固定连接 |
4.6 避坑指南:加载失败的常见原因
模型加载失败,是初学者最容易碰到的坑。我根据自己的经验,总结了几个常见原因:
- 参数名称不对: 默认参数名是
robot_description,如果你改了名字,记得在构造函数里传进去。 - URDF 语法错误: 用
check_urdf工具先验证一下文件。 - 缺少依赖包: 比如用了
mesh标签但没安装对应的 mesh 文件。 - SRDF 不匹配: URDF 更新了但 SRDF 没同步,导致关节组定义失效。
roslaunch urdf_tutorial display.launch model:=your_robot.urdf,看看 Rviz 里能不能正常显示。如果 Rviz 都显示不了,那肯定是 URDF 本身有问题。
4.7 获取模型信息的实用方法
加载完模型之后,我们经常需要查询一些信息。下面这几个方法,我在项目中几乎天天用:
// 获取指定关节组的模型
const robot_model::JointModelGroup* group =
model->getJointModelGroup("arm_group");
// 获取关节组的末端执行器
const std::string& end_effector =
group->getEndEffectorName();
// 获取关节的极限值
const robot_model::JointModel::Bounds& bounds =
joint->getVariableBounds();
// 获取连杆的变换矩阵
const Eigen::Isometry3d& transform =
link->getJointOriginTransform();
嗯,这里要注意一点:getJointModelGroup 返回的是指针,如果找不到对应的组,它会返回 nullptr。所以调用之前,最好先判空。
我个人习惯在加载完模型之后,先打印一遍完整的关节树,确认结构对不对。你想想看,如果连树结构都是错的,后面做运动规划肯定跑偏。