kdl_parser 源码详细分析

kdl_parser 源码详细分析

工作区路径:/home/cp/work2/ros2Learn/ros2_humble/src/ros/kdl_parser
版本:2.6.4,许可证 BSD。仓库含两个包:kdl_parser(C++)与 kdl_parser_py(Python)。

kdl_parser 的职责很单一:把 URDF 机器人模型 转成 Orocos KDL 的 KDL::Tree,供正/逆运动学、动力学求解使用。它不做 URDF 解析本身(交给 urdf / urdf_parser_py),也不发布 TF(那是 robot_state_publisher 的事)。


1. 总体认识

1.1 在 ROS 2 栈中的位置

URDF XML / robot_descriptionurdf::Model<br/>urdf_parser_pykdl_parserKDL::Tree / PyKDL.Treerobot_state_publisherKDL 求解器<br/>Fk/IK/Id 等
语言 依赖 主要消费者(本工作区)
kdl_parser C++14 urdf, orocos_kdl, rcutils robot_state_publisher
kdl_parser_py Python urdfdom_py, python_orocos_kdl 测试/脚本(ROS 2 中较少直接使用)

2. 仓库结构

1
2
3
4
5
6
7
8
9
10
11
12
kdl_parser/
├── kdl_parser/ # C++ 库(ament_cmake)
│ ├── include/kdl_parser/
│ │ ├── kdl_parser.hpp # 公开 API(3 个函数)
│ │ └── visibility_control.hpp
│ ├── src/
│ │ ├── kdl_parser.cpp # 核心转换逻辑(~210 行)
│ │ └── check_kdl_parser.cpp # 命令行调试工具
│ └── test/ # gtest + PR2/r2d2 URDF
├── kdl_parser_py/ # Python 包
│ └── kdl_parser_py/urdf.py # 与 C++ 平行的转换逻辑
└── README.md

体量很小:C++ 核心仅 一个源文件,公开 API 3 个函数


3. C++ API

1
2
3
4
5
6
7
8
KDL_PARSER_PUBLIC
bool treeFromFile(const std::string & file, KDL::Tree & tree);

KDL_PARSER_PUBLIC
bool treeFromString(const std::string & xml, KDL::Tree & tree);

KDL_PARSER_PUBLIC
bool treeFromUrdfModel(const urdf::ModelInterface & robot_model, KDL::Tree & tree);
函数 输入 流程
treeFromFile URDF 文件路径 读文件 → treeFromString
treeFromString URDF XML 字符串 urdf::Model::initStringtreeFromUrdfModel
treeFromUrdfModel 已解析的 URDF 模型 递归构建 KDL::Tree

4. 核心转换逻辑(C++)

4.1 数据流

1
2
3
4
5
6
7
8
treeFromUrdfModel
├─ tree = KDL::Tree(root_link_name) # 仅设根名,根 link 不建 segment
├─ 警告:根 link 有 inertia(KDL 不支持)
└─ 对每个 root 的子 link 递归 addChildrenToTree
├─ toKdl(inertial) → RigidBodyInertia
├─ toKdl(parent_joint) → KDL::Joint
├─ Segment(link_name, joint, origin, inertia)
└─ tree.addSegment(segment, parent_link_name)

4.2 URDF → KDL 类型映射

位姿/几何

URDF KDL 实现
Vector3 KDL::Vector 直接拷贝 x,y,z
Rotation (四元数) KDL::Rotation Rotation::Quaternion(x,y,z,w)
Pose KDL::Frame Frame(R, p)

关节toKdl(urdf::JointSharedPtr)

URDF Joint 类型 KDL Joint 类型 说明
FIXED Joint::None 固定关节
REVOLUTE Joint::RotAxis 旋转轴 = F_parent_jnt.M * axis
CONTINUOUS Joint::RotAxis 同 revolute,无限位
PRISMATIC Joint::TransAxis 平移轴
其他(floating/planar 等) Joint::None 警告后当 fixed 处理

关节原点与轴的处理:

1
2
3
4
5
6
7
8
KDL::Joint toKdl(urdf::JointSharedPtr jnt)
{
KDL::Frame F_parent_jnt = toKdl(jnt->parent_to_joint_origin_transform);
// ...
case urdf::Joint::REVOLUTE: {
KDL::Vector axis = toKdl(jnt->axis);
return KDL::Joint(jnt->name, F_parent_jnt.p, F_parent_jnt.M * axis, KDL::Joint::RotAxis);
}

即:关节锚点在父 link 坐标系中的位置为 F_parent_jnt.p,旋转/平移轴在父 link 系下为 F_parent_jnt.M * axis(与 KDL Segment 约定一致)。

惯性toKdl(urdf::InertialSharedPtr)

这是转换中最 subtle 的部分:

1
2
3
4
5
6
7
8
// URDF 惯性矩阵在 inertial 参考系;KDL 要求在 link 参考系
KDL::RotationalInertia urdf_inertia = ...;
// 用 RigidBodyInertia 运算符 workaround 做旋转
KDL::RigidBodyInertia kdl_inertia_wrt_com_workaround =
origin.M * KDL::RigidBodyInertia(0, KDL::Vector::Zero(), urdf_inertia);
KDL::RotationalInertia kdl_inertia_wrt_com =
kdl_inertia_wrt_com_workaround.getRotationalInertia();
return KDL::RigidBodyInertia(kdl_mass, kdl_com, kdl_inertia_wrt_com);
  • 质量、质心:URDF 与 KDL 都在 link 坐标系 下表达 COM
  • 惯性张量:URDF 在 inertial 原点坐标系;KDL 需要 相对 COM 且在 link 系 的张量
  • 通过 origin.M 旋转张量,再提取 getRotationalInertia()

test_inertia_rpy.cpp递归牛顿-欧拉逆动力学 对比两种 URDF 惯性描述,验证转换正确性。

4.3 KDL Tree 与 URDF 的对应关系

URDF 概念 KDL 概念
link(除根外) Segment(含 name、joint、tip frame、inertia)
joint 挂在 子 segment 上的 KDL::Joint
根 link 仅作为 Tree 根名;不生成带惯性的 segment
固定关节 Joint::None

测试 URDF test_robot.urdf 使用 dummy_link 技巧:根 link 无 inertia,真实 base 通过 fixed joint 挂在 dummy 下,以满足 KDL 限制:

1
2
3
4
5
6
<link name="dummy_link"/>
...
<joint name="dummy_to_base" type="fixed">
<parent link="dummy_link"/>
<child link="base_link"/>
</joint>

5. Python 包(kdl_parser_py)

路径:kdl_parser_py/kdl_parser_py/urdf.py

与 C++ 逻辑平行,差异如下:

方面 C++ Python
URDF 解析 urdf::Model urdf_parser_py.urdf.URDF
KDL 绑定 orocos_kdl (C++) PyKDL
返回值 bool (ok, tree) 元组
位姿 四元数 Rotation RPY + XYZpose.rpy, pose.xyz
树遍历 child_links 递归 child_map / parent_map / link_map
treeFromParam 读 ROS 参数服务器(ROS 1 风格,ROS 2 中通常不用)

关节映射(Python 显式处理更多类型):

1
2
3
4
5
6
7
8
9
type_map = {
'fixed': fixed,
'revolute': rotational,
'continuous': rotational,
'prismatic': translational,
'floating': fixed,
'planar': fixed,
'unknown': fixed,
}

注意:Python 版 floating/planar 静默变为 fixed;C++ 版对未知类型会 RCUTILS_LOG_WARN


6. 构建与依赖

kdl_parser(C++)

1
2
3
4
5
6
7
add_library(${PROJECT_NAME} src/kdl_parser.cpp)
target_link_libraries(${PROJECT_NAME} PUBLIC
orocos-kdl
urdfdom_headers::urdfdom_headers)
target_link_libraries(${PROJECT_NAME} PRIVATE
rcutils::rcutils
urdf::urdf)
  • orocos_kdl_vendor:ROS 2 将 Orocos KDL 以 vendor 形式打进工作区
  • urdf:ROS 2 的 URDF C++ 解析库(基于 urdfdom)
  • 日志:RCUTILS_LOG_*_NAMED("kdl_parser", ...)

7. ROS 2 中的主要消费者:robot_state_publisher

1
2
3
4
5
6
7
8
9
10
11
KDL::Tree RobotStatePublisher::parseURDF(const std::string & urdf_xml, urdf::Model & model)
{
if (!model.initString(urdf_xml)) {
throw std::runtime_error("Unable to initialize urdf::model from robot description");
}
KDL::Tree tree;
if (!kdl_parser::treeFromUrdfModel(model, tree)) {
throw std::runtime_error("Failed to extract kdl tree from robot description");
}
return tree;
}

robot_state_publisher 用 KDL Tree 做:

  1. 遍历 segment,区分 fixed可动 关节
  2. 结合 /joint_states正向运动学,发布 TF
  3. mimic 关节 在 RSP 里单独处理(kdl_parser 不解析 mimic)

即:kdl_parser 提供 kinematic tree 结构;RSP 负责运行时状态与 TF。


8. 工具与测试

文件 用途
check_kdl_parser.cpp CLI:解析 URDF 并打印 segment 树
test_kdl_parser.cpp 验证 r2d2 URDF:8 joints、16 segments、惯性数值
test_inertia_rpy.cpp 两种 inertia 描述的动力学等价性
test/*.xml PR2 等复杂模型(部分应解析失败)
kdl_parser_py/test/ Python 侧 rostest

9. 限制与注意事项

限制 说明
根 link 惯性 KDL 根 segment 不支持 inertia;需 dummy fixed link
floating / planar 转为 fixed,多自由度关节不被 KDL Tree 表达
mimic 关节 不在 kdl_parser 处理;由上层(RSP)扩展
闭环机构 URDF 为树;闭环需额外约束(KDL 不直接支持)
语义 vs 几何 material/visual/collision 被忽略,只取 kinematics + inertia
双包一致性 C++ 用四元数、Python 用 RPY,极端姿态下需留意数值差异

10. 与相关包对比

输入 输出 用途
urdf XML urdf::Model 对象图 解析、修改 URDF
kdl_parser URDF 模型 KDL::Tree 运动学/动力学计算
robot_state_publisher URDF + joint_states TF 运行时状态
tf2 几何变换 TF 缓冲 坐标变换查询

11. 推荐阅读顺序

  1. 公开 APIkdl_parser.hpp — 三个入口函数
  2. 转换细节kdl_parser.cpptoKdl 系列 + addChildrenToTree
  3. 测试 URDFtest/test_robot.urdf — dummy link 模式
  4. 惯性验证test/test_inertia_rpy.cpp
  5. ROS 2 集成robot_state_publisher/src/robot_state_publisher.cppparseURDF / addChildren
  6. Python 对照kdl_parser_py/urdf.py

12. 设计特点小结

特点 说明
薄适配层 几乎只做 URDF→KDL 字段映射,无独立状态机
单文件核心 维护成本低,行为清晰
递归建树 与 URDF 树结构一一对应
惯性变换 正确处理 inertial frame → link frame
双语言实现 C++ 为主路径;Python 供脚本/原型
ROS 解耦 库本身不依赖 rclcpp(除日志用 rcutils)

如果你希望,我可以把本文写入 ros2doc/ros/kdl_parser 源码详细分析.md,或继续分析 robot_state_publisher 如何用 KDL Tree + joint_states 计算 TF 的完整链路。

文章互动

阅读 --

留言

0 条留言

正在加载留言…