unsetunset0. 什么是cuRobounsetunset
打开网易新闻 查看精彩图片
unsetunset0. 什么是cuRobounsetunset

cuRobo 是 NVIDIA Research 开源的 CUDA 加速机器人运动生成库,底层建立在 PyTorch、CUDA 和 NVIDIA Warp 之上。

它提供的主要能力包括:

正运动学(Forward Kinematics,FK)

逆运动学(Inverse Kinematics,IK)

环境碰撞与机器人自碰撞检测

轨迹优化

几何路径规划

模型预测控制(MPC)

从深度图构建 TSDF/ESDF 距离场

高自由度机器人和人形机器人的全身运动计算

cuRobo 不是机器人驱动,也不是完整的机器人操作系统。它更像一个高性能的“运动计算后端”。因此,cuRobo 解决的不是“机器人要完成什么任务”,而是:已知机器人当前状态和任务目标,机器人应该以什么关节姿态、沿什么运动过程完成目标。

核心参考资料:https://curobo.org/reports/curobo_report.pdf

api:https://curobo.org/get_started/7_api.html

技术报告:https://curobo.org/research/research.html-page

源代码:https://github.com/nvlabs/curobo

本篇技术博客由Mbot实验室成员dangyang11分享

打开网易新闻 查看精彩图片

判断一个抓取姿态能不能用:抓取网络可能一次输出 100 个抓取姿态。这些姿态中,有些超出机械臂工作空间,有些会导致夹爪撞到桌子,还有些虽然末端能够到达,但机械臂自身会发生碰撞。这时可以使用批量 IK用GPU算出可行的IK

生成一条无碰撞轨迹

连续跟踪变化的目标:如果目标一直在移动,例如,遥操作时拖动末端目标、相机持续跟踪传送带工件、障碍物位置不断变化,可以使用 Differential IK 或 MPC,让机械臂持续更新动作。

0.2 cuRobo 中最常见的四类问题

问题

输入

输出

常用 API

机器人现在在哪里?

关节状态

各连杆和末端位姿

Kinematics

末端要到这里,关节怎么转?

目标末端位姿

一组或多组关节解

InverseKinematics

怎样完整地移动过去?

当前状态、目标、场景

无碰撞关节轨迹

MotionPlanner

目标变化时下一步怎样动?

当前状态、动态目标

短时域动作序列

ModelPredictiveControl

0.3 cuRobo 的核心原理 0.3.1 先把机器人表示成计算模型

它需要明确知道:

机器人有哪些连杆;

连杆之间通过哪些关节连接;

每个关节的旋转轴是什么;

每个关节的上下限是多少;

哪个连杆是末端工具坐标系;

机器人各部分的碰撞几何是什么。

URDF 能提供运动学树、关节和网格信息,但 cuRobo 还需要适合 GPU 碰撞检测的数据,例如碰撞球和自碰撞忽略关系。

最终,后续算法使用的通常是一个 cuRobo YAML 配置:

URDF + 网格文件


RobotBuilder

├── 运动学树
├── 工具坐标系
├── 关节限制
├── 连杆碰撞球
└── 自碰撞忽略关系


robot.yml
0.3.2 使用碰撞球

机器人的三维网格很复杂,直接反复计算网格与网格之间的碰撞代价较高。

cuRobo 会使用一组球体近似机器人各连杆:

复杂连杆网格  →  多个碰撞球

球与球、球与长方体、球与距离场之间的距离更容易计算,而且大量距离查询可以放到 GPU 上并行执行。

0.3.3 GPU 加速

GPU 并不是让一条普通 Python 语句自动变快。

cuRobo 的优势来自任务本身存在大量并行结构,例如:

同时计算 1000 组正运动学;

同时尝试 32 个 IK 初始种子;

同时检查大量碰撞球;

同时评估多条轨迹种子;

并行执行优化器的多个候选步长。

传统串行思路可能是:

方案 1 → 失败
方案 2 → 失败
方案 3 → 成功

cuRobo 更倾向于:

方案 1、2、3……同时计算 → 选择成功方案

所以 cuRobo 最适合的不是只做一次简单计算,而是:

批量目标;

多种子优化;

大量碰撞查询;

轨迹中多个时间点的并行评估。

unsetunset1. 安装unsetunset

# 步骤1:克隆 cuRobo
git clone https://github.com/NVlabs/curobo && cd curobo
# 步骤2:创建虚拟环境
export UV_PYTHON_INSTALL_MIRROR=https://registry.npmmirror.com/-/binary/python-build-standalone npmmirror 国内镜像
uv venv --python 3.11
source .venv/bin/activate
# 步骤3:安装cuRobo
nvidia-smi | grep CUDA
13.x:
uv pip install -e ".[cu13-torch,dev]" # 包含PyTorch的完整版本
12.x:
uv pip install -e ".[cu12-torch,dev]" # 包含PyTorch的完整版本
:测试安装
python -c "import curobo; print(curobo.__version__)"
env -u PYTHONPATH PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 \
python -m pytest --pyargs curobo.tests
打开网易新闻 查看精彩图片
unsetunset2.正运动学unsetunset

正运动学解决的问题是:已知机器人的关节角,末端执行器现在在哪里?

这是理解机器人模型是否正确的第一步。URDF 中的关节方向、坐标轴、工具坐标系只要有一处错误,后面的 IK 和运动规划都会受到影响。

cuRobo在GPU上利用自动微分计算正向运动学。正向运动学 (FK) 将关节角度向量映射到运动链中每个连杆的 6 自由度位姿。cuRobo 使用并行指数乘法公式在 GPU 上完全计算 FK,并且每个运算都可通过 PyTorch 自动微分进行微分。这意味着 FK 可以作为构建模块,应用于更大的优化循环(例如 IK、运动规划、MPC)中,并且梯度可以以零额外成本返回到关节角度。

python -m curobo.examples.getting_started.forward_kinematics

程序首先加载 cuRobo 自带的 Franka Panda 机器人模型,然后分别演示单组关节状态、批量关节状态和带梯度的正运动学计算。官方示例中,单次计算使用的是一组全零关节角。

输出通常包含:

Robot has 7 degrees of freedom
Tool frames: ['panda_hand']

Single FK:
EE position: tensor([[ 8.8000e-02, -7.1490e-13, 9.2600e-01]], device='cuda:0')
EE quaternion (wxyz): tensor([[ 4.5239e-12, 9.2388e-01, 3.8268e-01, -1.8738e-12]], device='cuda:0')

Batched FK (1000 configs): 0.84 ms
EE positions shape: torch.Size([1000, 1, 1, 3])

Differentiable FK:
Gradient w.r.t. joints: tensor([[-7.1490e-13, -5.6361e-01, 5.6009e-13, 2.3293e-01, -9.8927e-13,
-1.3192e-02, 6.8164e-20]], device='cuda:0')

正运动学在实际项目中常用于:

验证机器人模型;

根据编码器关节角计算末端位置;

检查规划轨迹中的末端运动;

为 IK 和控制器提供初始状态。

2.1 单次正运动学

7 degrees of freedom 表示当前加载的机械臂有 7 个可运动关节,也就是 Franka Panda 机械臂本体的 7 个自由度。

Tool frames: ['panda_hand'] 表示机器人配置中定义的工具坐标系是 panda_hand。后续输出的末端位置和姿态,都是针对这个坐标系计算的。

EE 是 End Effector,也就是末端执行器。这个位置可以写成:

x = 0.088 m
y ≈ 0 m
z = 0.926 m

也就是说,在当前零关节角状态下,panda_hand 位于机器人基坐标系前方约 8.8 厘米、高约 92.6 厘米的位置。

EE quaternion (wxyz):
tensor([[ 4.5239e-12, 9.2388e-01, 3.8268e-01, -1.8738e-12]])

这是末端姿态的四元数表示。接入 ROS、SciPy 或其他机器人库时,必须确认四元数顺序,否则末端姿态可能完全错误。官方示例也是从计算结果中读取工具坐标系的位置和 wxyz 四元数。

2.2 批量正运动学

Batched FK (1000 configs): 0.84 ms
EE positions shape: torch.Size([1000, 1, 1, 3])

这一部分不再只计算一组关节角,而是随机生成了 1000 组关节配置,并在 GPU 上一次性完成正运动学计算。官方代码使用 CUDA Event 测量这次批量计算的时间(0.84ms)。

张量形状:

[1000, 1, 1, 3]

可以重点看两个维度:

1000:一共有 1000 组机器人关节状态;

最后的 3:每一个末端位置都包含 x、y、z 三个坐标。

中间两个长度为 1 的维度来自当前版本对批次和工具坐标系的组织方式。这个示例只有一个工具坐标系,因此这些维度都是 1。官方教程想表达的核心是:输入形状为一批关节角,输出则是一批对应的末端位置和姿态。

这类批量计算很适合:

大规模工作空间分析;

判断大量关节状态对应的末端位置;

同时评估多条候选轨迹;

为优化器计算多个候选解。

2.3 可微分正运动学

Differentiable FK:
Gradient w.r.t. joints:
tensor([[-7.1490e-13, -5.6361e-01, 5.6009e-13,
2.3293e-01, -9.8927e-13, -1.3192e-02,
6.8164e-20]])

示例先设置了一个目标位置:

target_pos = [0.5, 0.0, 0.5]

然后计算当前末端位置与目标位置之间的平方误差:

loss = torch.sum((ee_pos - target_pos) ** 2)

最后调用:

loss.backward()

由 PyTorch 自动计算损失函数相对于 7 个关节角的梯度。

输出中的 7 个数字,分别对应机械臂的 7 个关节。

例如第二个关节的梯度是-0.56361,说明在当前姿态附近,第二个关节对末端位置误差的影响比较明显;第四个关节的梯度是0.23293,它同样会明显影响当前目标误差;接近 0 的梯度则说明,在当前关节状态和当前损失函数下,该关节的小幅变化对误差影响较小。

优化算法可以根据这些梯度不断调整关节角,使末端逐渐接近目标。cuRobo 的优化式 IK、轨迹优化和 MPC,都建立在类似的可微分计算基础上。官方示例也明确将可微分 FK 视为后续优化模块的基础。

unsetunset3. 逆运动学unsetunset

正运动学是“关节角推末端位姿”,逆运动学正好相反:已知末端应该到达的位置和姿态,求机器人关节角。

运行官方 IK 教程:

python -m curobo.examples.getting_started.inverse_kinematics

教程会依次演示三种情况:

单个目标 IK;

100 个目标的批量 IK;

考虑环境和自碰撞的 IK。

成功后会看到类似输出:

=== Single IK ===
IK solved!
Joint angles: tensor([[[ 2.2254, -0.1998, -2.3231, -2.7024, 2.6837, 0.3339, 0.4574,
0.0400, 0.0400]]], device='cuda:0')
Position error: 0.000 mm

=== Batched IK (100 poses) ===
Solved 93/100 poses (93% success)
Mean position error: 0.055 mm
Max position error: 4.389 mm

=== Collision-Free IK ===
Single collision-free IK solved!
Position error: 0.000 mm

After adding obstacle -- still solved!
Position error: 0.000 mm

Batched collision-free IK: 50/50 solved

cuRobo 的 IK 会同时运行多组初始种子,而不是只从一个初始关节角开始求解。官方示例默认使用 32 个种子;批量模式则可以一次提交多组目标位姿,让 GPU 同时计算。

3.1 单目标IK

运行:

python -m curobo.examples.getting_started.inverse_kinematics \
--mode single

对应源码中的函数:

def single_ik_example():
"""Solve IK for a single target pose.

Returns:
True if IK succeeded.
"""
config = InverseKinematicsCfg.create(
robot="franka.yml",
num_seeds=32,
)
ik = InverseKinematics(config)
target_link = ik.tool_frames[0]

goal_pose = Pose(
position=torch.tensor([[0.4, 0.0, 0.4]], device="cuda", dtype=torch.float32),
quaternion=torch.tensor([[1.0, 0.0, 0.0, 0.0]], device="cuda", dtype=torch.float32),
)

result = ik.solve_pose(GoalToolPose.from_poses({target_link: goal_pose}, num_goalset=1))

if result.success.item():
print("IK solved!")
print(f" Joint angles: {result.js_solution.position}")
print(f" Position error: {result.position_error.item() * 1000:.3f} mm")
return True
else:
print("IK failed -- target may be unreachable")
return False

完整流程可拆成五步:

3.1.1 第一步:创建 IK 配置

config = InverseKinematicsCfg.create(
robot="franka.yml",
num_seeds=32,
)

这里加载 Franka 模型,franka.yml 中保存了机器人运动学结构、关节名称、关节限制、工具坐标系和碰撞模型等信息。

同时,IK 是一个非线性问题,同一个末端目标可能对应多个关节解,也可能因为初始值不好而求解失败。因此,cuRobo 不只尝试一个初始关节状态,而是同时使用 32 个初始种子进行优化,然后从中选择较好的结果。官方教程明确说明,单目标 IK 会运行 32 个并行优化种子。并使用 32 个并行种子。

3.1.2 第二步:创建求解器

ik = InverseKinematics(config)

这一步根据刚才的配置创建真正的 IK 求解器。

创建完成后,ik 中已经包含:

Franka 的运动学模型;

默认关节状态;

工具坐标系;

IK 优化器;

相关 GPU 缓存。

接着源码执行:

target_link = ik.tool_frames[0]

tool_frames 表示机器人配置中定义的工具坐标系。

Franka 示例里,第一个工具坐标系通常是panda_hand.因此,这一行的意思是:接下来要求解的是 panda_hand 这个坐标系的目标位姿。以后换成自己的机器人时,工具坐标系名称取决于机器人配置文件。

3.1.3 第三步:定义目标位姿

goal_pose = Pose(
position=torch.tensor(
[[0.4, 0.0, 0.4]],
device="cuda",
dtype=torch.float32,
),
quaternion=torch.tensor(
[[1.0, 0.0, 0.0, 0.0]],
device="cuda",
dtype=torch.float32,
),
)

一个完整的末端目标由两部分组成,即位置(position)和姿态(quaternion)

这里定义目标位置和 wxyz 四元数姿态。表示目标点位于机器人基坐标系下x = 0.4 m y = 0.0 m z = 0.4 m的地方,四元数(w=1,x=0,y=0,z=0)表示不需要额外旋转。

3.1.4 第四步:把目标交给求解器

result = ik.solve_pose(                                  # 求解器执行逆运动学计算
GoalToolPose.from_poses( # 把普通的Pose转换成IK求解器需要的目标格式
{target_link: goal_pose}, # 字典,表示希望panda_hand达到这个位置和姿态
num_goalset=1,
)
)

调用求解器solve_pose。求解器运行 32 个并行优化种子,并返回最佳解。

3.1.5第五步:读取结果

result.success                                        # 表示IK是否找到了满足条件的关节解
result.js_solution.position # 求出关节位置
result.position_error # 最终末端位置与目标位置之间的误差

读取是否成功、关节角和位置误差。

3.2 批量IK

运行:

python -m curobo.examples.getting_started.inverse_kinematics \
--mode batch

单目标 IK 一次只处理一个目标。,而这一部分创建 100 个目标,X 坐标从 0.2 米变化到 0.8 米,Y 固定为 0,Z 固定为 0.4 米,cuRobo 让 GPU 批量处理同时求解。

def batched_ik_example():
"""Solve IK for a batch of target poses.

Returns:
True if at least one pose was solved.
"""
n_poses = 100
config = InverseKinematicsCfg.create(
robot="franka.yml",
num_seeds=32,
max_batch_size=n_poses,
)
ik = InverseKinematics(config)
target_link = ik.tool_frames[0]
positions = torch.zeros(n_poses, 3, device="cuda", dtype=torch.float32) ,形状为[100.3],也就是100个目标,每个目标3个位置坐标
positions[:, 0] = torch.linspace(0.2, 0.8, n_poses) 坐标从 0.2 米逐渐变化到 0.8 米
positions[:, 1] = 0.0 # 设置Y 坐标全部为 0
positions[:, 2] = 0.4 # 设置Z 坐标全部为 0.4 米

quaternions = torch.zeros(n_poses, 4, device="cuda", dtype=torch.float32) # 姿态张量形状[100,4]
quaternions[:, 0] = 1.0 # 将每个目标的四元数都设为:[1,0,0,0]

goal_poses = Pose(position=positions, quaternion=quaternions)

result = ik.solve_pose(GoalToolPose.from_poses({target_link: goal_poses}, num_goalset=1))

n_success = result.success.sum().item() # 将目标是否成功的布尔值相加即可得到成功数量
print(f"Solved {n_success}/{n_poses} poses ({100 * n_success / n_poses:.0f}% success)")

successful = result.success.squeeze() # 选出成功目标
if n_success > 0:
pos_errors = result.position_error[successful] # 统计成功结果的位置误差
print(f"Mean position error: {pos_errors.mean().item() * 1000:.3f} mm")
print(f"Max position error: {pos_errors.max().item() * 1000:.3f} mm")
return n_success > 0

含义是:

X 坐标从 0.2 米逐渐变化到 0.8 米;

Y 坐标全部为 0;

Z 坐标全部为 0.4 米。

所以这些目标形成一条直线。且100个目标的位置不同,但末端方向相同。

批量目标适合可达性分析和抓取姿态评估。

3.3 碰撞感知IK

运行:

python -m curobo.examples.getting_started.inverse_kinematics \
--mode collision_free

普通 IK 只关心:末端能不能到达目标?碰撞感知 IK 还要关心:到达目标时,机器人有没有撞到环境或撞到自己?

配置中增加了:

config = InverseKinematicsCfg.create(
robot="franka.yml",
scene_model="collision_table.yml", # 加载包含桌子等障碍物的场景配置。求解时,目标关节状态不仅要满足末端位姿,还必须避免机器人碰到这些障碍物。
num_seeds=32,
self_collision_check=True, # 启用机器人自碰撞检查。
)

一个负责加载环境障碍物,一个负责启用机器人自碰撞检查。在创建求解器配置时传递scene_model。self_collision_check=True 优化器会添加碰撞代价项,以确保每个机器人连杆球面都避开障碍物和其他连杆。

教程还创建了一个新的长方体:

new_obstacle = Cuboid(
name="box_1",
pose=[0.5, 0.0, 0.3, 1, 0, 0, 0],
dims=[0.1, 0.3, 0.2],
)

其中,

pose 前三个数:障碍物位置
pose 后四个数:wxyz 四元数
dims:长、宽、高

所以这个障碍物中心位于(0.5m,0,0.3m),尺寸为 0.1 × 0.3 × 0.2 m

然后通过:

ik2.update_world(Scene(cuboid=[new_obstacle]))

把新障碍物加入当前场景。

这说明场景变化时,不一定要重新创建整个 IK 求解流程,可以调用 update_world() 更新障碍物。官方教程把运行时场景更新列为 IK 的第四个层次。

3.4 三种可视化模式 3.4.1 完整交互式IK

# 1.完整交互式 IK
python -m curobo.examples.getting_started.inverse_kinematics --visualize


# 浏览器打开
http://localhost:8080
打开网易新闻 查看精彩图片

界面中可以拖动末端目标坐标系。

每次目标变化后,程序重新调用:

ik_solver.solve_pose(...)

然后把成功结果更新到可视化机器人上。

3.4.2 Differential IK

python -m curobo.examples.getting_started.inverse_kinematics \
--differential

普通完整 IK 可能在不同关节解之间跳转。Differential IK 会以上一次结果作为下一次求解的起点,并加入速度和加速度正则项,使连续运动更平滑。官方源码说明它适合响应式控制和类似遥操作的交互。它更像:

目标稍微移动一点

关节也只移动一点

继续使用当前结果求下一步

而不是每次都从许多随机姿态重新寻找一个完全不同的解。

3.4.3 可达性分析

python -m curobo.examples.getting_started.inverse_kinematics \
--reachability

这个模式会在一个平面上生成大量目标点,然后使用批量 IK 判断哪些点可达。成功区域显示为绿色,失败区域显示为红色。它适合用来分析:

机器人工作空间;

工位应该放在哪里;

料箱是否放得太远;

哪些抓取目标需要提前过滤;

双臂机器人在哪些区域都能到达。

打开网易新闻 查看精彩图片

Mbot具身智能实验室

让尖端科技触手可及,人人皆可探索未来

打开网易新闻 查看精彩图片

Mbot基础交流群等你加入,下方扫码联系

具身-杰西

Mbot具身-小助手

Mbot-视频号

Mbot-公众号