ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

KDL库安装与使用:从零掌握机械臂正逆运动学求解

KDL库安装与使用:从零掌握机械臂正逆运动学求解 搞机器人开发的同学尤其是做ROS那一套的几乎都会碰到KDL库。KDL全称Kinematics and Dynamics Library是Orocos项目里专门做机器人运动学与动力学计算的C库。很多人在实际项目里被正逆运动学卡住自己写矩阵变换写到头秃其实KDL已经把刚体运动学、链式机器人的正解逆解、雅可比计算、动力学递推这些都封装好了。这篇博文就以KDL库的安装与使用为主线从零带你把环境搭起来、跑通第一个正逆解程序并把我这些年编译和使用KDL时踩过的坑一并写出来。无论你是准备做机械臂轨迹规划、做移动机器人腿臂协调还是单纯想深入理解机器人学的坐标变换KDL都是值得花时间研究的库。它不依赖ROS也能独立运行但又是ROS MoveIt等框架默认的运动学后端之一所以学了完全不亏。接下来我直接进入正题从“为什么用它”讲到“装好它”再到“把第一个程序跑起来”最后是常见问题排查全程给出可直接复制的代码和命令。1. KDL库到底是什么为什么值得学1.1 KDL能干什么从运动学到动力学的核心能力KDL的定位很纯粹面向链式机器人的运动学与动力学求解。这里的“链”可以是一条机械臂的连杆序列可以是一条腿的关节序列也可以是任意一个由关节和刚体连接而成的运动树。它最常见的功能就是算正解和逆解。正解好理解告诉它每个关节角度它返回末端在笛卡尔空间下的位姿也就是位置加姿态。逆解反过来给定末端期望位姿它返回一组满足条件的关节角。除了这两件事KDL还提供雅可比矩阵计算、速度/静力传递、质量矩阵和科氏力矩阵计算动力学用的递推牛顿欧拉算法RNEA也在里面。对做轨迹规划和控制的人来说这些基础算法相当于日常工具箱里的螺丝刀和扳手自己造轮子既容易出错又浪费时间KDL直接提供一套久经验证的实现省下来的精力可以放在更高层的逻辑上。1.2 为什么选KDL它在机器人生态里的位置可能有人会问机器人运动学库这么多为什么偏偏要学KDL我的实际感受是KDL在“轻量”“稳定”“生态成熟”这三个维度上非常均衡。第一它底层只依赖Eigen和少量系统库不拖泥带水跨平台编译也简单从Ubuntu到macOS到Windows都能编译。第二它在ROS生态里默认就是MoveIt的运动学后端之一虽然有时候因为解析式逆解表现更好很多人会切换成IKFast或者TRAC-IK但KDL的通用性最强不挑机械臂构型URDF模型进来就能用。第三KDL数据结构本身设计得舒服比如KDL::Frame、KDL::Rotation、KDL::Joint这些类型语义清楚写代码的时候不容易把平移和旋转混在一起。如果你之前用过TF或Eigen的几何模块再看KDL的类型会很有亲切感。这不代表KDL没有缺点比如纯数值迭代的逆解速度不算快奇异点附近容易卡住但作为上手学习和做原理验证的工具它非常合适。1.3 先理清几个概念Chain、Frame、Joint、Solver学习KDL之前我建议先搞懂几个核心概念否则后面看代码很容易懵。KDL::Chain是关节与刚体连接成的运动链方向从头到尾KDL::Joint代表一个关节类型包括旋转关节RotAxis、平移关节TransAxis等KDL::Frame表示空间中的一个刚体变换内部包含旋转矩阵和平移向量KDL::Solver求解器是一族类的统称比如ChainFkSolverPos_recursive是正解求解器ChainIkSolverVel_pinv是速度级逆解求解器ChainIkSolverPos_NR是把速度级逆解包装成位置级逆解的迭代器。整个KDL的使用套路就是建好Chain建好Solver然后把关节数据喂给Solver让它输出结果。想通了这个套路后面看所有KDL示例代码都不会觉得难。2. 安装KDL前的准备工作2.1 环境依赖与版本说明KDL本身不复杂但依赖要注意。最核心的依赖是Eigen3一个模板头文件库负责底层矩阵运算。KDL的Frame本质上就是基于Eigen的矩阵和向量封装出来的。编译时还需要CMake以及可选的Boost某些版本会用到。如果你打算把KDL接到ROS里用建议同时安装urdfdom相关工具方便从URDF模型文件直接生成KDLChain。如果只是学习纯算法不装ROS环境也没关系我自己最初就是直接在纯C工程里编KDL的完全能跑。版本上需要注意KDL官方仓库是orocos/orocos_kinematics_dynamics里面分orocos_kdl和kdl_parser两个大模块。orocos_kdl是核心数值库kdl_parser则是把URDF/XML模型转成KDL树的解析器。一般学习阶段用orocos_kdl就够。2.2 安装方案的横向对比该用哪种方式我总结了三种常用安装方式看需求选择。第一种是apt直接安装二进制包最简单适合只想快速跑通示例的人。命令是sudo apt update sudo apt install liborocos-kdl-devUbuntu 20.04和22.04的软件源里都有这个包装完后头文件在/usr/include/kdl库文件在/usr/lib。第二种是源码编译安装适合需要改KDL源码、调试底层算法或者要打包到自己的工程里的场景我推荐正式项目都用这个。源码编译的好处是可以自己选择版本、加编译选项调试信息也全后面第三节我详细讲。第三种是Python绑定方式通过PyKDL或者ros-noetic-pykdl之类的包适合快速原型验证不想写C的人。我的习惯是学习用源码编译项目用源码编译临时验证小Demo也可以apt。三种方式对比见下表安装方式命令/操作优点缺点适用场景apt二进制包sudo apt install liborocos-kdl-dev快、省事版本可能较旧不能改源码快速验证、学习API源码编译git clone cmake make版本自选、可改源码、可调试稍微麻烦一点正式项目、二次开发Python绑定pip install pykdl_utils 或 apt安装python3-pykdl上手极快、可视化方便无法处理高性能场景原型验证、算法学习2.3 为什么我建议你用源码编译虽然apt一行命令就能装好但我还是建议至少手动源码编译一次。原因有几个。第一apt源里的KDL版本更新往往滞后可能落后官方几个小版本某些新接口或bug修复没有。第二源码编译能让你看到KDL由哪些模块组成CMakeLists逻辑是什么这样以后出了问题你能快速定位。第三实际项目里你很可能需要同时改KDL源码和你的工程代码这时候源码安装是唯一方案。我见过不止一次有人apt装了一个老版本之后想用新API才发现没有最后还得回头重新编译。所以这一趟折腾是值得的。接下来我直接给出源码编译的完整步骤每一处关键配置都会解释为什么这么做。3. 亲手编译安装KDL从源码到可用的完整流程3.1 第一步安装编译依赖在编译KDL之前先把依赖装齐。在Ubuntu/Debian系系统上执行sudo apt update sudo apt install build-essential cmake libeigen3-dev libboost-all-devbuild-essential提供gcc、g、make等基础工具cmake负责构建libeigen3-dev提供矩阵运算库libboost-all-dev是可选依赖但某些功能会用到装上有备无患。Eigen3需要注意一点它是个头文件库不生成.so或.a文件所以CMake里通常是通过Eigen3::Eigen这个导入目标来引用头文件路径的。如果你要基于KDL做URDF模型解析还要装sudo apt install liburdfdom-dev libtinyxml-dev这样后面编译kdl_parser就不会缺头文件。这些依赖装完后可以用一个简单命令验证Eigen版本pkg-config --modversion eigen3如果输出了版本号说明Eigen装好了。如果提示找不到可以通过dpkg -L libeigen3-dev查看头文件具体安装位置一般是在/usr/include/eigen3。3.2 第二步获取KDL源码并切换到稳定版本KDL的官方源码托管在GitHub上仓库地址是https://github.com/orocos/orocos_kinematics_dynamics。克隆仓库的同时我建议加上--depth 1只拉取最新提交省时间。命令如下git clone --depth 1 https://github.com/orocos/orocos_kinematics_dynamics.git cd orocos_kinematics_dynamics进入仓库后你会发现几个目录orocos_kdl是核心库kdl_parser是URDF解析模块本章节不深入python_orocos_kdl是Python绑定doc是文档和示例。因为完整仓库分支很多我建议查看一下当前版本的tag等一等让代码切换到官方发布的稳定版本上。比如git tag git checkout v1.5.1为什么非要切到tag因为master分支上可能存在开发中未完全验证的改动而稳定tag是在发布前经过测试的。我实测过很多次用master分支编译偶尔会遇到API变化用release tag就稳定得多。如果你不知道哪个版本合适选最新的release tag基本不会错。3.3 第三步CMake配置、编译和安装进入orocos_kdl目录执行cd orocos_kdl mkdir build cd build cmake .. -DCMAKE_BUILD_TYPERelease -DCMAKE_INSTALL_PREFIX/usr/local这里解释一下两个关键参数。CMAKE_BUILD_TYPERelease表示启用编译器优化Release模式下KDL的数值计算速度快很多Debug模式适合调试但性能明显下降。CMAKE_INSTALL_PREFIX/usr/local表示头文件安装到/usr/local/include库文件安装到/usr/local/lib这是Linux系统上第三方库的默认位置CMake和pkg-config默认都会搜索这个路径。如果你希望装到自己某个工程目录下也可以修改成别的路径但后面所有依赖它的工程都要手动指定路径会比较麻烦所以我建议非特殊情况就用/usr/local。配置完成后开始编译make -j$(nproc)-j$(nproc)会让make使用所有CPU核心并行编译速度最快。第一次编译KDL很快因为它的源码量不大几十秒就能编完。编译完成后安装sudo make install这一步会把头文件、库文件、CMake配置文件拷贝到系统目录。安装完成后建议执行一下动态链接库刷新让系统能找到新安装的库sudo ldconfig3.4 第四步验证安装是否成功安装完成后怎么确认没问题最简单的办法直接用cmake写一个最小工程调用KDL的头文件。我先创建一个测试目录mkdir -p ~/kdl_test cd ~/kdl_test然后写一个CMakeLists.txtcmake_minimum_required(VERSION 3.10) project(kdl_test) find_package(OROCOS_KDL REQUIRED) add_executable(kdl_test main.cpp) target_link_libraries(kdl_test OROCOS_KDL::OROCOS_KDL)再写一个main.cpp#include kdl/chain.hpp #include iostream int main() { KDL::Chain chain; std::cout KDL works, chain has chain.getNrOfJoints() joints. std::endl; return 0; }然后执行cmake .. make ./kdl_test如果输出“KDL works, chain has 0 joints.”说明库和CMake配置都正常。如果cmake报找不到OROCOS_KDL可能是CMake没有在/usr/local/lib/cmake下找到配置文件可以检查一下/usr/local/lib/cmake/OROCOS_KDL是否存在或者用find_package时加路径cmake .. -DOROCOS_KDL_DIR/usr/local/lib/cmake/OROCOS_KDL到这一步KDL已经顺利装好了。接下来写第一个真正的运动学程序。4. 第一个KDL程序从机械臂模型到正逆解4.1 定义一个简单的两关节机械臂为了讲清楚KDL的使用流程我用一个非常简单的2R平面机械臂举例。这个臂有两个旋转关节都在Z轴方向旋转连杆长度分别是1.0米和0.8米。虽然简单但麻雀虽小五脏俱全正解逆解的完整逻辑都能演示出来。创建一份新的C文件命名为fk_ik_demo.cpp。核心代码如下#include kdl/chain.hpp #include kdl/chainfksolver.hpp #include kdl/chainfksolverpos_recursive.hpp #include kdl/chainiksolvervel_pinv.hpp #include kdl/chainiksolverpos_nr.hpp #include kdl/frames.hpp #include kdl/joint.hpp #include kdl/rigidbodyinertia.hpp #include kdl/segment.hpp #include iostream int main() { // 1. 构建运动链 KDL::Chain chain; // 第一个关节绕Z轴旋转 chain.addSegment(KDL::Segment(KDL::Joint(joint1, KDL::Joint::RotZ), KDL::Frame(KDL::Vector(1.0, 0.0, 0.0)))); // 第二个关节绕Z轴旋转 chain.addSegment(KDL::Segment(KDL::Joint(joint2, KDL::Joint::RotZ), KDL::Frame(KDL::Vector(0.8, 0.0, 0.0)))); std::cout Chain number of joints: chain.getNrOfJoints() std::endl; std::cout Chain number of segments: chain.getNrOfSegments() std::endl; return 0; }这里要理解Segment和Joint的区别。KDL::Segment表示“关节连杆”的组合Joint描述关节的运动方式而Segment中给的Frame描述的是关节运动之后连杆坐标系的固定偏移。比如第一个Segment里的Frame(KDL::Vector(1.0, 0.0, 0.0))表示关节1旋转后从关节1坐标系向X轴平移1米到达连杆末端连杆末端就是关节2的安装位置。第二个Segment的偏移量0.8米代表第二根连杆的长度。这种表达方式非常直接和实际机械臂的D-H建模可以对应起来只是KDL用的是“关节偏移量”这种更直观的组合。4.2 正运动学求解给定关节角求末端位姿正运动学是KDL里最基础也最不容易出错的模块。把上一节的代码扩展一下引入正解求解器。代码完整如下#include kdl/chain.hpp #include kdl/chainfksolverpos_recursive.hpp #include kdl/frames.hpp #include kdl/joint.hpp #include kdl/segment.hpp #include iostream int main() { KDL::Chain chain; chain.addSegment(KDL::Segment(KDL::Joint(joint1, KDL::Joint::RotZ), KDL::Frame(KDL::Vector(1.0, 0.0, 0.0)))); chain.addSegment(KDL::Segment(KDL::Joint(joint2, KDL::Joint::RotZ), KDL::Frame(KDL::Vector(0.8, 0.0, 0.0)))); // 创建正解求解器 KDL::ChainFkSolverPos_recursive fk_solver(chain); // 输入关节角假设两个关节都是30度 KDL::JntArray q(chain.getNrOfJoints()); q(0) 30.0 * M_PI / 180.0; q(1) 30.0 * M_PI / 180.0; // 输出末端位姿 KDL::Frame end_frame; int ret fk_solver.JntToCart(q, end_frame); if (ret 0) { std::cout FK solved successfully. std::endl; std::cout Position: x end_frame.p.x() , y end_frame.p.y() , z end_frame.p.z() std::endl; std::cout Rotation matrix: std::endl; std::cout end_frame.M std::endl; } else { std::cout FK failed, error code: ret std::endl; } return 0; }这一段里KDL::JntArray是关节数组类型本质上是一个向量。注意q的索引和Chain里Segment的对应关系第0个元素对应第一个关节第1个元素对应第二个关节。所有角度单位都是弧度不是度。我自己第一次用的时候直接填了30结果末端位置完全不对后来才意识到要转弧度。JntToCart是“关节空间转笛卡尔空间”的意思也就是正解。返回值ret如果大于等于0代表计算成功否则返回负数错误码。用30度加30度计算末端x坐标约1.5588y约0.9这个结果可以通过平面几何手动验证有兴趣可以自己画个图算一下。4.3 逆运动学求解从末端位姿反推关节角逆运动学比正解复杂得多。KDL提供的是数值迭代解法核心思路是先求速度级逆解再用牛顿-拉夫逊迭代逼近位置级逆解。代码如下#include kdl/chain.hpp #include kdl/chainfksolverpos_recursive.hpp #include kdl/chainiksolvervel_pinv.hpp #include kdl/chainiksolverpos_nr.hpp #include kdl/frames.hpp #include kdl/joint.hpp #include kdl/segment.hpp #include iostream int main() { KDL::Chain chain; chain.addSegment(KDL::Segment(KDL::Joint(joint1, KDL::Joint::RotZ), KDL::Frame(KDL::Vector(1.0, 0.0, 0.0)))); chain.addSegment(KDL::Segment(KDL::Joint(joint2, KDL::Joint::RotZ), KDL::Frame(KDL::Vector(0.8, 0.0, 0.0)))); // 末端期望位姿 KDL::Frame desired; double theta1 30.0 * M_PI / 180.0; double theta2 30.0 * M_PI / 180.0; double x 1.0 * cos(theta1) 0.8 * cos(theta1 theta2); double y 1.0 * sin(theta1) 0.8 * sin(theta1 theta2); desired.p KDL::Vector(x, y, 0.0); desired.M KDL::Rotation::RotZ(theta1 theta2); // 初始关节角 KDL::JntArray q_init(chain.getNrOfJoints()); q_init(0) 0.0; q_init(1) 0.0; // 速度级逆解 位置级逆解 KDL::ChainIkSolverVel_pinv ik_solver_vel(chain); KDL::ChainIkSolverPos_NR ik_solver_pos(chain, ik_solver_vel, 100, 1e-6); KDL::JntArray q_result(chain.getNrOfJoints()); int ret ik_solver_pos.CartToJnt(q_init, desired, q_result); if (ret 0) { std::cout IK solved successfully. std::endl; std::cout q1 q_result(0) * 180.0 / M_PI deg std::endl; std::cout q2 q_result(1) * 180.0 / M_PI deg std::endl; } else { std::cout IK failed, error code: ret std::endl; } return 0; }CartToJnt是关节空间逆解的入口函数参数分别是初始猜测关节角、期望末端位姿、输出关节角。注意我必须强调初始值很重要KDL用的是数值迭代初始值选得不好可能收敛到别的分支甚至直接发散。比如我们这个例子初始值设成(0,0)最终IK大概率会收敛回(30,30)附近。但如果初始值设成(150度, 150度)数值迭代就可能跳到另一个解上这在实际项目中是常态。对于真实机械臂你应该把上一时刻的关节角度作为当前时刻IK的初值这样既能提高收敛速度也能保证解的连续性。4.4 怎么验证逆解结果的正确性逆解算出来的关节角对不对不能只看返回值。正确做法是把结果再代回正解看看末端位姿和期望是否一致。很多初学者忽略这一步结果末端位置差了一大截还找不到原因。我提供一个简单的验证代码片段。在得到q_result之后再次调用正解KDL::ChainFkSolverPos_recursive fk_solver(chain); KDL::Frame check_frame; fk_solver.JntToCart(q_result, check_frame); double pos_err (check_frame.p - desired.p).Norm(); std::cout Position error: pos_err std::endl;如果pos_err在1e-5以下说明逆解结果可信。如果误差很大大概率是迭代没有收敛需要调整迭代次数、容差或者换一个更好的初值。这个“逆解之后必须正解验证”的习惯在我做过的所有机器人项目里都是标配强烈建议你也养成。4.5 更复杂的链从URDF模型构建KDL手动用Segment和Joint搭链只适合学习。真正的机械臂模型都是URDF文件KDL提供了一个解析工具叫kdl_parser。它可以读取URDF文件里的link和joint然后生成KDL::Chain或者KDL::Tree。基本用法如下#include kdl_parser/kdl_parser.hpp #include kdl/tree.hpp #include kdl/chain.hpp #include urdf_model/model.h #include iostream int main() { KDL::Tree tree; if (!kdl_parser::treeFromFile(robot.urdf, tree)) { std::cerr Failed to parse URDF file. std::endl; return -1; } std::cout Tree root: tree.getRootSegment()-first std::endl; KDL::Chain chain; // 从base_link提取到tool0的链 if (!tree.getChain(base_link, tool0, chain)) { std::cerr Failed to get chain. std::endl; return -1; } std::cout Chain joints: chain.getNrOfJoints() std::endl; return 0; }treeFromFile函数的第一个参数是URDF文件路径第二个参数是输出的KDL::Tree。getChain的两个字符串分别是基座link名和末端link名。这个方法在实际项目里非常常用因为它省去了手动建模的麻烦而且只要URDF对结构信息绝对不会错。我建议你在学习阶段可以先用简单机械臂手动搭链实际项目里再切换到kdl_parser。5. 常见问题与排查经验5.1 编译阶段常见错误速查表下面这张表是我多年使用KDL过程中遇到的典型问题遇到类似报错可以对照排查。报错信息原因解决方法fatal error: kdl/chain.hpp: No such file or directory头文件路径缺失确认安装路径将/usr/local/include加入CMake的include目录CMake Error: Could not find ORTOCOS_KDLCMake找不到库配置检查/usr/local/lib/cmake/OROCOS_KDL是否存在用-DOROCOS_KDL_DIR指定路径undefined reference to KDL::ChainFkSolverPos_recursive...链接阶段没链接库在CMake里target_link_libraries显式链接OROCOS_KDL::OROCOS_KDL找不到liborocos-kdl.so.1.5动态库路径未刷新执行sudo ldconfig或将/usr/local/lib添加到/etc/ld.so.conf.d/error: ‘M_PI’ was not declared in this scopeM_PI宏在C里默认不可见在文件顶部#define _USE_MATH_DEFINES或者传入头文件cmath/numbersEigen版本不兼容报错系统Eigen版本过旧apt安装最新libeigen3-dev或指定EIGEN3_INCLUDE_DIR手动指向新版Eigen头文件5.2 链接KDL库时最容易踩的坑链接阶段的问题比编译阶段更隐蔽我单独拿出来说。KDL在编译成功后会同时生成动态库liborocos-kdl.so和静态库liborocos-kdl.a。如果你用CMake的find_package(OROCOS_KDL)正常情况下一切自动搞定。但如果你喜欢直接写Makefile或者手动g编译就要小心。一个常见错误是链接顺序问题比如g main.cpp -o demo -lorocos-kdl这里-lorocos-kdl放在main.cpp后面是没问题的因为处理的是动态库。但如果你链接的是静态库命令就必须把库放在源文件之后g main.cpp -o demo -Wl,-Bstatic -lorocos-kdl -Wl,-Bdynamic否则链接器在处理符号时还没加载库里的定义就会报undefined reference。另外KDL依赖EigenEigen是header-only库所以不需要额外链接但如果是Debug版本KDL还可能需要链接Boost。我建议能用CMake就用CMake手动写g命令在依赖多的时候就是给自己挖坑。5.3 逆解不收敛的排查思路逆解不收敛是项目中最常被问的问题。我总结一套排查流程按顺序检查基本能定位问题。第一确认期望位姿在机械臂工作空间内。如果末端点离基座太远或者太靠近奇异点数值迭代很难收敛。第二检查初始值和期望解之间的距离。迭代法对初值敏感初值距离真解太远会大概率发散。第三检查关节限位。KDL默认不检查关节限位如果迭代过程跑到了限位外结果会很容易错乱。第四检查容差和迭代次数。ChainIkSolverPos_NR的构造参数里100是最大迭代次数1e-6是收敛容差。对快速原型验证这两个参数足够但对高精度任务可以适当增加迭代次数到500容差到1e-8。第五如果以上都不行换一种IK策略比如先做速度级逆解再自己积分或者使用IKFast、TRAC-IK这类更快的库对比。5.4 使用过程中的性能问题KDL的逆解是数值迭代每一轮迭代都要计算雅可比矩阵和矩阵的伪逆所以耗时会比解析解高一个数量级。实测在普通笔记本CPU上一个6轴机械臂的KDL逆解大概耗时1到5毫秒看迭代次数和初值。这个性能对离线轨迹规划足够但对高频在线控制如1kHz就有压力。我的经验是做实时控制时不要每周期都调KDL可以先用KDL离线算好一组关节角轨迹或者把KDL的结果作为种子配合更快的解析解库使用。KDL也支持OpenMP加速部分计算但我实测效果一般关键瓶颈还是在伪逆矩阵计算上。5.5 一个隐藏的大坑旋转表示方法的混淆KDL内部用旋转矩阵表示姿态但API里有些函数会接受欧拉角、轴角等参数。我见过很多人把欧拉角的顺序搞错导致结果差之毫厘谬以千里。KDL::Rotation::RPY构造函数的参数顺序是滚转roll、俯仰pitch、偏航yaw并且是固定轴顺序。KDL::Rotation::EulerZYX则对应另一种旋转顺序。你在和ROS的quaternion互转时一定要先明确自己手里的姿态数据是什么表示再调用对应的转换函数。尤其是从MoveIt拿goal pose时里面是quaternion如果不转成Rotation直接用Frame的构造就会出错。这个坑我自己踩过好几回而且这类问题编译不会报错只会在运行结果上神秘地差出某个角度排查起来特别费劲。6. 进阶使用从单链走向树、从纯运动学走向动力学6.1 KDL::Tree双足、多臂等分支结构KDL不仅能处理单条链也能处理树形结构。双足机器人、四足机器人、多臂机器人这类结构本质上是树因为从基座出发会分出多个分支。KDL::Tree和KDL::Chain的区别就在这里Chain只有一条从头到尾的路径Tree可以有任意分支。使用Tree时正解可以通过KDL::TreeFkSolverPos_recursive求解速度级逆解不太适合用Tree通常是对每个分支单独取链。如果你做的是足式机器人建议了解一下因为每个腿单独建Chain后再配合KDL的Tree做整体正解是比较常见的架构。不过要注意的是KDL的Tree只负责运动学树结构动力学相关的Tree接口有限做整机动力学仿真时还是推荐Pinocchio、RBDL这类更专门的库。6.2 动力学求解KDL不只有运动学KDL的动力学能力也值得提。它提供ChainDynParam和ChainDynSolver等接口可以计算质量矩阵、科氏力/离心力矩阵、重力项以及正向动力学和逆向动力学。逆向动力学的典型用途是计算期望轨迹对应的关节力矩。KDL实现的逆动力学基于递推牛顿欧拉RNEA复杂度O(n)效率不错。使用前要正确设置每个Segment的惯性参数KDL::RigidBodyInertia。这个过程比较繁琐容易出错。我第一次用的时候就是惯性张量给错了结果逆动力学求出来的力矩完全离谱。需要先对单轴运动验证确认每个惯性参数含义都对再上多轴计算。这个建议同样适用于其他动力学库惯性参数几乎是所有动力学算法的灵魂。6.3 和ROS生态协同MoveIt、tf2、kdl_parser的组合在ROS里用KDL基本组合是用kdl_parser从URDF生成Tree/Chain用tf2处理坐标变换用MoveIt做上层规划底层运动学调用KDL插件。MoveIt的Kinematics插件机制允许你配置不同的运动学求解器默认是KDL也可以改成TRAC-IK。如果你做仿真和实际机械臂都在ROS里建议把KDL的运动学结果和实际反馈的关节角做对比一旦发现偏差优先排查URDF模型参数是否正确。很多调试噩梦都源于URDF里某个link的xyz或rpy写错KDL本身是好的模型带歪了。把这些工具组合使用之后从模型到规划到执行的链路就通了KDL在其中的角色就像一台精密的计算器模型精确、输入正确输出就干净利落。6.4 Python绑定用PyKDL做快速原型验证如果嫌C编译太麻烦PyKDL是个很好的选择。在Ubuntu上可以直接sudo apt install python3-pykdl在ROS环境里也可以用ros-noetic-pykdl。导入方式是import PyKDL as kdl chain kdl.Chain() chain.addSegment(kdl.Segment(kdl.Joint(kdl.Joint.RotZ), kdl.Frame(kdl.Vector(1.0, 0.0, 0.0))))PyKDL的接口几乎和C一一对应写算法原型非常方便。我经常先用Python调通逻辑再翻译成C开发效率提升很多。不过性能上Python绑定会慢一些实时控制还是要回C。7. 我的几点实操心得最后分享几个个人的经验不算总结就当作避坑补充。第一个体会是KDL虽然是“老库”但它的核心算法至今依然可靠我做过六轴机械臂的轨迹规划、七轴冗余臂的逆解测试、双足腿部的运动学分析KDL都能胜任。第二个经验是如果你在项目里遇到逆解偶尔不稳定的情况不要急着质疑KDL先检查是不是初值设置不合理或者期望位姿太靠近奇异点。大多数情况下问题出在上层策略而不是KDL本身。第三个建议是遇到矩阵变换问题多用KDL::Frame自带的调试输出它打印旋转矩阵时非常清晰把每一步的中间量打出来看比闷头读代码快得多。最后一个实用技巧在CMake里如果find_package找不到OROCOS_KDL先看/usr/local/lib/cmake/OROCOS_KDL目录下有没有OROCOS_KDLConfig.cmake如果没有很可能是你的KDL版本太老安装路径不对重新检查CMake安装前缀即可。这套流程我已经走过很多遍照着执行基本二十分钟内能把KDL跑起来。
返回列表