简介:DH2URDF是一款面向机器人开发者和ROS学习者的JavaScript工具,用于将Denavit-Hartenberg参数表自动转换为统一机器人描述格式(URDF),省去手工编写机器人模型的繁琐过程。压缩包共9个文件,以JavaScript脚本为核心,附带HTML演示页面、URDF示例文件、许可证及说明文档,整体仅138KB,轻量易部署。已有1048人浏览学习,适合希望快速搭建机器人模型或深入理解DH参数与URDF对应关系的开发者。资源不仅提供可运行的转换脚本,还包含空白URDF模板、简化格式的litcoffee源文件以及mustache、vkbeautify等辅助脚本,便于二次开发;配合演示网站可直观查看URDF结构并调节关节值,帮助读者将抽象运动学参数落地为可视化机器人模型。
1. DH2URDF:把 DH 表变成能直接进 MoveIt 的 URDF 脚本
做过机械臂建模的人都有这种经历:手头只有一张 Denavit-Hartenberg 参数表,却要在 URDF 里手写六个 joint 的 origin、axis 和 rpy,换算坐标系时翻车是家常便饭。DH2URDF 是个 JavaScript 实现的小工具,专门把 DH 参数表转成 URDF 文件,省去手动推导变换矩阵的环节。它解决的是从运动学参数到仿真模型之间的最后一公里问题——你不用再对着四行参数矩阵抠半天,直接把表丢进去,脚本帮你生成可加载到 Rviz 和 MoveIt 里的完整 URDF。适合正在做机器人仿真、买了机械臂想搭虚拟模型、或者给学生出运动学作业的工程师和研究者。
2. 为什么需要 DH2URDF:手写 URDF 的三个翻车现场与坐标系换算
2.1 URDF 的连杆/关节结构与 DH 表的映射模型
先理清一个容易被忽略的事实:URDF 和 DH 表描述的是两种不同的运动学语言。URDF 里每个 joint 用 origin(xyz 和 rpy)来定义子坐标系相对父坐标系的位姿,轴由 axis 指定;而 DH 表用四个参数 a、alpha、d、theta 隐式定义了相邻坐标系的变换。两者之间的桥接不是一一对应,需要按标准 DH 约定做矩阵展开。
常见的翻车现场有三个。第一个是把 DH 参数直接填进 URDF 的 joint 标签,结果模型在 Rviz 里扭成麻花——因为 origin 里的 xyz 不直接等于 a、d,rpy 也不直接等于 alpha、theta。第二个是搞混标准 DH 和改进 DH(Modified DH)的约定,两者的变换顺序完全不同,填错一个符号整条链就废了。第三个是忽略了 joint 的 axis 方向,以为只要 origin 对就能动起来,结果关节一转动,后面整条连杆都偏离预期。
DH2URDF 存在的意义就是把这三个坑一次性填平。它内部实现的是标准 DH 的变换矩阵求逆解,从 DH 表反推出每个 joint 的 origin 和 axis,保证生成结果跟手算的运动学完全一致。这样你在验证逆解或者标定时,至少模型本身不会给你添乱。
2.2 DH 参数到 URDF joint 的数学换算:四步走
要理解这个工具的产出,得先知道它内部做了什么。对一个标准 DH 关节,相邻坐标系的齐次变换矩阵是四个基本变换的连乘:绕 z 轴转 theta、沿 z 轴平移 d、沿 x 轴平移 a、绕 x 轴转 alpha。而 URDF 里的 joint 只有两种信息:origin 是父坐标系到子坐标系的固定位姿,axis 是旋转轴在父坐标系下的方向向量。
从 DH 表到 URDF 的换算步骤,核心是拆出每个 joint 两个坐标系之间的旋转和平移分解。我一般会按四步走:
第一步,把每个关节的 DH 参数整理成四元组,注意单位统一,alpha 和 theta 用弧度,a 和 d 用米。第二步,写出相邻坐标系的齐次变换矩阵T = RotZ(theta) * TransZ(d) * TransX(a) * RotX(alpha),这是标准 DH 的固定顺序,不能乱调。第三步,从这个矩阵里提取旋转部分,再转换成 URDF 需要的 rpy 欧拉角;提取平移部分,得到 xyz。第四步,关节轴向量取矩阵第三列,也就是 z 轴在父坐标系下的方向,这决定关节怎么转。
这三步就是 DH2URDF 脚本的核心逻辑。普通工程师手算第二步最容易错,尤其是当 alpha 不为 0 时,旋转部分会同时混入 theta 和 alpha 两个角度,很难心算出来。工具的作用就是把这个四步过程自动化。
2.3 工具的安装与基本调用方式
DH2URDF 是 JavaScript 写成的,依赖 Node.js 环境运行。安装方式是标准的 npm 流程,从仓库拉下来后安装依赖即可。先用命令行验证 Node 环境版本是否满足要求。
node -v npm -v git clone <仓库地址> dh2urdf cd dh2urdf npm install这里git clone之后需要确认 package.json 里的依赖是否完整,常见问题是网络不佳导致某个包没装上。装完以后,用npm test跑一下自带的测试用例,如果测试通过说明环境没问题。这个工具的入口是一个命令行脚本,接收 DH 参数表的 JSON 文件作为输入,指定输出 URDF 文件的路径。
参数层面,输入文件格式一般是一个 JSON 数组,每个元素描述一个关节的 DH 参数,字段包括name(关节名)、joint_type(旋转或平移)、a、alpha、d和theta。如果你是刚接触 DH 表,建议先从四轴机械臂或六轴机械臂的标准 DH 参数开始,因为自由度高、奇异点多的构型容易暴露工具边界。
3. 用 DH2URDF 生成完整机械臂 URDF:从参数整理到文件产出
3.1 参数表怎么填:DH 表格式和单位坑
这个工具对 DH 表有明确的格式要求,不是随便给个 CSV 就能跑。根据我拆过的几份输入样例,标准的输入 JSON 长这样:
[ { "name": "joint1", "joint_type": "revolute", "a": 0.0, "alpha": 1.5707963267948966, "d": 0.15, "theta_offset": 0.0, "limit_lower": -3.141592653589793, "limit_upper": 3.141592653589793 }, { "name": "joint2", "joint_type": "revolute", "a": 0.35, "alpha": 0.0, "d": 0.0, "theta_offset": -1.5707963267948966, "limit_lower": -1.5707963267948966, "limit_upper": 1.5707963267948966 } ]每个字段的含义都值得说清楚。joint_type决定生成 joint 时用type="revolute"还是type="prismatic",标准 DH 表的 theta 和 d 中,只有一个是变量。alpha和theta_offset都填弧度,不是角度,这是最容易踩的单位坑。limit_lower和limit_upper是关节运动范围限制,如果填成角度值,生成的 URDF 在 Rviz 里转动范围会完全不对。
另一个容易忽略的点是theta_offset。有些 DH 表把关节变量的零点偏移写在 theta 列里,有些写在单独字段里。用这个工具时,需要把固定偏移放进这个字段,不要在 theta 里重复加。否则生成出来的 URDF 零点位置会和标定结果对不上。
3.2 生成与导出命令
参数表整理好之后,调用方式很直接。工具提供的是命令行接口,基本用法如下:
node index.js --input dh_table.json --output robot.urdf --name my_robot--input指定刚才整理好的 DH 参数 JSON 文件,--output指定生成的 URDF 路径,--name给 URDF 里的 root link 命名。如果希望给每个 link 加上可视化几何体,有些版本会额外支持--mesh-dir参数,把 STL 或 DAE 文件目录传进去:
node index.js --input dh_table.json --output robot_with_mesh.urdf \ --name my_robot --mesh-dir ./meshes \ --link-suffix "_link" --joint-suffix "_joint"这里--link-suffix和--joint-suffix控制命名规则,生成的 link 名字像joint1_link,joint 名字像joint1_joint。如果你之后要接 MoveIt 配置,建议保持这种带后缀的命名习惯,因为 MoveIt 的 setup assistant 对 link 命名格式有自动识别,能省掉不少手动修改。
命令跑完以后不会有太多输出,成功时只打印 URDF 文件路径和 joint 数量。如果某个关节的 DH 参数有数值问题,会报出一个比较笼统的错误提示,这时先查 JSON 格式再查数值单位。
3.3 生成的 URDF 怎么检查
文件生成以后,先别急着丢进 Rviz。我会按三步检查法过一遍。第一步看结构,确认每个 joint 都挂在正确的 parent 上;第二步看数值,抽查几个关节的 origin 和 axis 是否与手算一致;第三步做运动学验证,把 URDF 加载进 Rviz 后手动拖动关节,观察末端是否沿着预期的轨迹运动。
检查 origin 时有个经验:对于标准 DH 表,第一个关节的 origin 通常接近于全零,第二个关节以后会出现明显的 z 轴偏移和旋转。如果发现某个关节的 origin 里 xyz 全为零但 rpy 不为零,先怀疑是不是把坐标系原点放错了。对照 DH 表逐项验证是排除问题最可靠的方式。
一般情况下,工具生成的 URDF 可以直接放进 Rviz 显示。但我也遇到过生成结果里缺少 inertia 信息的情况——因为 DH 表本身不包含质量属性,工具只能生成运动学结构,动力学参数需要额外挂载。这是工具的边界,不算 bug,但要在意,免得后面做仿真时报错。
4. 避坑重点:六个高频翻车点与排查记录
4.1 模型所有关节反转:DH 约定混用
现象:生成的 URDF 加载后,关节正向转动时末端运动方向与预期完全相反。
原因:这个工具内部按标准 DH 约定计算变换矩阵,而你手里的 DH 参数表是改进 DH 格式。两种约定的 alpha 正负号规则不同,导致旋转矩阵提取结果差一个负号。组里几个人各改各的参数,改到最后模型在仿真里对着反方向转,排查了大半天才发现是参数表来源不一。
解决:统一 DH 表来源,把改进 DH 表先转换成标准 DH 再输入。转换时重点关注 alpha 的正负号以及 a 的数值,这两个参数在两种约定下并不总是一致。不要试图在 theta_offset 里通过加 180 度来纠正方向,那样会污染其他关节的计算结果。
4.2 单位混乱导致关节疯转
现象:URDF 能加载,但拖动关节时角度范围异常,比如标称 180 度的关节只能转 3.14 度,或者超出范围。
原因:DH 参数表里 alpha、theta 用了角度值,而工具按弧度解析。限位字段limit_lower和limit_upper也跟着填了角度,出来的 URDF 限位数值比预期小 57.3 倍。这类问题在团队协作传表时尤其常见,Excel 里默认显示角度,导出 JSON 时忘了换算。
解决:写一个预处理脚本统一单位,读入原始参数表时把所有角度字段乘以Math.PI / 180再生成输入 JSON。用 JavaScript 做这个转码很简单,但要在脚本里加断言,检测到某个限位绝对值小于 1 时提示可能已经是弧度,反过来则提示可能还有角度混入。
4.3 生成的 URDF 在 Rviz 里 joint 显示为灰色不可动
现象:URDF 加载到 Rviz 后,joint 状态发布器下拉列表里能看到关节,但拖动滑块时模型完全不动。
原因:多数情况下不是工具的问题,而是 URDF 里 joint 的type写成了fixed。工具在joint_type字段为prismatic或revolute时才会生成可动关节,填continuous时某些版本会误判为固定。另一个常见原因是 URDF 里缺少<transmission>标签,部分仿真插件会因此拒绝执行关节运动。
解决:生成后在编辑器里检查 joint 标签,确认type属性与预期一致。如果工具输出fixed,回到输入 JSON 检查joint_type的拼写。也可以写一段正则从 URDF 里抓取所有 joint 类型做清单:grep -o 'type="[a-z]*"' robot.urdf,一眼看出哪些关节被拍成固定了。
4.4 链接了 mesh 文件但 Rviz 里不显示形状
现象:几何体没显示,控制台报找不到资源。
原因:工具生成的 mesh 路径是绝对路径,换机器或移动目录后全部失效。我常见这种情况:在办公电脑生成 URDF,拿去实验室电脑运行,所有 mesh 路径都指向旧位置。
解决:用--mesh-dir时让工具导出相对路径package://格式,并在 URDF 顶部声明<mesh>的搜索路径。如果工具版本不支持,生成后全局替换路径前缀,把绝对路径改成相对路径,配合 Rviz 里的RobotModel设置里配置资源路径即可。
4.5 多关节机械臂末端连杆位置微偏
现象:整体结构正常,但末端 position 与手算值差几个毫米到几厘米。
原因:DH 表里某些连杆的a或d本来就应该按零处理,但填表时为了"好看"补了个小数值;或者 alpha 值在参数表里按小数近似,生成后误差累积到末端。这是参数准确性而不是工具的问题。
解决:用脚本反查,把每个关节的 origin 逐项与 DH 矩阵计算对比,找出偏差最大的那一项。从 DH 表源头修正。工具能做的是保持一致,不能替你纠正错误参数。
4.6 生成过程抛错但信息不明确
现象:运行命令时返回undefined或 JSON 解析错误,但看不到具体是哪个字段。
原因:输入 JSON 里某个关节缺少必填字段,工具没有做逐字段校验,把 undefined 直接送进矩阵计算。
解决:把所有字段手工补全后再跑。这是一个典型的"垃圾进,垃圾出"问题,工具只负责算,不负责猜。后来我写了一个简单的表单校验,在传给工具之前检查每个 joint 是否同时包含name、joint_type、a、alpha、d五个基本 field,任何缺失直接拦在门口。
5. 把生成的 URDF 接入仿真与验证:Rviz 可视化与运动学验证
5.1 导入 Rviz 的完整操作流
拿到 URDF 之后,标准动作是启动一个最小仿真环境验证模型。常见做法是用joint_state_publisher配合robot_state_publisher把 URDF 加载进 Rviz。先启动节点再打开 Rviz 界面,加载 URDF 文件并添加 RobotModel 显示项。启动命令组写在一个 launch 文件里。
<launch> <param name="robot_description" textfile="$(find my_robot)/urdf/robot.urdf" /> <node name="robot_state_publisher" pkg="robot_state_publisher" type="robot_state_publisher" /> <node name="joint_state_publisher" pkg="joint_state_publisher" type="joint_state_publisher" /> <node name="rviz" pkg="rviz" type="rviz" /> </launch>把 DH2URDF 生成的 robot.urdf 放到功能包的 urdf 目录,然后roslaunch即可。如果一切正常,Rviz 里会出现整条机械臂,左侧面板可以拖动关节滑块。
robot_state_publisher订阅robot_description参数读取 URDF 并发布 TF,joint_state_publisher负责读取关节状态并发布 joint_states 消息。如果 URDF 里缺少某个 link 的 inertia,robot_state_publisher通常会忽略它继续运行,但 Rviz 可能不显示该 link 的姿态。这是仿真前最需要关注的一条隐性依赖链。
5.2 用正运动学验证 DH 换算是否准确
模型能显示不代表算得对,我通常用正运动学做一次数字验证。写一段 JavaScript 脚本,按相同 DH 表手动实现正运动学,对比 Rviz 的末端 link 位姿。
function dhTransform(a, alpha, d, theta) { const ct = Math.cos(theta); const st = Math.sin(theta); const ca = Math.cos(alpha); const sa = Math.sin(alpha); return [ [ct, -st * ca, st * sa, a * ct], [st, ct * ca, -ct * sa, a * st], [0, sa, ca, d], [0, 0, 0, 1] ]; } function multiplyMatrices(A, B) { const result = []; for (let i = 0; i < 4; i++) { result[i] = []; for (let j = 0; j < 4; j++) { result[i][j] = 0; for (let k = 0; k < 4; k++) { result[i][j] += A[i][k] * B[k][j]; } } } return result; }脚本里dhTransform实现了标准 DH 变换矩阵,joint1到joint6对应的矩阵连乘后取最后一列前三行,得到末端位置。把这个数值和 Rviz 里显示的末端 link 原点对比,偏差在毫米级以上说明换算链路有问题。
参数层面,这个验证脚本的输入是 DH 表里的原始数值,不经过任何单位转换,直接和 URDF 的 origin 对比。我的习惯是设几组典型的关节角度,比如全零位、90 度位和两个随机位姿,分别验证工具生成的 URDF 是否全部一致。只要有一组对不上,就要回去重新审查 DH 表输入。
5.3 生成的 URDF 还缺什么:从运动学到动力学
很多人在拿到 URDF 后以为万事大吉,实际只完成了一半。DH 参数本质上是几何参数,只能描述运动学,URDF 里还有三块信息需要手动补:惯性参数<inertial>、碰撞检测用的<collision>以及可视化用的<visual>。
工具生成的 URDF 通常只带<visual>或者连这个都省了,只写 joint 和 link 的坐标系定义。在 Rviz 里能看到骨骼模型,但在 Gazebo 里仿真时,缺少<inertial>的 link 会被当作质量为零的刚体,物理引擎直接报错或表现诡异。
补<inertial>时我一般按连杆的近似形状估一个值。如果你有 CAD 模型,直接从 CAD 导出质量、质心和惯性张量填入,这是最可靠的做法。没有 CAD 模型就按圆柱或长方体近似,特别注意惯性张量的主对角线不要填成零,否则转动计算直接崩。
<collision>标签也是容易被忽略的一环。运动学验证用不到,但一进物理仿真就触发碰撞检测异常。我的做法是给每一段连杆包一个最小的圆柱或盒子,宁可保守一点,也不要让仿真里的自碰撞误报。
6. 进阶:用 JavaScript 批量化验证和回归测试,不再被模型改崩
多次生成 URDF 并且每次都收到"模型又不对了"的反馈之后,我给自己定了一条规矩:每次改完 DH 表,第一时间跑一遍自动化的 JS 回归脚本。
做法很简单:写一个测试脚本,把当前工程的 DH 表输入文件作为唯一数据源,先调用 DH2URDF 生成 URDF,再加载 URDF 解析所有 joint 的 origin 字段,和一套固定的关节角度组合下预先手算好的末端位姿做对比。整个流程封装成一个 npm script,npm run check一条命令跑完。
const fs = require('fs'); const { execSync } = require('child_process'); const dhTable = JSON.parse(fs.readFileSync('dh_table.json', 'utf8')); execSync( `node index.js --input dh_table.json --output robot.urdf --name regress_robot`, { stdio: 'inherit' } ); const expectedPose = { q1: [0, 0, 0, 0, 0, 0], pose: [0.35, 0.0, 0.5] }; function verifyAgainstExpected(dh, q, expected) { // 在这里连乘DH矩阵,得到末端位置 // 与expected对比,允许1e-3的误差 } const result = verifyAgainstExpected(dhTable, expectedPose.q1, expectedPose.pose); if (!result.passed) { console.error(`验证失败: ${result.error}`); process.exit(1); } console.log('DH 表与 URDF 一致性验证通过');这套脚本维护成本极低,但收益很直接。DH 表里某个参数被误改时,回归测试会在毫秒级的时间内报警,不用等到同事在 Rviz 里拖半天关节才发现末端偏了。拼装工具链的过程中,我还把 Git 的 pre-commit hook 绑到了这个脚本上,任何一次提交都强制先验证再入库。
从那以后我每次拿到新的机械臂 DH 表,都强制走一遍生成加验证的完整流程,从参数输入到 URDF 产出再到正运动学回归,十分钟以内结束。这种习惯听起来繁琐,实际上省掉的是反复排查坐标系、单位和约定不符的半天时间。希望这个流程能帮你在机器人建模的路上少踩几个坑。
本文还有配套的精品资源,点击获取