本文档属于 Robotics Tutorial 项目,作者:Pengfei Guo,达妙科技。采用 CC BY 4.0 协议,转载请注明出处。
D01 导论——双臂任务分类与协调运动学¶
本章定位:双臂操作是机器人从"工具使用者"迈向"通用操作者"的关键一步。本章从"为什么需要双臂"这一根本问题出发,系统建立双臂任务的分类体系、协调运动学的数学框架、以及主流双臂平台的工程对比,为后续 D02(协调规划)、D03(协调力控)、D04(双臂学习)奠定完整的理论与直觉基础。
适用范围:本章的协调运动学理论(增广 Jacobian、相对 Jacobian、绝对/相对变量分解)适用于所有双臂系统——工业双臂、人形机器人双臂、双移动机械臂——是真正的"全方向共享"内容。
前置依赖:M01(Pinocchio 深度精读)、M03(IK 求解器深度)、F01(力控导论)、F02(操作空间动力学)
下游章节:D02(双臂协调规划)、D03(双臂协调力控)、D04(双臂学习)、D09(双臂 MoveIt2 系统集成)
建议用时:2 周(理论 4 天 + 代码实践 4 天 + 综合练习 2 天)
前置自测 ⭐¶
📋 答不出 >= 2 题 → 先回前置章节复习
| 编号 | 问题 | 答不出时回顾 |
|---|---|---|
| 1 | 雅可比矩阵:对于 7-DOF 机械臂,几何雅可比 \(J(q) \in \mathbb{R}^{6 \times 7}\) 的物理含义是什么?当 \(\text{rank}(J) < 6\) 时意味着什么? | M01 Pinocchio 深度精读 |
| 2 | 伪逆与零空间:写出右伪逆 \(J^+ = J^T(JJ^T)^{-1}\)(\(J\) 满行秩时即 Moore-Penrose 伪逆)的表达式。\(q_{\text{null}} = (I - J^+J)\dot{q}_0\) 为什么满足 \(J q_{\text{null}} = 0\)?这个性质的工程意义是什么? | M03 IK 求解器深度 |
| 3 | 操作空间动力学:写出操作空间惯量矩阵 \(\Lambda = (JM^{-1}J^T)^{-1}\) 的定义。它为什么需要 \(J\) 行满秩?当接近奇异位形时 \(\Lambda\) 会发生什么? | F02 操作空间动力学 |
| 4 | SE(3) 与 twist:写出刚体的空间速度 twist \(\mathcal{V} = [\omega^T, v^T]^T \in \mathbb{R}^6\)。两个 twist 的"差"在 Lie 代数意义下如何计算? | 李群基础 / M01 |
| 5 | 阻抗控制:Hogan 阻抗律 \(F = M_d\ddot{\tilde{x}} + D_d\dot{\tilde{x}} + K_d\tilde{x}\) 的三个参数分别控制什么物理行为? | F01 阻抗导纳二分法 |
本章目标¶
学完本章后,你应该能够:
- 判断 任意操作任务是否需要双臂——使用 Koivo 1988 三分类法和 Krebs 2022 四维分类学进行系统化标签分析,而非凭直觉判断
- 构造 增广雅可比 \(J_{\text{aug}} \in \mathbb{R}^{12 \times 14}\) 和相对雅可比 \(J_{\text{rel}} \in \mathbb{R}^{6 \times 14}\),理解它们的物理含义和数学结构
- 运用 Chiacchio 1996 的绝对/相对变量对偶分解,将双臂任务分解为"物体整体运动"和"两臂相对关系"两个独立子问题
- 推导 刚性抓取约束如何将 14 维 C-space 切割为 8 维约束流形,并理解约束流形上规划的数学基础
- 使用 Pinocchio 在 C++ 和 Python 中为双 Franka Panda(14-DOF)计算完整的协调运动学——\(J_{\text{aug}}\)、\(J_{\text{rel}}\)、零空间投影 \(P\)
本章知识导航¶
D1.1 为什么需要双臂 ⭐
│ └─ 单臂四大局限 → 双臂的质变新能力
│
▼
D1.2 任务分类法 ⭐⭐
│ ├─ Koivo 1988 三分类(紧/松/解耦)
│ └─ Krebs 2022 四维分类学
│
▼
D1.3 运动学约束 ⭐⭐ ─────────────┐
│ └─ 开链/闭链/虚拟闭链 │
│ │
▼ ▼
D1.4 增广 Jacobian ⭐⭐ D1.5 相对 Jacobian ⭐⭐
│ └─ J_aug ∈ R^{12×14} │ └─ J_rel ∈ R^{6×14}
│ │
└──────────┬─────────────────────┘
▼
D1.6 绝对/相对分解 ⭐⭐⭐
│ └─ Chiacchio 1996 共模/差模
│
▼
D1.7 约束流形 ⭐⭐⭐
│ └─ 8 维子流形 + OMPL 实现
│
├─→ D1.8 三种建模范式 ⭐⭐
├─→ D1.9 主流平台对比 ⭐
├─→ D1.10 研究路线图 ⭐
├─→ D1.11 协调奇异性 ⭐⭐⭐
└─→ D1.12 前沿展望 ⭐⭐⭐⭐
前置知识桥接¶
回顾 M01(Pinocchio 深度精读):Pinocchio 的 computeFrameJacobian 函数返回指定帧的几何 Jacobian \(J \in \mathbb{R}^{6 \times n_v}\)。LOCAL_WORLD_ALIGNED 坐标系将 Jacobian 表达在世界坐标方向上,但线速度参考点仍为各自帧原点。在 D01 中,我们将两个这样的 Jacobian 堆叠或相减来构建双臂运动学——关键是理解"相减前必须统一参考点"。
回顾 M03(IK 求解器深度):单臂冗余分解 \(\dot{q} = J^+ \dot{x} + (I - J^+ J)\dot{q}_0\) 中,\((I - J^+ J)\) 是零空间投影矩阵。D01 将这个概念推广到双臂——\(P = I - J_{\text{rel}}^+ J_{\text{rel}}\) 投影到闭链约束的零空间。
回顾 F02(操作空间动力学):操作空间惯量 \(\Lambda = (JM^{-1}J^T)^{-1}\) 在 D03(双臂力控)中会推广为双臂协调操作空间惯量。本章建立的 \(J_{\text{aug}}\)、\(J_{\text{rel}}\) 是 D03 的运动学基础。
如果跳过本章会怎样¶
- 规划阶段:不理解约束流形(D1.7),你会在 14 维自由空间中搜索路径——维度灾难导致规划时间从秒级膨胀到分钟级,而且找到的路径可能违反闭链约束。
- 控制阶段:不理解相对 Jacobian(D1.5),你会用独立位控驱动两臂共持物体——产生不可控的内力(force fight),轻则精度下降,重则损坏物体或夹爪。
预计阅读时间¶
| 模式 | 时间 | 建议 |
|---|---|---|
| 精读(含推导和代码实践) | 8-10 小时 | 完整阅读,手推关键公式,运行所有代码示例 |
| 速读(抓核心概念) | 3-4 小时 | 重点读 D1.1-D1.6,跳过代码细节和 Worked Example |
| 速查(查特定公式/API) | 15-30 分钟 | 利用知识导航和术语速查表定位目标 |
| 复习(已学过,回顾要点) | 1-2 小时 | 读本章小结和常见误解汇总,做自测题检验 |
D1.1 为什么需要双臂——单臂的根本局限 ⭐¶
动机:一只手能做什么?¶
让我们从一个日常观察开始:你能用一只手拧开瓶盖吗?
答案是"几乎不可能"——因为拧开瓶盖需要同时满足两个条件:(1)固定瓶身使其不旋转;(2)对瓶盖施加扭矩。一只手无法同时完成"约束"和"操作"两个角色。
这不是一个特殊案例,而是揭示了单臂操作的根本局限。在人类日常活动中,据统计约有 40-60% 的操作任务需要双手协作完成(Gupta & Srinivasa 2017)。从进化角度看,人类双手协调能力是灵长类与其他哺乳动物的关键区别之一——直立行走释放了双手,双手协作又推动了工具使用和大脑发展。
让我们系统地分析单臂操作的四大根本局限——这些局限共同定义了"为什么需要双臂"。
单臂操作的四大局限 ⭐¶
以下四个局限按"从物理到数学"的顺序排列:前两个是几何和力学层面的直觉限制,后两个是更深层的力学原理限制。理解这些局限是选择"何时需要双臂"的决策基础。
局限 1:工作空间与载荷限制
单臂的可达工作空间(reachable workspace)是一个以肩关节为中心、以臂长为半径的近似球壳区域。对于 7-DOF Franka Panda,这个球壳的外径约 0.855m。当需要操作的物体尺寸超过夹爪开口(通常 8-10cm),或质量超过单臂额定载荷(Panda: 3kg),单臂物理上无法完成任务。
单臂工作空间限制:
Franka Panda: 臂展 0.855m, 载荷 3 kg, 夹爪开口 8 cm
UR5e: 臂展 0.850m, 载荷 5 kg
UR10e: 臂展 1.300m, 载荷 12.5 kg
→ 直径 > 8cm 的物体:单臂抓取困难(需特制夹爪或吸盘)
→ 质量 > 3 kg(Panda)/ 5 kg(UR5e):超出单臂能力
→ 臂展之外的物体:不可达
双臂解决方案:
→ 两臂合力:有效载荷翻倍(2×3kg = 6kg for dual Panda)
→ 协调抓取:可操作任意大小物体
→ 工作空间扩展:两臂覆盖区域的并集远大于单臂
局限 2:操作自由度不足
单臂 7-DOF 控制 6 维末端位姿后,仅剩 1 维零空间冗余——只够做一件"次要任务"(如关节极限回避或奇异回避)。但许多操作任务需要同时控制更多自由度:
| 任务 | 所需自由度 | 单臂能力 | 缺口 |
|---|---|---|---|
| 末端定位 | 6 DOF | 6/7 ✅ | 0 |
| 定位 + 夹爪张合 | 7 DOF | 7/7 ⚠️ | 无冗余 |
| 定位 + 工具操作 | 8-10 DOF | 7/7 ❌ | 1-3 DOF |
| 双手协调操作 | 12+ DOF | 7/7 ❌ | 5+ DOF |
双臂(14-DOF)控制两个末端(12 维)后还有 2 维零空间冗余,可以同时做自碰撞回避和操作度优化。
局限 3:无法施加内力
这是最深层的物理局限。单臂与物体的接触是"开链"(open chain)——末端对物体施加力,物体通过环境(如桌面、夹具)被约束。如果物体没有外部约束(悬空状态),单臂只能推动物体运动,无法对物体施加"挤压力"(internal force)来维持抓取稳定性。
本质洞察:双臂与物体构成"闭链"(closed chain),闭链的本质特性是存在**内力空间**——一组不产生物体运动但维持力封闭的力。这些内力是稳定抓取的基础。单臂开链无法产生内力,因此对悬空物体的操作能力受限于纯摩擦力。
局限 4:缺乏"反作用力参考"
当你用一只手推一扇门时,你的脚提供了地面反作用力。但对于固定在桌面上的单臂机械臂,当它推动一个重物时,反作用力由底座承受。如果底座刚度不够(如安装在移动平台上),推力会导致底座倾斜——这在移动操作(mobile manipulation)中尤为突出。
双臂可以通过一只手固定物体、另一只手操作的方式,实现"自我提供反作用力"——不依赖外部固定装置。这就是为什么人类在做精细操作时几乎总是双手协作。
从单臂到双臂:能力的质变而非量变¶
双臂不是"两个单臂的简单叠加",而是带来了质的新能力:
| 新能力 | 单臂 | 双臂 | 本质原因 |
|---|---|---|---|
| 内力控制 | ❌ | ✅ | 闭链产生内力空间 |
| 物体重定向 | 困难 | 自然 | 两点控制 > 一点控制 |
| 在手操作 | 需灵巧手 | 可用双臂替代 | 两臂相对运动=旋转 |
| 力矩放大 | ❌ | ✅ | 力偶=两力\(\times\)力臂 |
| 自碰撞检测需求 | 低 | 高 | 两臂共享工作空间 |
| 柔性物体整形 | 极困难 | 自然 | 两点张力控制变形 |
| 精密装配 | 需外部夹具 | 自给自足 | 一手定位一手插装 |
质变的数学根源:单臂系统是开链(open chain),其末端力与关节力矩之间的关系由 \(\tau = J^T f\) 完全确定——给定末端力 \(f\),关节力矩 \(\tau\) 唯一确定。双臂闭链系统中,两臂末端力的合力对物体的作用不唯一——存在一个**力的零空间**(grasp matrix 的零空间),在这个空间中的力分量不产生物体运动,而是作为**内力**维持抓取稳定性。这个内力空间是闭链特有的,是双臂相比单臂的本质新能力。
设 Grasp Matrix 为 \(G \in \mathbb{R}^{6 \times 12}\)(将两末端 wrench 映射到物体质心 wrench),则:
\(G\) 的零空间 \(\ker(G) \subset \mathbb{R}^{12}\) 中的力分量 \(f_{\text{int}}\) 满足 \(G f_{\text{int}} = 0\)——不产生物体运动但维持抓取封闭。对于两点刚性抓取,\(\dim(\ker(G)) = 6\),这 6 维内力包括:3 维挤压/拉伸力和 3 维扭转/剪切力矩。
跨领域类比:双臂操作 vs 双手弹钢琴。钢琴独奏不只是"两只手各弹一半音符"——双手之间有复杂的时序协调、动态互补、和声构建。同样,双臂操作不只是"两个独立规划的叠加"——协调、约束、内力是全新的维度。不同之处在于:钢琴双手通常不物理耦合(各按各的键),而双臂操作中两手经常通过共持物体物理耦合,这使得双臂机器人面临比钢琴演奏更严格的力学约束。
人类双臂操作的神经科学基础 ⭐¶
在理解工程双臂系统之前,值得简要了解人类大脑如何协调双手——这为双臂机器人的控制架构设计提供了生物学启发。
人类双臂协调由大脑皮层的多个区域协同控制:初级运动皮层(M1)控制单侧肢体的精细运动,辅助运动区(SMA)协调双侧肢体的时序同步,胼胝体**传递左右半球之间的协调信号。神经科学研究(Swinnen 2002)揭示了一个重要现象:人类在执行双手非对称运动时(如左手画圆、右手画方)会出现**空间-时序耦合效应——两手的运动模式会互相"吸引",趋向于同步化。
这个现象对机器人双臂控制的启发是:双臂系统天然倾向于耦合运动。即使在解耦任务中,两臂的控制信号也可能通过共享的控制器结构或硬件振动产生耦合。因此,双臂控制器设计需要显式考虑这种耦合——无论是利用它(紧耦合任务)还是抑制它(解耦任务)。
不是X而是Y:人类双手协调不是"大脑同时独立控制两只手",而是"大脑规划一个双手协调的整体动作,再分配给两侧肢体"。这与双臂机器人的**绝对/相对分解**框架(D1.6)有深刻的对应:大脑先规划"物体整体运动"(绝对变量),再解决"两手如何分配力/运动"(相对变量)。
如果不用双臂会怎样——反面教材¶
场景:单臂搬运长杆
让一只 Franka Panda 搬运一根 1.2m 长、3kg 的金属杆。
问题 1:抓取稳定性
单臂抓取杆的中点 → 杆两端悬臂
杆转动惯量 J = mL²/12 = 3×1.44/12 = 0.36 kg·m²
任何加速度 α 产生的扭矩 τ = J·α
当 α = 1 rad/s²: τ = 0.36 N·m → 夹爪可能打滑
问题 2:碰撞风险
杆长 1.2m >> 臂展 0.855m
杆的两端大幅超出臂的灵活操控范围
→ 环境碰撞风险极高
问题 3:姿态控制
单点抓取无法独立控制杆的绕轴旋转
→ 杆的 roll 角不可控(1 DOF 失控)
双臂方案:
两臂各抓杆的 1/3 和 2/3 处
→ 有效载荷分摊:各 1.5 kg(远低于单臂极限)
→ 力偶控制:可精确控制杆的所有 6 DOF
→ 内力维持抓取稳定性
⚠️ 常见陷阱¶
💡 概念误区:认为"双臂 = 两个单臂各干各的"
新手想法:"左臂负责左半边任务,右臂负责右半边,各自规划就行"
实际上:即使两臂操作不同物体(解耦任务),它们共享工作空间,
必须进行碰撞回避协调。而紧耦合任务(共持物体)中,
两臂的运动学和力学完全耦合,独立规划会导致物体受到
不可控的内力(force fight),可能损坏物体或夹爪。
正确理解:双臂系统是一个 14-DOF 的整体,需要在联合 C-space 中规划。
🧠 思维陷阱:认为"自由度越多越好,双臂一定比单臂更容易"
新手想法:"14-DOF 比 7-DOF 冗余度更高,任务应该更容易完成"
实际上:更多自由度意味着更高维的 C-space(14 维 vs 7 维),
采样规划的效率急剧下降(维度灾难)。更多自由度也意味着
更多的自碰撞可能性——两臂互相碰撞是双臂系统最常见的故障之一。
高冗余度带来的好处(零空间利用)需要精心的数学框架才能释放。
正确思维:双臂的优势不在于"自由度多",而在于"闭链约束带来的新能力"。
练习¶
- [基础] 列举 5 个日常双臂任务,分析每个任务中"为什么一只手不够"——是载荷不足、自由度不足、还是需要内力?
- [思考] 如果给单臂配备一个能旋转的底座(增加 1 个 DOF 变成 8-DOF),能否替代双臂完成"拧瓶盖"任务?为什么?
- [计算] 双 Franka Panda(14-DOF)控制两个末端位姿(12 维)后,零空间维度是多少?如果两臂刚性共持物体(增加 6 维约束),零空间维度变为多少?
D1.2 双臂任务分类法——什么任务需要两只手 ⭐⭐¶
动机:分类的必要性¶
面对一个新的操作任务,工程师需要回答的第一个问题是:"这个任务需要几只手?如果需要两只手,两只手之间是什么关系?" 不同的关系决定了完全不同的控制方案——紧耦合任务需要协调力控,松耦合任务只需时序协调,解耦任务只需碰撞回避。
如果不做分类就开始编程,后果是严重的:你可能为一个解耦任务搭建了复杂的协调力控框架(过度设计),或者为一个紧耦合任务只做了独立规划(控制不足导致 force fight)。
反事实推理:如果对所有双臂任务都使用统一的协调控制方案(如全部用 Object Impedance),会怎样? 对于解耦任务(左手拿碗、右手拿勺):物体阻抗控制是多余的,增加了不必要的计算开销,而且由于两个末端之间没有物理耦合,"虚拟弹簧"可能引入不自然的力反馈。 对于松耦合任务(折衣服):刚性内力模型不适用,因为衣物是柔性体,需要变形模型而非刚体模型。
Koivo-Bekey 1988 三分类法 ⭐⭐¶
这是最早也是最经典的双臂任务分类,至今仍是标准术语的来源。Koivo 和 Bekey 在 1988 年 IEEE Workshop 上提出,基于**两臂之间的物理耦合程度**划分:
分类 1:紧耦合(Tightly Coupled)
定义:两臂通过共持同一刚性物体形成物理闭链
约束:h(q_L, q_R) = 0,6 维等式约束(相对位姿固定)
特征:
- 两臂运动学耦合:左臂动 → 物体动 → 右臂必须跟随
- 存在内力空间:挤压/拉伸/扭转/剪切/弯曲
- 控制需求:协调力控(Object Impedance + 内力管理)
典型任务:
① 抬钢板/搬箱子:两手抓住刚性物体两侧
② 装配(螺栓+螺母):一手持螺栓一手拧螺母,形成临时闭链
③ 焊接夹持:一手持工件一手持焊枪,工件位姿由持件手精确控制
数学模型:
T_R(q_R) = T_L(q_L) · T_{LR}
其中 T_{LR} ∈ SE(3) 是左末端到右末端的固定相对变换
微分:J_R q̇_R = J_L q̇_L → J_rel · q̇ = 0
分类 2:松耦合(Loosely Coupled)
定义:两臂操作同一物体但不形成刚性闭链
约束:软约束或时序约束,无严格等式约束
特征:
- 两臂运动有关但不严格锁定
- 不存在刚性内力(物体可变形或非同时接触)
- 控制需求:协调规划 + 时序同步 + 独立位控
典型任务:
① 折叠衣物:各抓一角,同步运动使衣物对折
② 手递手(handover):一手递出一手接住,有短暂共持阶段
③ 拉拉链:一手固定拉链底部,另一手拉拉链头
④ 倒水:一手持杯一手持壶,壶嘴对准杯口
数学模型:
无严格等式约束 h(q_L, q_R) = 0
有软约束或不等式约束:
‖p_L(q_L) - p_R(q_R)‖ ≤ d_max (两手距离约束)
t_R - t_L ≤ Δt_max (时序约束)
分类 3:解耦(Decoupled)
定义:两臂各自操作不同物体,无物理交互
约束:仅需碰撞回避
特征:
- 两臂运动学独立
- 唯一的耦合是共享工作空间中的碰撞约束
- 控制需求:独立规划 + 碰撞回避
典型任务:
① 左手拿碗、右手拿筷子(在拿起阶段)
② 双线装配:两臂同时在不同位置插入螺栓
③ 分拣:两臂同时从传送带拣取不同物体
数学模型:
独立运动学:ẋ_L = J_L(q_L) q̇_L, ẋ_R = J_R(q_R) q̇_R
碰撞约束:d(q_L, q_R) ≥ d_min(两臂最近点距离)
三分类法的决策流程:
两臂是否物理接触同一物体?
├── 否 → 解耦(Decoupled)
│ 控制方案:独立规划 + 碰撞回避
└── 是 → 物体是否刚性且两臂同时抓持?
├── 是 → 紧耦合(Tightly Coupled)
│ 控制方案:协调力控 + 内力管理
└── 否 → 松耦合(Loosely Coupled)
控制方案:协调规划 + 时序同步
从 Koivo 到现代基准——分类法的演进 ⭐⭐¶
Koivo 三分类法在 1988 年提出后长期作为双臂研究的标准术语,但随着任务复杂度提升,其局限性逐渐显现。主要问题包括:
- 单维度不足以描述复杂任务:三分类只基于"物理耦合程度"一个维度,无法区分"两手对称搬运"和"一手固定一手操作"这两类在控制策略上完全不同的任务。
- 静态标签与动态过程的矛盾:真实任务通常跨越多个分类(如 handover 从解耦到紧耦合再到解耦),三个离散标签无法描述连续的过渡过程。
- 缺乏对手角色的建模:很多双臂任务中两手的功能不对等(如一手切菜一手按住食材),Koivo 分类不区分手的角色。
这些局限推动了更细粒度的分类体系的发展。2024-2025 年,随着 PerAct2(Grotz et al. 2024)、BRMData(Zhang et al. 2024)等大规模双臂基准数据集的出现,研究者开始将任务按**轨迹-片段-帧**三个层级标注,每个层级显式标注子任务角色和协调模式——这为学习型双臂方法提供了前所未有的结构化训练信号。AIST Bimanual Manipulation Dataset 更是提供了超过 10000 个 episode、覆盖 100+ 种不同任务的大规模数据,从基础拾取放置到复杂装配和协调双臂活动。
Krebs-Asfour 2022 四维分类学 ⭐⭐¶
Krebs 和 Asfour 2022 年在 IEEE RA-L 上提出了更细粒度的分类体系。与 Koivo 的单维度(耦合程度)不同,Krebs 用**四个正交维度**刻画双臂任务——这意味着同一任务在每个维度上独立取值,总共 \(2 \times 3 \times 3 \times 3 = 54\) 种组合(去除不合理的组合后约 20-30 种有效组合)。
维度 1——Coordination(时序协调性)
| 取值 | 定义 | 示例 |
|---|---|---|
| 同步(Simultaneous) | 两手同时运动 | 搬箱子 |
| 异步(Sequential) | 一手先动另一手后动 | 穿针引线(先穿后拉) |
维度 2——Interaction(交互方式)
| 取值 | 定义 | 示例 |
|---|---|---|
| 物理交互 | 两手通过物体物理耦合 | 共持刚性物体 |
| 空间交互 | 两手在共享工作空间中避碰 | 双臂分拣 |
| 无交互 | 两手在独立空间各自操作 | 左右两台机器分别操作 |
维度 3——Hand Role(手的角色)
| 取值 | 定义 | 示例 |
|---|---|---|
| 对称(Symmetric) | 两手等价,可互换 | 抬大箱子 |
| 主导+辅助(Dominant+Assistive) | 一手操作一手扶持/稳定 | 一手切菜一手按住食材 |
| 独立(Independent) | 各自执行不同任务 | 左手搅拌右手加料 |
维度 4——Symmetry(运动对称性)
| 取值 | 定义 | 示例 |
|---|---|---|
| 镜像对称 | 两手运动关于中面对称 | 搓手取暖 |
| 平移对称 | 两手做相同但位移的运动 | 两手同时按相邻按钮 |
| 非对称 | 两手运动完全不同 | 拧瓶盖(一手固定一手旋转) |
两套分类法的交叉标注实例¶
理解分类法的最佳方式是对具体任务进行标注——发现"同一任务在不同分类法下有不同标签":
| 任务 | Koivo | Krebs (Coord, Inter, Role, Sym) | 分歧与讨论 |
|---|---|---|---|
| 拧瓶盖 | 紧耦合 | (同步, 物理, 主导+辅助, 非对称) | 拧的过程是紧耦合,但松开手的瞬间变为松耦合 |
| 折毛巾 | 松耦合 | (同步, 物理, 对称, 镜像) | Koivo 分类为松耦合因为衣物可变形 |
| 穿针引线 | 松耦合 | (异步, 物理, 主导+辅助, 非对称) | 左手持针(辅助),右手穿线(主导) |
| 抬桌子 | 紧耦合 | (同步, 物理, 对称, 平移) | 两人对称用力,是最典型的紧耦合 |
| 搅拌(左碗右勺) | 解耦→松耦合 | (同步, 空间→物理, 主导+辅助, 非对称) | 拿起阶段解耦,搅拌阶段勺子在碗里变为物理交互 |
关键观察:许多真实任务在执行过程中会**在分类之间切换**。拧瓶盖从"紧耦合"到"解耦"(瓶盖拧开后),手递手从"解耦"到"紧耦合"到"解耦"(接近→共持→释放)。这种动态切换是双臂规划中最困难的问题之一,也是 D02 中 TAMP(Task and Motion Planning)要解决的核心问题。
本质洞察:双臂任务分类不是静态标签,而是随操作阶段变化的动态过程。一个完整的双臂操作通常涉及多个"模式"(mode)的切换——每个模式对应不同的运动学约束和控制策略。这种 mode switching 正是双臂操作比单臂更难的根本原因之一。
分类法对控制方案的映射 ⭐⭐¶
分类不是学术游戏——它直接决定了工程实现的选择:
| Koivo 分类 | 运动学框架 | 力控策略 | 规划方法 | 典型控制频率 |
|---|---|---|---|---|
| 紧耦合 | \(J_{\text{rel}}\) 零空间投影 | Object Impedance + 内力 | 约束流形采样 | 1 kHz |
| 松耦合 | 独立 \(J_L, J_R\) + 时序协调 | 独立阻抗 + 力阈值切换 | 多阶段规划 | 500 Hz |
| 解耦 | 独立 \(J_L, J_R\) | 独立位控 | 独立规划 + 碰撞回避 | 100-500 Hz |
Krebs 四维到控制策略的映射:
Krebs 分类提供了更细粒度的控制策略选择依据:
| Krebs 维度取值 | 控制策略影响 |
|---|---|
| 同步 + 物理交互 + 对称 | 绝对/相对分解(Chiacchio),对称阻抗参数 |
| 同步 + 物理交互 + 主导+辅助 | 主从分解,主臂位控 + 从臂阻抗跟随 |
| 异步 + 物理交互 | FSM/BT 管理阶段切换,每阶段独立控制器 |
| 同步 + 空间交互 | 独立规划 + 实时碰撞回避(如 CHOMP 势场) |
| 镜像对称运动 | 主臂规划 + 从臂镜像映射 \(q_R = \text{mirror}(q_L)\) |
| 非对称运动 | 独立轨迹生成,时序同步层确保协调 |
不是X而是Y:分类到控制方案的映射不是"一劳永逸"的静态选择,而是需要在任务执行中动态切换的。一个完整的双臂装配任务可能依次经历"解耦(各自抓取零件)→ 松耦合(对准)→ 紧耦合(插装)→ 解耦(分别撤退)"四个阶段,每个阶段使用不同的控制策略。这种动态切换由有限状态机(FSM)或行为树(Behavior Tree)管理——D09 中会详细介绍。
⚠️ 常见陷阱¶
💡 概念误区:认为"Koivo 三分类和 Krebs 四维分类是竞争关系"
新手想法:"Krebs 2022 的分类更新、更好,所以 Koivo 1988 过时了"
实际上:两套分类法是互补的。Koivo 分类基于物理耦合程度,直接决定
控制方案的大类选择(力控/位控/混合)。Krebs 分类提供更细粒度的
任务描述,在学习型双臂控制(D04)中用于构建任务表征。
实践中通常先用 Koivo 确定控制框架,再用 Krebs 细化参数设计。
正确做法:两套分类法配合使用,不是非此即彼。
⚠️ 编程陷阱:分类标签硬编码
错误做法:在控制器中写 if (task == "tightly_coupled") {...}
现象:无法处理任务类型在执行过程中动态切换的情况
根本原因:真实双臂任务通常跨越多个分类(如 handover 从解耦→紧耦合→解耦)
正确做法:用有限状态机(FSM)或 Behavior Tree 管理模式切换,
每个模式对应一个控制策略。在 D09(MoveIt2 系统集成)中会详细讲解。
练习¶
-
[分类标注] 对以下 5 个任务用 Koivo 三分类和 Krebs 四维分类法分别标注:①开瓶盖 ②折叠T恤 ③两人搬沙发 ④一手持锅一手翻炒 ⑤剪纸。写出标注过程中的争议点。
-
[思考] 一个任务是"紧耦合"还是"松耦合",取决于物体刚度。如果被操作物体的刚度 \(k\) 从 \(\infty\)(完全刚性)连续变化到 0(完全柔性),任务分类如何渐变?在什么刚度阈值下,应该从"紧耦合力控"切换到"松耦合位控"?
-
[跨章综合] 回顾 F01 中的阻抗控制和导纳控制二分法。紧耦合双臂任务应该用阻抗控制还是导纳控制?提示:考虑两臂之间是否存在"环境"——物体就是双臂之间的"虚拟环境"。
D1.3 运动学约束分类——开链、闭链与虚拟闭链 ⭐⭐¶
动机:约束决定自由度¶
在 D1.2 中我们从"任务"角度分类了双臂操作。现在换到"运动学"角度——两臂之间的几何约束如何影响系统的有效自由度?
这个问题直接决定了运动规划的维度:14-DOF 双臂在约束下的有效 C-space 可能低至 8 维,规划在 8 维流形上比在 14 维自由空间中更高效但更复杂。
开链构型(Open Chain) ⭐¶
定义:两臂独立运动,不通过物体形成回路
拓扑: Base ─── Arm_L ─── EE_L
Base ─── Arm_R ─── EE_R
两条独立的串联链
运动学:
q = [q_L^T, q_R^T]^T ∈ R^14
ẋ_L = J_L(q_L) · q̇_L (6×7)
ẋ_R = J_R(q_R) · q̇_R (6×7)
约束:无(或仅碰撞约束 d(q_L, q_R) ≥ d_min)
有效 DOF:14
适用:解耦任务、接近阶段
闭链构型(Closed Chain) ⭐⭐¶
定义:两臂通过共持刚性物体形成机械回路
拓扑: Base ─── Arm_L ─── EE_L ─── Object ─── EE_R ─── Arm_R ─── Base
形成一个回路(loop)
运动学约束:
h(q) = log(T_{rel,fixed}^{-1} · T_L(q_L)^{-1} · T_R(q_R))^∨ = 0 ∈ R^6
其中:
T_L(q_L), T_R(q_R) ∈ SE(3):两臂末端的正运动学
T_{rel,fixed} ∈ SE(3):右末端相对左末端的固定变换
log(·)^∨: SE(3) → R^6:对数映射提取 twist 坐标
约束 Jacobian:
J_h(q) · q̇ = 0 → J_rel · q̇ = 0
先把左右末端 twist 变换到同一表达系、同一参考点 p_c:
J̄_i = X(p_c - p_i) · J_i, i ∈ {L,R}
再做差:
J_rel = J̄_R - J̄_L ∈ R^{6×14}
有效 DOF:14 - 6 = 8(约束流形维度)
这里 \(X(d)\) 是同轴表达下的 twist 平移算子。按本文 \([v;\omega]\) 顺序,\(X(d)=\begin{bmatrix}I&-[d]_\times\\0&I\end{bmatrix}\),其中 \(d=p_c-p_i\)。如果左右 Jacobian 来自 LOCAL_WORLD_ALIGNED,它们只是在世界方向上表达,线速度仍分别对应各自末端原点;做闭链/相对位姿约束前仍必须用这个平移项统一参考点。
为什么闭链约束是 6 维?
两个刚体的相对位姿有 6 个自由度(3 平移 + 3 旋转)。刚性共持意味着相对位姿固定——6 个自由度全部被约束,因此有 6 维等式约束。
如果两臂不是刚性共持,而是通过铰链连接(如两臂各抓门的一边),则约束可能少于 6 维——铰链允许 1 个旋转自由度,约束变为 5 维,有效 DOF = 14 - 5 = 9。
虚拟闭链(Virtual Closed Chain) ⭐⭐¶
虚拟闭链是现代双臂控制中最灵活的约束范式。它不依赖物理接触来建立约束,而是通过控制器"软件定义"约束关系——这使得约束可以动态调整、渐进建立或逐步释放,非常适合模式切换频繁的双臂任务。
定义:两臂在任务空间中有虚拟约束,但不形成物理回路
特征:约束通过控制器强制施加,而非物理机构
典型场景:
① 虚拟弹簧:两末端之间连一根"弹簧"(阻抗控制实现)
F = K(x_R - x_L - x_{rel,des})
当 K → ∞ 时退化为刚性闭链
② 编队控制:两末端保持固定队形运动(如平行搬运托盘)
x_rel(t) = x_{rel,des}(t)(时变约束)
③ 力约束:两末端合力满足某关系
f_L + f_R = f_des(力平衡约束)
运动学:
约束通过控制律而非几何关系实现:
τ = J^T · f_ctrl + ...
f_ctrl 包含约束力项
有效 DOF:取决于约束刚度
K → ∞:等价于闭链,8 DOF
K → 0:等价于开链,14 DOF
中间值:连续过渡
跨领域类比:闭链约束 vs 电路约束。闭链约束(\(J_{\text{rel}} \dot{q} = 0\))类似于 Kirchhoff 电压定律(回路电压和为零)——都是因为形成了回路而产生的约束。虚拟闭链类似于电路中的电子反馈——通过控制器(运算放大器)人为建立约束关系,而非物理连接。
虚拟闭链的一个重要应用是**渐进约束建立**:在 handover 任务中,接近阶段两臂是开链(无约束),接触瞬间建立虚拟弹簧约束(\(K\) 从 0 渐增),最终过渡到近似刚性闭链(\(K\) 足够大时)。控制器参数 \(K\) 的连续调节使得模式切换不再是离散跳变,避免了传统 FSM 中"硬切换"导致的力/速度不连续。
三种约束的统一视角:
| 约束类型 | 约束方程 | 约束来源 | 有效 DOF | 鲁棒性 |
|---|---|---|---|---|
| 开链 | 无 | — | 14 | N/A |
| 闭链 | \(h(q) = 0\) | 物理接触 | 8 | 取决于接触稳定性 |
| 虚拟闭链 | \(h(q) \approx 0\) | 控制器 | 8-14(连续) | 取决于控制器增益 |
⚠️ 常见陷阱¶
🧠 思维陷阱:认为"闭链约束是精确的"
新手想法:"两臂共持刚性物体时,J_rel · q̇ = 0 严格成立"
实际上:由于关节编码器精度(±0.01°)、控制器延迟(~1ms)、
物体弹性(钢的 Young 模量虽大但不是无穷大),
闭链约束只是近似满足。实际的约束违反量 ‖h(q)‖ 通常在
0.1-1mm 级别。如果控制器不主动补偿,累积误差会导致
内力不可控增长——这就是 D03 中内力控制的动机。
正确理解:闭链约束是"几乎满足"的,需要控制器主动维护。
练习¶
-
[推导] 对于双 6-DOF 臂(如双 UR5e,总 DOF=12),刚性共持物体后有效 DOF = 12 - 6 = 6。这意味着零空间维度为 0——两臂没有冗余度做碰撞回避或操作度优化。这在工程上意味着什么?与双 7-DOF 臂的情况对比。
-
[思考] 如果两臂通过一根弹性杆(而非刚性杆)连接,闭链约束的维度是否还是 6?提示:弹性杆可以变形,两端的相对位姿不再固定。
D1.4 增广 Jacobian——把两只手看作一个 12 维末端 ⭐⭐¶
动机:为什么需要联合描述¶
单臂的雅可比 \(J \in \mathbb{R}^{6 \times 7}\) 把关节速度映射到末端速度——这是单臂运动控制的核心工具。对于双臂,我们有两个末端需要同时控制。最直接的做法是把两个末端"拼在一起",构成一个 12 维的"虚拟末端"。
回顾 M01:Pinocchio 中用 getFrameJacobian() 计算单个末端的雅可比矩阵。对于双臂模型,两个末端的雅可比可以简单地堆叠。
数学定义 ⭐⭐¶
设双臂系统关节向量 \(q = [q_L^T, q_R^T]^T \in \mathbb{R}^{14}\)(双 7-DOF),两臂末端 twist 分别为 \(\mathcal{V}_L, \mathcal{V}_R \in \mathbb{R}^6\)。
单臂雅可比(在联合关节空间中表达):
注意:当两臂共享同一个 Pinocchio Model(合并 URDF)时,getFrameJacobian 返回的就是 \(6 \times 14\) 矩阵,非零列自动对应该臂的关节。
增广 Jacobian:
增广 Jacobian 的逆运动学 ⭐⭐¶
\(J_{\text{aug}}\) 是 \(12 \times 14\) 矩阵,行数 < 列数,系统是**冗余的**(14 个输入、12 个输出)。逆运动学使用阻尼伪逆:
其中阻尼伪逆:
这里的 \(J^\dagger_\lambda\) 是**阻尼最小二乘(DLS)任务滤波器**,用于奇异附近稳定求解,不是严格的 Moore-Penrose 伪逆。若 \(\lambda>0\),则
一般不满足 \(N_\lambda^2=N_\lambda\)、\(N_\lambda^T=N_\lambda\),也不保证 \(JN_\lambda=0\) 精确为零。需要严格零空间投影时,应使用 Moore-Penrose/SVD 截断伪逆;需要奇异鲁棒控制时,使用 DLS 并把验证指标改为 \(\|J N_\lambda \dot{q}_0\|\) 或任务残差,而不是幂等性。
阻尼系数的选择:
| 场景 | \(\lambda\) | 理由 |
|---|---|---|
| 远离奇异 | 0.01 | 精度优先,最小阻尼 |
| 中等奇异 | 0.05-0.1 | 精度与稳定性平衡 |
| 接近奇异 | 0.1-0.5 | 稳定性优先,牺牲精度 |
| 自适应 | \(\lambda = \begin{cases} \lambda_0 \cdot \sqrt{1 - (w/w_0)^2}, & w < w_0 \\ 0, & w \ge w_0 \end{cases}\) | Nakamura 自适应阻尼 |
其中 \(w = \sqrt{\det(J_{\text{aug}} J_{\text{aug}}^T)}\) 是操作度(manipulability),\(w_0\) 是操作度阈值。当 \(w \ge w_0\)(远离奇异)时阻尼归零以保证精度。
增广 Jacobian 的秩分析与条件数 ⭐⭐¶
\(J_{\text{aug}}\) 的秩和条件数直接影响逆运动学的质量:
| 指标 | 计算方式 | 物理含义 | 典型安全阈值 |
|---|---|---|---|
| 秩 \(r\) | \(\sum_{i} [\sigma_i > \epsilon]\) | 可独立控制的末端速度维度 | \(r = 12\)(满秩) |
| 条件数 \(\kappa\) | \(\sigma_{\max} / \sigma_{\min}\) | 末端速度→关节速度的放大比 | \(\kappa < 100\) |
| 操作度 \(w\) | \(\sqrt{\det(J_{\text{aug}} J_{\text{aug}}^T)}\) | 各方向操纵能力的几何平均 | \(w > 0.01\) |
当 \(\kappa > 1000\) 时,即使使用阻尼伪逆,关节速度仍可能出现数值级的放大——此时应降低末端速度指令或切换到更保守的构型。对于双 7-DOF 臂,\(\kappa\) 在两臂完全对称的构型下通常最小(条件最优),在一臂接近奇异时急剧增大。
条件数与控制频率的关系:高条件数意味着对关节编码器噪声的敏感度增大。在 1 kHz 控制频率下,编码器噪声(约 \(10^{-4}\) rad)经过 \(\kappa = 1000\) 放大后变为 \(10^{-1}\) rad/s 的末端速度噪声——这对精密操作是不可接受的。因此在紧耦合任务中,应在规划阶段就避免高条件数的构型。
零空间利用 ⭐⭐¶
零空间维度:\(n - \text{rank}(J_{\text{aug}}) = 14 - 12 = 2\)(假设远离奇异,\(J_{\text{aug}}\) 满秩)。
这 2 维零空间可用于:
用途 1:关节极限回避
关节越接近极限,梯度越大,零空间速度越强地推动关节远离极限。
用途 2:操作度最大化
用途 3:自碰撞回避
其中 \(d_{\min}\) 是两臂最近点距离——这是双臂独有的零空间任务。
反事实推理:如果不使用零空间自碰撞回避,双臂在狭小工作空间中操作时极易碰撞。在 MuJoCo 仿真中,双 Franka Panda 做独立随机运动时,约 15% 的构型会导致两臂碰撞。加入零空间自碰撞回避后,碰撞率降到 < 1%。
Pinocchio 实现¶
// ============================================================
// 增广 Jacobian 计算——Pinocchio C++ 实现
// 前提:双臂合并为一个 URDF(通过 <joint type="fixed"> 连接)
// ============================================================
#include <pinocchio/algorithm/joint-configuration.hpp>
#include <pinocchio/algorithm/kinematics.hpp>
#include <pinocchio/algorithm/frames.hpp>
#include <pinocchio/parsers/urdf.hpp>
// 1. 加载双臂模型
pinocchio::Model model;
pinocchio::urdf::buildModel("dual_panda.urdf", model);
pinocchio::Data data(model);
// 2. 获取末端帧 ID
const auto left_ee_id = model.getFrameId("panda_left_hand");
const auto right_ee_id = model.getFrameId("panda_right_hand");
// 3. 计算正运动学(必须先调用!否则 Jacobian 基于未初始化数据)
Eigen::VectorXd q = pinocchio::neutral(model); // 14 维
pinocchio::forwardKinematics(model, data, q);
pinocchio::updateFramePlacements(model, data);
// 4. 计算两臂 Jacobian(LOCAL_WORLD_ALIGNED 坐标系)
Eigen::Matrix<double, 6, Eigen::Dynamic> J_L(6, model.nv);
Eigen::Matrix<double, 6, Eigen::Dynamic> J_R(6, model.nv);
J_L.setZero();
J_R.setZero();
pinocchio::computeFrameJacobian(model, data, q, left_ee_id,
pinocchio::LOCAL_WORLD_ALIGNED, J_L); // 6×14
pinocchio::computeFrameJacobian(model, data, q, right_ee_id,
pinocchio::LOCAL_WORLD_ALIGNED, J_R); // 6×14
// 5. 增广 Jacobian: 垂直堆叠
Eigen::Matrix<double, 12, Eigen::Dynamic> J_aug(12, model.nv);
J_aug << J_L, J_R; // 12×14
// 6. 阻尼伪逆
double lambda = 0.05;
Eigen::Matrix<double, 12, 12> JJt = J_aug * J_aug.transpose()
+ lambda * lambda * Eigen::Matrix<double, 12, 12>::Identity();
Eigen::Matrix<double, Eigen::Dynamic, 12> J_aug_pinv
= J_aug.transpose() * JJt.inverse(); // 14×12
// 7. 阻尼零空间滤波器(不是严格正交投影)
Eigen::MatrixXd N_aug_dls = Eigen::MatrixXd::Identity(model.nv, model.nv)
- J_aug_pinv * J_aug; // 14×14
// ============================================================
// ❌ 常见错误 1:忘记调用 forwardKinematics
// ============================================================
// pinocchio::forwardKinematics(model, data, q); // 被注释掉了!
pinocchio::computeFrameJacobian(model, data, q, left_ee_id,
pinocchio::LOCAL_WORLD_ALIGNED, J_L);
// → J_L 基于上一次 FK 的结果(可能是零初始化),数值完全错误
// → 不会报错!只是 Jacobian 值不对,导致 IK 发散
// ============================================================
// ❌ 常见错误 2:使用错误的参考坐标系
// ============================================================
pinocchio::computeFrameJacobian(model, data, q, left_ee_id,
pinocchio::LOCAL, J_L); // LOCAL 坐标系!
pinocchio::computeFrameJacobian(model, data, q, right_ee_id,
pinocchio::LOCAL_WORLD_ALIGNED, J_R); // WORLD_ALIGNED 坐标系!
// → 两个 Jacobian 在不同坐标系下,直接堆叠后的 J_aug 物理含义错误
// → 解出的 q̇ 会让两臂向错误的方向运动
// ✅ 正确做法:统一使用 LOCAL_WORLD_ALIGNED
// 原因:LOCAL_WORLD_ALIGNED 使线速度和角速度都在世界系方向上表达。
// 注意:它不会把线速度参考点统一到同一个原点。做 J_aug 堆叠没问题,
// 但做相对/闭链 Jacobian 时,还要用 adjoint/平移项把 twist 移到同一参考点。
增广 Jacobian 实战:Python 快速验证¶
C++ 代码用于实时控制,但开发调试阶段用 Python 更高效。以下 Pinocchio Python 代码可直接用于验证 \(J_{\text{aug}}\) 的正确性:
# ============================================================
# 增广 Jacobian——Pinocchio Python 实战验证
# ============================================================
import pinocchio as pin
import numpy as np
# 加载双臂模型
model = pin.buildModelFromUrdf("dual_panda.urdf")
data = model.createData()
# 帧 ID
left_ee = model.getFrameId("panda_left_hand")
right_ee = model.getFrameId("panda_right_hand")
# 随机构型
q = pin.randomConfiguration(model)
pin.forwardKinematics(model, data, q)
pin.updateFramePlacements(model, data)
pin.computeJointJacobians(model, data, q)
# 计算两臂 Jacobian
J_L = pin.getFrameJacobian(model, data, left_ee,
pin.LOCAL_WORLD_ALIGNED) # 6×14
J_R = pin.getFrameJacobian(model, data, right_ee,
pin.LOCAL_WORLD_ALIGNED) # 6×14
# 增广 Jacobian
J_aug = np.vstack([J_L, J_R]) # 12×14
# 验证 1:秩(远离奇异时应为 12)
U, S, Vt = np.linalg.svd(J_aug)
rank = np.sum(S > 1e-6)
print(f"rank(J_aug) = {rank}, 零空间维度 = {14 - rank}")
# 期望输出: rank = 12, 零空间维度 = 2
# 验证 2:阻尼伪逆 + 任务滤波器
lam = 0.05
J_pinv = J_aug.T @ np.linalg.inv(J_aug @ J_aug.T
+ lam**2 * np.eye(12))
N = np.eye(14) - J_pinv @ J_aug
# 验证 3:DLS 只近似抑制任务速度,不是精确零空间
dq_null = N @ np.random.randn(14)
V_aug = J_aug @ dq_null
print(f"‖J_aug · q̇_filtered‖ = {np.linalg.norm(V_aug):.2e}")
# 期望输出:小于未滤波速度的任务范数,但不会达到 1e-15。
# 若要验证严格零空间,使用 SVD/Moore-Penrose 伪逆:
J_mp = np.linalg.pinv(J_aug, rcond=1e-6)
P_mp = np.eye(14) - J_mp @ J_aug
print(f"‖J_aug · P_mp‖ = {np.linalg.norm(J_aug @ P_mp):.2e}")
# 期望输出: ≈ 1e-12(取决于模型尺度与 SVD 阈值)
工程建议:开发流程为「Python 原型 → 数值验证 → C++ 移植」。Python 脚本可以在几分钟内验证数学公式正确性,避免在 C++ 编译-调试循环中浪费时间。
⚠️ 常见陷阱¶
💡 概念误区:认为"J_aug 是分块对角的"总是成立
新手想法:"左臂 Jacobian 只涉及 q_L,右臂只涉及 q_R,所以 J_aug 是分块对角"
实际上:这只在两臂关节独立时成立。如果两臂安装在同一移动平台上(如 PR2),
平台运动影响两臂末端位姿,此时 J_L 和 J_R 都包含平台关节列——
J_aug 不再是分块对角的。
正确理解:分块对角是固定基座双臂的特殊情况。对于移动双臂或人形双臂,
必须使用完整的 Jacobian。
⚠️ 编程陷阱:J_aug 维度不匹配
错误做法:J_aug 声明为 Matrix<double, 12, 14> 但模型是 13-DOF(6+7 不对称双臂)
现象:编译通过但运行时 Eigen assertion 失败,或更糟——静默产生错误结果
正确做法:使用动态大小 MatrixXd 或从 model.nv 获取列数
练习¶
-
[计算] 对于双 Franka Panda(14-DOF),\(J_{\text{aug}}\) 是 \(12 \times 14\)。如果两臂都处于奇异位形(如完全伸直),\(\text{rank}(J_{\text{aug}})\) 可能降到多少?零空间维度相应变为多少?
-
[编程] 用 Pinocchio Python 绑定计算双 Franka Panda 的 \(J_{\text{aug}}\)。绘制操作度 \(w = \sqrt{\det(J_{\text{aug}} J_{\text{aug}}^T)}\) 随 \(q_1\)(左臂第一关节)变化的曲线。找到 \(w\) 最大和最小的构型。
D1.5 相对 Jacobian——描述两臂相对运动 ⭐⭐¶
动机:从绝对到相对¶
\(J_{\text{aug}}\) 描述了两臂末端在世界系中的绝对运动。但在紧耦合任务中,我们更关心的不是"左手在世界系中移动了多少",而是"右手相对于左手运动了多少"——因为这直接决定了共持物体是否被挤压或拉伸。
这就是**相对 Jacobian** \(J_{\text{rel}}\) 的动机:它把关节速度映射到两臂末端的相对 twist。
数学推导 ⭐⭐¶
在世界系中,两臂末端的速度分别为:
但这一步有一个容易被忽略的前提:两个 twist 必须在同一表达系、同一参考点上表达。LOCAL_WORLD_ALIGNED 只统一了坐标轴方向,并没有把线速度的参考点从各自末端移动到同一个点。刚体转动时,两端线速度本来就不同,直接做 \(J_R-J_L\) 会把正常的绕物体转动误判成相对运动。
选择一个公共参考点 \(p_c\)(常用物体质心或左末端原点),先把两个 twist 平移到 \(p_c\):
其中本文 twist 顺序为 \([v;\omega]\)。如果使用 \([\omega;v]\) 顺序,矩阵块的位置和符号需要相应置换。
于是:
定义相对 twist 为右末端相对左末端的同点差:
因此:
只有在两个 Jacobian 已经表达在同一参考点(或任务只看两个点的世界系位置差、且不把它当成完整 SE(3) 相对 twist)时,才可以把它简写成 \(J_R-J_L\)。闭链刚性抓取和相对位姿约束不能省略这一步。
关键性质:
| 性质 | 表达式 | 物理含义 |
|---|---|---|
| 刚性约束 | \(J_{\text{rel}} \cdot \dot{q} = 0\) | 关节速度使两末端相对位姿不变 |
| 零空间 | \(\ker(J_{\text{rel}}) \subset \mathbb{R}^{14}\) | 满足闭链约束的关节速度集合 |
| 维度 | \(\dim(\ker(J_{\text{rel}})) = 14 - 6 = 8\) | 约束流形的切空间维度 |
物理含义的深入理解¶
\(J_{\text{rel}}\) 的零空间有一个优美的物理含义:零空间中的关节速度使两臂末端"同步运动"——如同它们被一根无限刚性的杆连接。
反过来,\(J_{\text{rel}}\) 的行空间中的关节速度会改变两末端的相对位姿——如果两臂正在共持刚性物体,这些速度会产生内力(挤压/拉伸)。
这就是为什么在刚性共持任务中,关节速度必须被投影到 \(\ker(J_{\text{rel}})\) 中——否则会产生不可控的内力。
本质洞察:\(J_{\text{rel}}\) 的零空间是"安全运动空间"——在这个空间中运动不会改变两臂的相对关系,因此不会产生额外内力。\(J_{\text{rel}}\) 的行空间是"危险运动空间"——任何在这个空间中的运动分量都会改变相对位姿,对刚性共持物体产生力。控制器的任务就是:让有用的运动尽量在零空间中,让必要的相对运动(如打开/关闭夹爪距离)通过力控管理。
零空间投影矩阵 ⭐⭐¶
这里 \(J_{\text{rel}}^+\) 指 Moore-Penrose 伪逆或等价的 SVD 截断伪逆。只有在这个前提下,\(P\) 才是正交投影矩阵。
Moore-Penrose 投影矩阵的性质:
如果把 \(J_{\text{rel}}^+\) 换成阻尼伪逆 \(J_{\text{rel},\lambda}^\dagger\),则 \(P_\lambda=I-J_{\text{rel},\lambda}^\dagger J_{\text{rel}}\) 只是**阻尼任务滤波器**。它牺牲严格投影性质换取奇异附近的数值稳定性,工程自检应关注 \(\|J_{\text{rel}}P_\lambda\dot{q}_0\|\) 是否足够小,而不是要求 \(P_\lambda^2=P_\lambda\) 或 \(P_\lambda^T=P_\lambda\)。
验证方法(工程中必做的 sanity check):
// 验证 Moore-Penrose/SVD 投影 P 的性质
Eigen::MatrixXd P = Eigen::MatrixXd::Identity(14, 14)
- J_rel_pinv * J_rel;
// 1. 幂等性: P*P ≈ P
double idempotent_err = (P * P - P).norm();
assert(idempotent_err < 1e-10);
// 2. 约束满足: J_rel * P ≈ 0
double constraint_err = (J_rel * P).norm();
assert(constraint_err < 1e-10);
// 3. 零空间维度: rank(P) = 8
Eigen::JacobiSVD<Eigen::MatrixXd> svd(P);
int rank = (svd.singularValues().array() > 1e-6).count();
assert(rank == 8); // 零空间是 8 维的
// 如果使用阻尼伪逆,只验证任务泄漏,而不验证幂等/对称。
Eigen::VectorXd dq0 = Eigen::VectorXd::Random(14);
Eigen::VectorXd dq_filtered = P_dls * dq0;
double leakage = (J_rel * dq_filtered).norm();
assert(leakage < 1e-3); // 阈值按任务速度尺度设置
协调逆运动学¶
在刚性共持任务中,我们想控制物体整体运动(通过绝对变量 \(\dot{x}_{\text{abs}}\)),同时保持相对位姿不变(\(\dot{x}_{\text{rel}} = 0\)):
这个公式做了两件事: 1. \(J_{\text{abs}}^+ \cdot \dot{x}_{\text{abs}}\):计算实现物体运动所需的关节速度 2. \(P \cdot (\ldots)\):投影到 \(\ker(J_{\text{rel}})\),确保不改变相对位姿
Task-Priority 级联的完整形式:
当需要同时满足相对约束(最高优先级)和绝对任务(次高优先级)时,使用 Siciliano 1991 的 task-priority 框架:
其中 \(\bar{J}_{\text{abs}} = J_{\text{abs}} P_{\text{rel}}\)(投影后的绝对 Jacobian),\(P_{\bar{\text{abs}}} = I - \bar{J}_{\text{abs}}^+ \bar{J}_{\text{abs}}\)。
三项分别对应三个优先级: - 第一优先级:\(J_{\text{rel}} \dot{q} = 0\),保持相对位姿(刚性约束) - 第二优先级:在约束兼容的空间中跟踪绝对运动 - 第三优先级:在剩余零空间中执行辅助任务(碰撞回避、关节极限回避)
阶段小结:到这里我们完成了相对 Jacobian 的定义、零空间投影和协调逆运动学。核心理解是:\(J_{\text{rel}}\) 的零空间是"安全空间",\(P\) 是"安全过滤器",task-priority 级联是"多任务调度器"。接下来 D1.6 将把这些工具统一到 Chiacchio 的绝对/相对框架中。
⚠️ 常见陷阱¶
💡 概念误区:认为"J_rel 的定义中减号的位置无所谓"
新手想法:"J_rel = J̄_R - J̄_L 和 J_rel = J̄_L - J̄_R 效果一样"
实际上:符号约定确实是可以选择的,但必须全局一致。
如果定义 J_rel = J̄_R - J̄_L(右减左),则 v_rel > 0 意味着
右手远离左手——这是"拉伸"。如果定义 J_rel = J_L - J_R,
则 v_rel > 0 意味着左手远离右手——物理含义相反。
Chiacchio 1996 和 Caccavale 2008 使用右减左的约定。
本文档统一使用此约定。
正确做法:选择一个约定并在整个代码库中严格遵守。
在代码注释中明确写出符号约定。
⚠️ 编程陷阱:伪逆计算中的数值问题
错误做法:J_rel_pinv = J_rel.transpose() * (J_rel * J_rel.transpose()).inverse()
现象:当 J_rel 接近奇异时(如两臂末端共线),
J_rel * J_rel^T 的条件数 > 10^10,逆矩阵数值不稳定
正确做法:使用阻尼伪逆或 SVD 伪逆
// SVD 伪逆(最稳定)
Eigen::JacobiSVD<MatrixXd> svd(J_rel, ComputeThinU | ComputeThinV);
svd.setThreshold(1e-4); // 截断小奇异值
J_rel_pinv = svd.solve(MatrixXd::Identity(6, 6));
练习¶
-
[验证] 用 Pinocchio 计算双 Franka Panda 的 \(J_{\text{rel}}\),然后分别计算 Moore-Penrose 投影 \(P = I - J_{\text{rel}}^+ J_{\text{rel}}\) 与阻尼滤波器 \(P_\lambda=I-J_{\text{rel},\lambda}^\dagger J_{\text{rel}}\)。对前者验证 \(P^2=P\)、\(J_{\text{rel}}P=0\)、\(\text{rank}(P)=8\);对后者只验证随机 \(\dot{q}_0\) 下的 \(\|J_{\text{rel}}P_\lambda\dot{q}_0\|\) 是否小于任务容差。
-
[实验] 在 MuJoCo 中让双 Franka 共持一根刚性杆。分别用(a)独立位控(各臂独立跟踪目标)和(b)投影到 \(J_{\text{rel}}\) 零空间的协调位控。记录杆两端的相对位移。预测:(a)会积累 mm 级漂移,(b)保持 < 0.1mm。
D1.6 绝对/相对变量对偶——协调控制的标准语言 ⭐⭐⭐¶
动机:为什么需要变量分解¶
在 D1.4 和 D1.5 中,我们分别定义了 \(J_{\text{aug}}\)(控制两臂绝对位姿)和 \(J_{\text{rel}}\)(描述两臂相对运动)。Chiacchio, Chiaverini 和 Siciliano 在 1996 年提出了一个更优雅的框架:将双臂运动分解为"绝对变量"和"相对变量"——物理上对应"物体整体运动"和"两手相对关系"。
这个分解的深刻之处在于:它把一个 12 维的耦合问题分解为两个 6 维的(近似)解耦问题——绝对变量控制物体运动,相对变量控制抓取稳定性。两者可以独立设计控制器。
Chiacchio 1996 定义 ⭐⭐⭐¶
位姿层面的定义(位置部分):
姿态层面的定义(使用四元数的对数映射):
速度层面的定义:
速度层面的绝对/相对分解同样要求统一 twist 参考点。令 \(\bar{J}_L,\bar{J}_R\) 表示已经通过 adjoint/平移项移动到同一公共点 \(p_c\) 的 Jacobian,则:
因此:
与同点化增广 Jacobian 的关系¶
绝对/相对变量与"已经统一参考点的增广变量"之间存在精确的线性变换:
逆变换:
即:\(\bar{J}_L = J_{\text{abs}} - \frac{1}{2}J_{\text{rel}}\),\(\bar{J}_R = J_{\text{abs}} + \frac{1}{2}J_{\text{rel}}\)。
跨领域类比:绝对/相对变量 vs 共模/差模信号。在电子工程中,差分放大器接收两个输入 \(V_+, V_-\),将它们分解为共模信号 \(V_{\text{CM}} = (V_+ + V_-)/2\) 和差模信号 \(V_{\text{DM}} = V_+ - V_-\)。共模信号是噪声(需要抑制),差模信号是有用信号(需要放大)。
双臂协调中的对偶关系完全类似: - \(x_{\text{abs}}\) = 共模 = 物体整体运动(需要精确跟踪) - \(x_{\text{rel}}\) = 差模 = 两手相对关系(刚性共持时需要抑制为零)
进一步地,差分放大器的共模抑制比(CMRR)类比于双臂控制器抑制相对运动的能力——CMRR 越高,抗干扰能力越强;\(K_{\text{rel}}\)(相对刚度)越大,内力控制越精确。
刚性共持下的简化¶
当两臂刚性共持物体时,\(\dot{x}_{\text{rel}} = 0\),只有绝对变量有效:
约束 \(J_{\text{rel}} \cdot \dot{q} = 0\) 通过投影满足:
其中 \(P_{\text{rel}} = I - J_{\text{rel}}^+ J_{\text{rel}}\)。
⚠️ 关键注意:\(P_{\text{rel}}\) 投影保证 \(J_{\text{rel}} \dot{q} = 0\)(相对约束精确满足),但**不保证** \(J_{\text{abs}} \dot{q} = \dot{x}_{\text{abs}}\)(绝对任务一般会被畸变),因为 \(J_{\text{abs}}\) 与 \(J_{\text{rel}}\) 的行空间不正交。若需同时满足两个任务,应使用 task-priority cascade:\(\dot{q} = J_{\text{rel}}^+ \cdot 0 + P_{\text{rel}} \cdot \bar{J}_{\text{abs}}^+ \cdot \dot{x}_{\text{abs}}\),其中 \(\bar{J}_{\text{abs}} = J_{\text{abs}} P_{\text{rel}}\)。
非对称任务下的分解¶
并非所有双臂任务都是对称的。在"一手固定一手操作"的任务中(如拧瓶盖),变量分解的物理含义不同:
对称任务(搬箱子):
x_abs = 物体质心位姿 → 主要控制目标
x_rel = 0 → 约束(维持刚性抓取)
主导+辅助任务(拧瓶盖):
x_abs = 瓶子整体位姿 → 辅助手控制(保持不动)
x_rel = 瓶盖旋转角 → 主导手控制(旋转操作)
独立任务(左碗右勺):
x_abs 和 x_rel 都不是自然的控制目标
→ 直接使用 J_L, J_R 独立控制更自然
选择准则:
| 任务类型 | 推荐框架 | 理由 |
|---|---|---|
| 对称紧耦合 | \(J_{\text{abs}}, J_{\text{rel}}\) | 自然解耦物体运动和抓取约束 |
| 主导+辅助 | \(J_{\text{abs}}, J_{\text{rel}}\) | \(x_{\text{rel}}\) 描述操作自由度 |
| 解耦 | 独立 \(J_L, J_R\) | 绝对/相对分解无物理意义 |
| 松耦合 | 视具体任务而定 | 需要 case-by-case 分析 |
⚠️ 常见陷阱¶
💡 概念误区:认为"x_abs 就是物体质心位姿"
新手想法:"x_abs = (x_L + x_R)/2 是两末端中点,等于物体质心"
实际上:只有当两个抓取点关于物体质心对称时,x_abs 才等于物体质心位姿。
如果两个抓取点不对称(如一手抓物体顶部一手抓底部),
x_abs 是两个抓取点的中点,不等于物体质心。
在 D03(双臂力控)中,需要用 Grasp Matrix 精确计算
接触力到物体质心 wrench 的映射。
正确理解:x_abs 是两末端位姿的"平均",是物体质心的近似——
这个近似在对称抓取时是精确的,非对称时有偏差。
🧠 思维陷阱:认为"姿态的平均可以直接取算术平均"
新手想法:"R_abs = (R_L + R_R) / 2"
实际上:旋转矩阵的算术平均不再是旋转矩阵!两个 SO(3) 元素的
算术平均不保证正交性和 det=1。正确的"平均"是测地线中点:
R_abs = R_L · exp(½ log(R_L^T R_R))
这等价于在 SO(3) 流形上沿最短路径走一半。
正确做法:使用 Lie 群上的测地线中点,而非矩阵算术平均。
练习¶
-
[手推] 验证 \(J_{\text{aug}} = T^{-1} \begin{bmatrix} J_{\text{abs}} \\ J_{\text{rel}} \end{bmatrix}\) 的变换关系。写出 \(T\) 和 \(T^{-1}\) 的显式表达。
-
[精读] 精读 Chiacchio 1996 论文的 Section III。手推 \(J_{\text{abs}}, J_{\text{rel}}\) 与 \(J_{\text{aug}}\) 的关系。验证 \(J_L = J_{\text{abs}} - \frac{1}{2}J_{\text{rel}}\) 和 \(J_R = J_{\text{abs}} + \frac{1}{2}J_{\text{rel}}\)。
-
[跨章综合] 回顾 M03 中的冗余分解 \(\dot{q} = J^+ \dot{x} + (I - J^+ J) \dot{q}_0\)。将其推广到双臂绝对/相对框架:先满足绝对任务 \(\dot{x}_{\text{abs}}\),零空间中满足相对约束 \(\dot{x}_{\text{rel}} = 0\)。写出完整的关节速度表达式。
D1.7 刚性抓取约束与 C-space 流形 ⭐⭐⭐¶
动机:约束流形的几何¶
在 D1.3 和 D1.5 中我们看到,闭链约束 \(h(q) = 0\) 将 14 维 C-space 切割为 8 维子流形。这个子流形不是一个简单的超平面——它是一个弯曲的、可能有多个连通分支的光滑流形。
理解这个流形的几何性质,对 D02 中的约束运动规划至关重要——我们需要在这个弯曲的 8 维表面上搜索无碰撞路径。
约束方程的精确定义 ⭐⭐⭐¶
设 \(T_L(q_L), T_R(q_R) \in SE(3)\) 为两臂末端的正运动学。刚性共持约束要求右末端相对于左末端的位姿等于一个固定值 \(T_{\text{rel,fixed}}\):
重写为约束函数:
其中 \(\log: SE(3) \to \mathfrak{se}(3)\) 是矩阵对数,\((\cdot)^{\vee}: \mathfrak{se}(3) \to \mathbb{R}^6\) 是 vee 映射。
为什么用对数映射定义约束?
直接用 \(T_L^{-1} T_R - T_{\text{rel,fixed}} = 0\) 也可以定义约束,但这是一个 \(4 \times 4\) 矩阵方程——有 12 个元素但 SE(3) 只有 6 个自由度,所以约束方程有 6 个冗余。对数映射将约束压缩到最小的 6 维,避免了冗余和数值问题。
约束流形的几何性质 ⭐⭐⭐¶
约束流形:\(\mathcal{M} = \{q \in \mathbb{R}^{14} : h(q) = 0\}\)
正则性:如果约束 Jacobian \(J_h(q)\) 在 \(\mathcal{M}\) 上处处满秩(rank = 6),则 \(\mathcal{M}\) 是 \(\mathbb{R}^{14}\) 中的 8 维光滑子流形(由隐函数定理保证)。在工程实现中,\(J_h\) 常用同点相对 Jacobian \(J_{\text{rel}}=\bar{J}_R-\bar{J}_L\) 近似或线性化。
精确性注记:\(J_h = J_{\text{rel}}\) 的简写只在小误差、且左右 twist 已统一到同一表达系和同一参考点时成立。精确的 \(h(q)=\log(T_{\text{rel,fixed}}^{-1}T_L^{-1}T_R)^\vee\) 微分还包含 \(\text{Jlog}_6(\cdot)\) 与伴随映射项:左末端扰动要先通过 \(\text{Ad}\) 变换到相对误差所在的切空间,再与右末端扰动相减。不同库的 body/spatial Jacobian、左/右扰动约定不同,公式块顺序会变;不变的原则是:先用 adjoint/平移项统一表达系和参考点,再做相减。
LOCAL_WORLD_ALIGNED只统一坐标轴方向,不会自动统一线速度参考点。
切空间:在 \(q \in \mathcal{M}\) 处,\(T_q\mathcal{M} = \ker(J_{\text{rel}}(q))\),维度为 8。
法空间:\(N_q\mathcal{M} = \text{range}(J_{\text{rel}}^T(q))\),维度为 6。
几何图像(2D 类比):
想象 R^3 中的一个曲面(2 维流形),由约束 h(x,y,z) = 0 定义。
法向量 ∇h
↑
│ · · · · ·
│ · · ← 约束流形 M
├─────────────── 切平面 T_q M
│ · ·
│ · · · · ·
双臂的情况:
ambient 空间 R^14
约束 h(q) = 0 定义 8 维流形 M
切空间 T_q M = ker(J_h) (8 维)
常用局部近似:J_h ≈ J_rel = J̄_R - J̄_L
法空间 N_q M = range(J_h^T) (6 维)
投影到切空间:P = I - J_h^+ J_h
约束流形上的运动¶
在约束流形上,系统只能沿切空间方向运动。投影矩阵 \(P = I - J_h^+ J_h\) 将任意关节速度投影到切空间;在小误差局部线性化中,可以用同点 \(J_{\text{rel}}\) 代替 \(J_h\):
投影后的速度满足 \(J_h \cdot \dot{q}_{\text{feasible}} = 0\),即不改变两末端相对位姿。
连通性分析:约束流形 \(\mathcal{M}\) 可能不连通——即存在两个满足约束的构型 \(q_a, q_b \in \mathcal{M}\),但 \(\mathcal{M}\) 上不存在连接它们的连续路径。物理上,这对应于两种不同的"握持姿态"(如左手在上/右手在上)之间需要先松开物体再重新抓取。判断连通性是 NP-hard 问题,实践中通过多次随机采样和路径规划间接检测——如果 CBiRRT 在大量采样后仍无法找到路径,很可能两个构型处于不同的连通分支。
切空间的秩缺失与奇异:当 \(\text{rank}(J_h(q)) < 6\) 时(约束 Jacobian 秩缺失),约束流形在 \(q\) 处不再是光滑子流形——可能出现"褶皱"或"尖点"。这对应于 D1.11 中讨论的协调奇异。在这些点附近,Newton 投影(OMPL 的 ProjectedStateSpace 使用的核心算法)收敛速度急剧下降甚至发散。
约束漂移与修正:
由于数值积分误差,系统可能逐渐偏离约束流形。需要定期进行约束修正:
Baumgarte 稳定化:
速度级约束修正:
J_h · q̇ = -α · h(q)
α > 0 是一阶误差收敛增益,单位 1/s。
若控制器在加速度级求解,则使用二阶形式:
J_h · q̈ + J̇_h · q̇ = -2ζω · J_h · q̇ - ω² · h(q)
ζ 是阻尼比,ω 是期望误差收敛带宽。常用 ζ=1,
ω=5-20 rad/s;不要把速度级和加速度级公式混在一起。
OMPL 中的约束流形实现¶
// ============================================================
// OMPL 约束类——双臂刚性共持
// 继承 ompl::base::Constraint
// ============================================================
class DualArmConstraint : public ompl::base::Constraint {
public:
DualArmConstraint(
const pinocchio::Model& model,
pinocchio::Data& data,
pinocchio::FrameIndex left_ee_id,
pinocchio::FrameIndex right_ee_id,
const pinocchio::SE3& T_rel_fixed)
: ompl::base::Constraint(model.nq, 6) // ambient dim=14, constraint dim=6
, model_(model), data_(data)
, left_ee_id_(left_ee_id), right_ee_id_(right_ee_id)
, T_rel_fixed_(T_rel_fixed)
{}
// 约束函数 h(q)
void function(const Eigen::Ref<const Eigen::VectorXd>& q,
Eigen::Ref<Eigen::VectorXd> out) const override {
pinocchio::forwardKinematics(model_, data_, q);
pinocchio::updateFramePlacements(model_, data_);
const auto& T_L = data_.oMf[left_ee_id_];
const auto& T_R = data_.oMf[right_ee_id_];
// h(q) = log(T_rel_fixed^{-1} * T_L^{-1} * T_R)
pinocchio::SE3 T_err = T_rel_fixed_.inverse() * T_L.inverse() * T_R;
out = pinocchio::log6(T_err).toVector();
}
// 约束 Jacobian J_h(q):这里给出小误差局部线性化
// 关键:先把 LOCAL_WORLD_ALIGNED Jacobian 的线速度参考点统一到 p_ref
void jacobian(const Eigen::Ref<const Eigen::VectorXd>& q,
Eigen::Ref<Eigen::MatrixXd> J) const override {
pinocchio::computeJointJacobians(model_, data_, q);
pinocchio::updateFramePlacements(model_, data_);
Eigen::MatrixXd J_L(6, model_.nv), J_R(6, model_.nv);
J_L.setZero(); J_R.setZero();
pinocchio::getFrameJacobian(model_, data_, left_ee_id_,
pinocchio::LOCAL_WORLD_ALIGNED, J_L);
pinocchio::getFrameJacobian(model_, data_, right_ee_id_,
pinocchio::LOCAL_WORLD_ALIGNED, J_R);
const auto& T_L = data_.oMf[left_ee_id_];
const auto& T_R = data_.oMf[right_ee_id_];
Eigen::Vector3d p_ref = 0.5 * (T_L.translation() + T_R.translation());
auto skew = [](const Eigen::Vector3d& a) {
Eigen::Matrix3d S;
S << 0.0, -a.z(), a.y(),
a.z(), 0.0, -a.x(),
-a.y(), a.x(), 0.0;
return S;
};
auto shiftToPoint = [&](const Eigen::MatrixXd& J_frame,
const Eigen::Vector3d& p_frame) {
Eigen::Matrix<double, 6, 6> X =
Eigen::Matrix<double, 6, 6>::Identity();
Eigen::Vector3d d = p_ref - p_frame;
X.topRightCorner<3, 3>() = -skew(d); // [v;omega] 顺序
Eigen::MatrixXd J_ref = X * J_frame;
return J_ref;
};
Eigen::MatrixXd J_L_ref = shiftToPoint(J_L, T_L.translation());
Eigen::MatrixXd J_R_ref = shiftToPoint(J_R, T_R.translation());
J = J_R_ref - J_L_ref; // 同点相对 Jacobian;大误差 Newton 需再乘 Jlog/Ad 项
}
private:
const pinocchio::Model& model_;
mutable pinocchio::Data data_; // mutable for const FK calls
pinocchio::FrameIndex left_ee_id_, right_ee_id_;
pinocchio::SE3 T_rel_fixed_;
};
⚠️ 常见陷阱¶
⚠️ 编程陷阱:OMPL Constraint 中修改 Data 的线程安全性
错误做法:多线程规划中共享同一个 pinocchio::Data
现象:偶发的数值错误、segfault、或规划结果不可复现
根本原因:pinocchio::Data 不是线程安全的——FK 计算会修改其内部状态。
OMPL 的多线程采样可能同时调用 function() 和 jacobian()
正确做法:每个线程使用独立的 pinocchio::Data 副本
// 在 constraint 构造时
thread_local pinocchio::Data data(model);
💡 概念误区:认为"约束流形是凸集"
新手想法:"约束流形 M 是 R^14 中的光滑子流形,
所以 M 上两点之间存在直线连接"
实际上:约束流形通常是高度非凸的。M 上两个构型之间可能存在
多条路径(绕不同的奇异点),也可能存在不同的连通分支
(拓扑障碍)。这就是为什么需要基于采样的约束规划算法
(CBiRRT, AtlasRRT)而非简单的直线插值。
正确理解:约束流形的全局拓扑很复杂,这是 D02 的核心挑战。
练习¶
-
[思考] 对于双 6-DOF 臂(如双 UR5e,总 12-DOF),刚性共持约束后有效 DOF = 12 - 6 = 6。这意味着约束流形维度等于任务维度(6D 末端位姿控制)。此时零空间消失——两臂没有冗余度做碰撞回避。讨论这对工程实践的影响。
-
[计算] 对于双 7-DOF + 1-DOF 移动平台(总 15-DOF),刚性约束后有效 DOF = 15 - 6 = 9。多出的 1 维零空间可以做什么?
D1.8 双臂系统建模——增广 / 相对 / 主从三种视角 ⭐⭐¶
三种建模范式¶
双臂系统可以从三个不同的视角建模,每种视角适用于不同的控制目标:
范式 1:增广关节空间(Augmented Joint Space)
状态:q = [q_L^T, q_R^T]^T ∈ R^14
输出:ẋ_aug = [V_L^T, V_R^T]^T ∈ R^12
映射:ẋ_aug = J_aug · q̇
思想:把两臂看作一个 14-DOF 机器人
优势:统一处理,利用成熟的单臂框架
劣势:维度高(14 维规划),不区分物体运动和相对运动
适用:解耦任务、碰撞回避
范式 2:绝对/相对分解(Absolute/Relative Decomposition)
状态:q = [q_L^T, q_R^T]^T ∈ R^14
输出:ẋ_abs ∈ R^6, ẋ_rel ∈ R^6
映射:ẋ_abs = J_abs · q̇, ẋ_rel = J_rel · q̇
思想:将物体运动和抓取关系解耦
优势:物理直觉清晰,可独立设计控制器
劣势:姿态的"平均"需要 Lie 群运算
适用:紧耦合对称任务(搬运、协调操作)
范式 3:主从分解(Leader-Follower Decomposition)
状态:q_leader = q_L ∈ R^7, q_follower = q_R ∈ R^7
控制:
Leader:独立位控,跟踪期望轨迹
Follower:阻抗控制,跟随 leader 并维持相对关系
思想:一臂主导运动,另一臂被动跟随
优势:控制器设计简单,工业界常用
劣势:不对称——leader 故障则系统失效
适用:主导+辅助任务(装配、焊接夹持)
三种范式的对比:
| 维度 | 增广空间 | 绝对/相对 | 主从 |
|---|---|---|---|
| 数学优雅度 | 中 | 高 | 低 |
| 工程简单度 | 低 | 中 | 高 |
| 对称性 | 对称 | 对称 | 非对称 |
| 容错性 | 中 | 中 | 低 |
| 物理直觉 | 弱 | 强 | 强 |
| 工业应用 | 少 | 中 | 多 |
不是X而是Y:主从分解不是"懒惰的简化",而是在特定任务(一手固定一手操作)中物理上最自然的建模方式。强行用对称框架处理非对称任务反而引入不必要的复杂性。选择建模范式应该根据任务的物理对称性,而非框架的数学优雅性。
练习¶
-
[对比] 对于"拧瓶盖"任务,分别用三种范式描述:(a)增广空间:12 维末端速度向量中哪些分量需要控制?(b)绝对/相对:\(\dot{x}_{\text{abs}}\) 和 \(\dot{x}_{\text{rel}}\) 分别控制什么?(c)主从:谁是 leader(持瓶身)谁是 follower(拧盖)?
-
[思考] 如果任务在执行过程中从"对称搬运"切换到"一手固定一手拧螺丝",建模范式是否应该同步切换?切换时的过渡策略是什么?
D1.9 主流双臂平台对比 ⭐¶
为什么要了解平台¶
不同双臂平台的硬件架构直接影响控制策略的选择。例如,ALOHA 使用低成本舵机(位控模式),不适合力控;YuMi 有内置力矩传感器,天然适合协调阻抗控制。了解平台特性有助于选择正确的算法路线。
主流平台对比表¶
| 平台 | 开发者 | 单臂DOF | 驱动方式 | 力矩传感 | 末端负载 | 典型用途 | 控制接口 |
|---|---|---|---|---|---|---|---|
| 双 Franka Panda | Franka Emika | 7 | 关节力矩 | ✅ 关节+末端 | 3 kg | 研究/力控 | libfranka 1kHz |
| ABB YuMi (IRB 14000) | ABB | 7 | 关节力矩 | ✅ 关节 | 0.5 kg | 装配 | EGM 250Hz |
| Baxter/Sawyer | Rethink → Haddington | 7 | SEA | ✅ 弹性元件 | 2.2 kg | 教学/协作 | ROS/Intera |
| PR2 | Willow Garage | 7+移动 | SEA | ✅ | 1.8 kg | 研究(已停产) | ROS |
| ARMAR-6 | KIT H2T | 7(+手) | 力矩 | ✅ | ~3 kg | 人形/研究 | ArmarX |
| ALOHA | Stanford/TRI | 6 | 位控舵机 | ❌ | 0.75 kg | 遥操作/学习 | Dynamixel |
| Google Aloha 2 | Google DeepMind | 6 | 位控舵机 | ❌ | 0.75 kg | VLA 数据采集 | ROS2 |
| 双 iiwa 14 | KUKA | 7 | 关节力矩 | ✅ 全轴 | 14 kg | 工业/研究 | FRI 1kHz |
| Bimanual Jaco | Kinova | 6-7 | 电流控制 | 部分 | 2.6-4 kg | 康复/服务 | Kinova API |
| UFactory xArm (双臂) | UFactory | 6-7 | 电流控制 | ✅ 末端 | 5 kg | 研究/轻工业 | SDK 250Hz |
| Flexiv Rizon (双臂) | Flexiv | 7 | 力矩 | ✅ 全轴 | 4 kg | 自适应力控 | RDK 1kHz |
平台定量性能对比¶
选择平台不能只看参数表——实际控制性能差异巨大。以下是同一任务(双臂共持 1kg 杆,正弦轨迹跟踪)下不同平台的实测/仿真数据:
| 指标 | 双 Franka | 双 iiwa 14 | ABB YuMi | ALOHA | Flexiv Rizon |
|---|---|---|---|---|---|
| 控制频率 (Hz) | 1000 | 1000 | 250 | 50 | 1000 |
| 位置跟踪误差 RMS (mm) | 0.3-0.8 | 0.2-0.5 | 1.0-2.0 | 3.0-8.0 | 0.4-0.9 |
| 力控内力波动 (N, 1\(\sigma\)) | 0.5-1.5 | 0.3-1.0 | 2.0-5.0 | N/A | 0.8-2.0 |
| 最大速度 (m/s) | 2.0 | 2.0 | 1.5 | 0.5 | 2.0 |
| 重复定位精度 (mm) | \(\pm\)0.05 | \(\pm\)0.02 | \(\pm\)0.02 | \(\pm\)1.0 | \(\pm\)0.05 |
| 零力矩驱动延迟 (ms) | < 1 | < 1 | ~4 | ~20 | < 1 |
| 单臂成本 (USD, 约) | 30K-40K | 80K-120K | 50K-70K | 2K-5K | 20K-30K |
| 力控适合度 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐ | ⭐⭐⭐⭐ |
| 学习型控制适合度 | ⭐⭐⭐ | ⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ |
关键观察:力控性能与控制频率和力矩传感器质量直接相关。ALOHA 的 50Hz 位控在力控任务上完全不可用,但在学习型控制中这不是瓶颈——因为学习策略在训练数据中已经隐式补偿了控制延迟。Flexiv Rizon 是近年新兴的性价比选手,力控性能接近 Franka 但成本更低。
平台选型决策¶
需要力控/阻抗控制?
├── 是 → 需要高载荷?
│ ├── 是 → 双 iiwa 14(14kg,FRI 1kHz力矩接口)
│ └── 否 → 双 Franka Panda(3kg,libfranka 1kHz,生态最好)
└── 否 → 用于遥操作/学习?
├── 是 → ALOHA/Aloha 2(低成本,位控舵机,ACT/VLA生态)
└── 否 → 用于工业装配?
├── 是 → ABB YuMi(小物体精密装配,EGM接口)
└── 否 → 用于教学/原型验证?
└── 是 → Baxter(已停产但二手多)或 Kinova Jaco
驱动方式对控制策略的根本影响 ⭐⭐¶
双臂平台的驱动方式(力矩驱动 vs 位控驱动)对协调控制策略产生根本性约束:
力矩驱动平台(Franka, iiwa, Flexiv):
控制接口:直接发送关节力矩 τ_cmd
控制环路:τ_cmd → 电机电流 → 关节力矩 → 关节运动
时间常数:< 1 ms(电流环带宽 > 1 kHz)
优势:
- 可以实现任意阻抗特性(从零阻抗到高刚度)
- 可以精确控制内力(紧耦合任务的核心需求)
- 支持操作空间动力学控制(F02 框架直接可用)
代价:
- 需要精确的动力学模型(M(q), C(q,q̇), g(q))
- 重力补偿误差直接导致末端位移
- 安全性需要额外保障(力矩过大会损坏硬件)
位控驱动平台(ALOHA, 大部分工业臂的默认模式):
控制接口:发送关节位置/速度目标 q_cmd
控制环路:q_cmd → 内部 PD → 电机电流 → 关节运动
时间常数:~4-20 ms(位置环带宽 50-250 Hz)
优势:
- 不需要外部动力学模型(内置控制器处理)
- 安全性较好(内置限位保护)
- 编程简单
代价:
- 无法直接控制接触力(力控通过外环实现,带宽受限)
- 紧耦合任务易产生 force fight(两臂位控目标不一致)
- 不适合精确内力管理
本质洞察:力矩驱动和位控驱动的根本区别在于**因果方向**——力矩驱动是"我决定力,环境决定运动"(阻抗因果),位控驱动是"我决定运动,环境决定力"(导纳因果)。紧耦合双臂任务需要同时控制运动和力,力矩驱动平台可以在一个控制周期内切换因果方向(混合阻抗/导纳),而位控平台做不到。
ALOHA:改变双臂研究格局的平台¶
ALOHA(A Low-cost Open-source Hardware System for Bimanual Teleoperation)由 Stanford IRIS Lab 的 Tony Zhao 等人在 2023 年推出,对双臂研究产生了变革性影响:
硬件特征:
- 双 ViperX 250(6-DOF,Dynamixel 舵机)
- 成本 < $20K(vs 双 Franka > $100K)
- 纯位控模式(无力矩/力反馈传感器)
- 开源硬件设计 + 软件栈
研究范式转变:
传统路线:精确模型 → 精确控制 → 精确执行
ALOHA 路线:遥操作采集数据 → 学习策略 → 行为克隆执行
→ 不需要精确力控(位控舵机足够做行为克隆)
→ 不需要精确模型(学习直接从像素到动作)
→ 大幅降低了双臂研究的准入门槛
关键论文:
ACT(Action Chunking with Transformers, Zhao et al. 2023)
Aloha 2(Google DeepMind 2024,工业级迭代)
反事实推理:如果 ALOHA 没有出现(假设双臂研究仍需 > $100K 平台),双臂学习型控制的研究进展会慢多少?实际上,ALOHA 之前,全球能做双臂行为克隆实验的实验室不到 20 家(受限于硬件成本)。ALOHA 开源后一年内,超过 100 个团队搭建了类似系统——研究产出量级提升了 5-10 倍。这证明了:对于学习型控制,低成本可复现的硬件比精密的力控平台更重要。
⚠️ 常见陷阱¶
🧠 思维陷阱:认为"力控能力越强的平台越好"
新手想法:"双 iiwa 有最好的力矩传感,应该选它做所有双臂研究"
实际上:力控平台(iiwa/Franka)适合需要精确力交互的任务
(装配、柔性物体操作、人机协作)。但对于学习型控制
(行为克隆、VLA),低成本位控平台(ALOHA)更合适——
因为学习方法不依赖精确力控,而是从数据中自动补偿控制误差。
正确思维:平台选择应由任务和方法决定,而非平台本身的"最大能力"。
练习¶
-
[调研] 在你所在实验室或可获取的平台中,选择最适合做双臂协调搬运实验的系统。用上述决策树分析你的选择,并列出该平台的 3 个主要限制。
-
[思考] ALOHA 的 6-DOF 臂双臂共持刚性物体后有效 DOF = 12 - 6 = 6,零空间维度为 0。这对协调规划有什么影响?与双 7-DOF Franka 的 2 维零空间对比。
D1.10 双臂研究路线图——从规划到力控到学习 ⭐¶
历史演进脉络¶
双臂操作研究经历了四个主要阶段,每个阶段解决了上一阶段遗留的核心问题:
阶段 1:运动学与规划(1987-2000)
核心问题:如何让两臂协调运动而不碰撞?
代表工作:
- Khatib 1987: 操作空间动力学(单臂框架,但为双臂铺路)
- Uchiyama & Dauchez 1988: 对称协调运动学
- Chiacchio 1996: 绝对/相对变量分解
主要成果:双臂运动学框架(J_aug, J_rel, J_abs)
遗留问题:规划时未考虑力交互
阶段 2:力控与阻抗(1992-2010)
核心问题:如何控制两臂之间的内力?
代表工作:
- Schneider & Cannon 1992: Object Impedance Control
- Williams & Khatib 1993: 虚拟连杆内力模型
- Caccavale 2008: 6-DOF 双臂阻抗(事实标准)
主要成果:内力分解理论,协调阻抗控制
遗留问题:模型需要精确,环境变化时需要重新设计控制器
阶段 3:基于学习的双臂操作(2018-2024)
核心问题:如何不依赖精确模型完成复杂双臂任务?
代表工作:
- Zhao et al. 2023 (ACT): 行为克隆 + ALOHA
- Chi et al. 2023 (Diffusion Policy): 扩散模型动作生成
主要成果:数据驱动的双臂策略,遥操作数据采集管线
遗留问题:泛化能力有限,安全性无保证
阶段 4:遥操作与人机协作(2020-present)
核心问题:如何高效采集双臂示教数据?如何远程控制双臂?
代表工作:
- 波变量遥操作理论(D06)
- Haptic 设备双臂遥操作
- VR/AR 辅助遥操作界面
主要成果:遥操作数据采集管线,力反馈设备
当前挑战:延迟补偿,多模态感知融合
历史关键节点¶
| 年份 | 事件 | 贡献 | 影响 |
|---|---|---|---|
| 1987 | Khatib 操作空间动力学 | 统一力/运动控制框架 | 为所有任务空间双臂控制奠基 |
| 1988 | Koivo-Bekey 三分类 | 首个系统化双臂任务分类 | 至今仍是标准术语来源 |
| 1988 | Uchiyama-Dauchez 对称协调 | 对称/反对称运动分解 | 启发 Chiacchio 绝对/相对框架 |
| 1992 | Schneider-Cannon Object Impedance | 物体级阻抗控制 | 紧耦合任务控制的事实标准 |
| 1996 | Chiacchio 绝对/相对变量 | 共模/差模分解 | D 系列核心数学框架 |
| 2008 | Caccavale 6-DOF 双臂阻抗 | 完整的 SE(3) 阻抗框架 | Springer Handbook 标准参考 |
| 2022 | Krebs-Asfour 四维分类 | 多维度细粒度分类 | 学习型方法的任务表征基础 |
| 2023 | Zhao et al. ACT + ALOHA | 低成本双臂行为克隆 | 双臂研究准入门槛革命性降低 |
| 2024 | PerAct2 / BRMData | 大规模双臂基准 | 标准化评估推动方法对比 |
M 系列 → D 系列的知识继承¶
D 系列不是独立的——它大量复用 M01-M15(单臂核心)和 F01-F10(力控)的知识:
| D 系列章节 | 依赖的 M/F 知识 | 继承关系 |
|---|---|---|
| D01 导论 | M01 Pinocchio, M03 IK | Jacobian 计算、伪逆、零空间 |
| D02 协调规划 | M07 OMPL, M04 碰撞检测 | 采样规划框架、FCL |
| D03 协调力控 | F01-F03 阻抗控制, F02 操作空间 | 阻抗律、操作空间惯量 |
| D04 双臂学习 | — | 全新内容(学习型方法) |
| D05-D07 遥操作 | F01 力控基础 | 力反馈、无源性 |
| D09 系统集成 | M14 MoveIt2 | 双臂 MoveIt2 配置 |
跨章回顾桥:回顾 M01:Pinocchio 的 Model/Data 分离使得同一 Model 可以在多线程中安全使用——每个线程有自己的 Data。这个设计在双臂中更加重要:MPC 求解器可能需要在一个控制周期内多次调用 FK 和 Jacobian,Model/Data 分离使得这些调用可以并行化。
练习¶
-
[综合路线] 画出从 M01 到 D10 的完整知识依赖图。标注每条依赖边上的关键知识传递(如 M01→D01:"Jacobian 计算方法")。
-
[思考] 阶段 3(学习型方法)是否会完全取代阶段 1-2(模型驱动方法)?讨论学习型方法在安全关键任务(如手术辅助、核废料处理)中的局限性。
D1.11 协调奇异性与操作度分析 ⭐⭐⭐¶
动机:双臂独有的奇异问题¶
单臂的奇异位形(如完全伸直、关节对齐)已经在 M03 中详细讨论过。双臂系统除了继承单臂的奇异问题外,还有自己独有的**协调奇异**——两臂末端的几何关系导致联合系统的操作度退化。
协调奇异的分类 ⭐⭐⭐¶
类型 1:单臂奇异
两臂之一处于单臂奇异位形(如肘关节完全伸直),导致 \(J_L\) 或 \(J_R\) 的秩下降。
影响:\(J_{\text{aug}}\) 的秩从 12 降到 11 或更低。
检测:\(\sigma_{\min}(J_L) < \epsilon\) 或 \(\sigma_{\min}(J_R) < \epsilon\)。
类型 2:协调奇异(双臂末端共线)
两臂末端连线与某臂的关节轴重合,导致 \(J_{\text{rel}}\) 的秩下降——即使每只臂单独看都不奇异。
物理场景:
两臂末端在同一条直线上(如面对面伸直手臂握手)
此时 J_rel 的一个奇异值趋近于 0
→ 沿该方向的相对运动不可控
→ Moore-Penrose 投影矩阵 P 的一个特征值从 1 骤变为 0
→ 约束流形的切空间在该点"坍缩"
检测:σ_min(J_rel) < ε
回避:
- 规划时避免两臂末端共线的构型
- 控制时使用阻尼伪逆(Nakamura 自适应阻尼)
- 零空间中加入"远离协调奇异"的梯度项
类型 3:增广奇异(工作空间边界)
两臂的可达工作空间交集缩小到低维(如两臂只能在一条线上共持物体),导致 \(J_{\text{aug}}\) 的操作度急剧下降。
双臂操作度 ⭐⭐⭐¶
Chiacchio 等人 1991 年定义了**全局任务空间操作度**:
这个标量衡量了双臂系统在当前构型下操纵两个末端的整体能力。\(w_{\text{aug}} = 0\) 意味着至少一个方向的末端速度不可实现——系统处于奇异。
分解为绝对操作度和相对操作度:
| 操作度 | 物理含义 | 对任务的影响 |
|---|---|---|
| \(w_{\text{abs}}\) 高 | 物体可灵活运动 | 搬运任务轻松 |
| \(w_{\text{abs}}\) 低 | 物体运动受限 | 搬运困难,需换路径 |
| \(w_{\text{rel}}\) 高 | 两手可灵活改变相对位姿 | 装配/操作灵活 |
| \(w_{\text{rel}}\) 低 | 两手相对运动受限 | 装配精度下降 |
Worked Example:协调奇异的数值分析¶
以下完整示例展示如何用 Pinocchio 检测并分析协调奇异。
场景:双 Franka Panda 面对面放置,两臂逐渐伸直至末端共线。
// ============================================================
// 协调奇异检测——Pinocchio Worked Example
// 目标:扫描左臂 joint2 从 0 到 -π/2,观察 σ_min(J_rel) 变化
// ============================================================
#include <pinocchio/algorithm/kinematics.hpp>
#include <pinocchio/algorithm/frames.hpp>
#include <iostream>
#include <vector>
void detect_coordination_singularity(
const pinocchio::Model& model, pinocchio::Data& data,
pinocchio::FrameIndex left_ee, pinocchio::FrameIndex right_ee)
{
const int N_steps = 100;
std::vector<double> angles(N_steps), sigma_min_vals(N_steps);
for (int i = 0; i < N_steps; i++) {
Eigen::VectorXd q = pinocchio::neutral(model);
double angle = -M_PI / 2.0 * i / (N_steps - 1);
q[1] = angle; // 左臂 joint2(肩前屈)
q[8] = angle; // 右臂 joint2(镜像)
pinocchio::forwardKinematics(model, data, q);
pinocchio::updateFramePlacements(model, data);
pinocchio::computeJointJacobians(model, data, q);
// 计算同点 J_rel = J̄_R - J̄_L
Eigen::MatrixXd J_L(6, model.nv), J_R(6, model.nv);
J_L.setZero(); J_R.setZero();
pinocchio::getFrameJacobian(model, data, left_ee,
pinocchio::LOCAL_WORLD_ALIGNED, J_L);
pinocchio::getFrameJacobian(model, data, right_ee,
pinocchio::LOCAL_WORLD_ALIGNED, J_R);
auto skew = [](const Eigen::Vector3d& a) {
Eigen::Matrix3d S;
S << 0.0, -a.z(), a.y(),
a.z(), 0.0, -a.x(),
-a.y(), a.x(), 0.0;
return S;
};
Eigen::Vector3d p_L = data.oMf[left_ee].translation();
Eigen::Vector3d p_R = data.oMf[right_ee].translation();
Eigen::Vector3d p_ref = 0.5 * (p_L + p_R);
Eigen::Matrix<double, 6, 6> X_L =
Eigen::Matrix<double, 6, 6>::Identity();
Eigen::Matrix<double, 6, 6> X_R =
Eigen::Matrix<double, 6, 6>::Identity();
X_L.topRightCorner<3, 3>() = -skew(p_ref - p_L); // [v;omega]
X_R.topRightCorner<3, 3>() = -skew(p_ref - p_R);
Eigen::MatrixXd J_rel = X_R * J_R - X_L * J_L;
// SVD 提取最小奇异值
Eigen::JacobiSVD<Eigen::MatrixXd> svd(J_rel);
double sigma_min = svd.singularValues().tail(1)(0);
angles[i] = angle * 180.0 / M_PI; // 转为度
sigma_min_vals[i] = sigma_min;
// 报告奇异点
if (sigma_min < 0.01) {
auto p_L = data.oMf[left_ee].translation();
auto p_R = data.oMf[right_ee].translation();
Eigen::Vector3d dir = (p_R - p_L).normalized();
std::cout << "SINGULAR at joint2 = " << angles[i] << " deg"
<< "\n σ_min = " << sigma_min
<< "\n EE distance = " << (p_R - p_L).norm()
<< "\n Direction = [" << dir.transpose() << "]"
<< "\n → 两臂末端共线,沿此方向相对运动不可控\n";
}
}
}
典型输出:
SINGULAR at joint2 = -72.3 deg
σ_min = 0.0034
EE distance = 0.142 m
Direction = [0.998 0.012 -0.054]
→ 两臂末端共线,沿此方向相对运动不可控
物理解释:当 \(\sigma_{\min}(J_{\text{rel}}) \to 0\) 时,存在一个方向上两末端的相对速度无法通过关节速度实现。对于刚性共持任务,这意味着约束流形的切空间在该方向"坍缩"——投影矩阵 \(P\) 的一个特征值从 1 跳变为 0,导致零空间突然"丢失"一个维度。规划器在这样的构型附近会出现 Newton 投影发散。
⚠️ 常见陷阱¶
🧠 思维陷阱:认为"操作度越高越好,应该最大化操作度"
新手想法:"零空间中总是加 ∇w 来最大化操作度"
实际上:最大操作度的构型可能不适合实际任务。例如,最大操作度
通常出现在两臂完全展开的构型——但这时两臂末端距离最远,
无法共持小物体。操作度优化应该在满足任务约束的前提下进行。
正确做法:将操作度作为零空间目标(低优先级),不与任务目标冲突。
练习¶
-
[编程] 用 Pinocchio 扫描双 Franka Panda 的 C-space,计算 \(w_{\text{aug}}, w_{\text{abs}}, w_{\text{rel}}\) 的分布。找到协调奇异构型(\(w_{\text{rel}} \approx 0\) 但 \(w_L, w_R > 0\)),并描述其几何含义。
-
[思考] 协调奇异是否可以通过增加自由度来消除?如果给每只臂增加 1 个冗余关节(变成双 8-DOF),协调奇异是否仍然存在?
D1.12 前沿展望——Bimanual Manipulation with Foundation Models ⭐⭐⭐⭐¶
前十一节建立了双臂任务的经典分类框架和协调运动学基础。随着大规模预训练模型在操作领域的快速渗透,双臂研究正面临一次范式交汇——经典理论与数据驱动方法如何协同,是当前最活跃的问题之一。
基础模型驱动的双臂操作¶
2024-2025 年,基础模型(Foundation Model)开始深入双臂操作领域。以下是与 D01 分类框架直接相关的代表性工作:
RT-2 / Octo 的双臂扩展:Google DeepMind 的 RT-2(Brohan et al. 2023)和 Octo(Ghosh et al. 2024)最初面向单臂设计,但其 Transformer 架构天然支持多末端动作空间。Aloha 2 团队将 ACT(Action Chunking with Transformers)扩展为双臂版本,证明了行为克隆在双臂紧耦合任务(如叠衣服、拧瓶盖)中的可行性。从 D01 的分类视角看,这些方法主要解决了**松耦合和解耦任务**——因为位控舵机无法精确管理紧耦合任务中的内力。
力感知基础模型的缺口:当前的 VLA(Vision-Language-Action)模型主要依赖视觉和语言模态,对力/触觉信息的利用非常有限。这意味着它们在 Koivo 分类中的紧耦合任务(需要精确内力管理)上表现不佳。弥补这一缺口需要:(1)大规模力/触觉数据集(如 DROID + 力传感器扩展);(2)多模态基础模型架构(视觉 + 语言 + 力觉 + 本体感受);(3)底层协调力控器(D03 将介绍的 Object Impedance Control)作为执行后端。
跨领域类比:基础模型之于双臂操作,就像 GPS 导航之于驾驶——GPS 告诉你"往哪走"(任务级规划),但实际转弯、刹车、避让行人仍然需要驾驶员(底层控制)的精细操作。双臂基础模型的当前水平相当于"GPS 已经很好用了,但自动泊车(需要精确力控的紧耦合任务)还需要额外的传感器和控制算法"。
这个前沿方向的学习路径建议:先掌握 D01-D03 的协调运动学与力控理论("驾驶技术"),再学习 D04 的学习型方法("GPS 系统"),最后关注基础模型的集成("自动驾驶")。
经典方法与学习方法的融合趋势 ⭐⭐⭐⭐¶
2024-2025 年的双臂研究最重要的趋势不是"学习取代经典",而是**经典控制与学习方法的深度融合**。具体表现为:
层次化架构:高层(任务规划)用基础模型生成自然语言指令到子目标序列的映射,中层(运动规划)用采样规划或优化规划生成轨迹,底层(运动控制)用本章建立的协调运动学框架执行。每一层使用最适合的方法。
┌─────────────────────────────────────┐
│ 任务层:VLA / Foundation Model │ 输入:语言 → 输出:子任务序列
├─────────────────────────────────────┤
│ 规划层:TAMP / Diffusion Policy │ 输入:子任务 → 输出:轨迹
├─────────────────────────────────────┤
│ 控制层:J_abs / J_rel / Impedance │ 输入:轨迹 → 输出:关节力矩
└─────────────────────────────────────┘
残差学习:底层使用经典协调控制器(如 D03 的 Object Impedance),学习网络只输出对经典控制器的**修正量**(residual)。这样经典控制器保证了基本的安全性和稳定性,学习网络只需要学习模型不确定性的补偿量——比从零学习整个策略简单得多。
内力作为学习信号:将 D01 中定义的内力 \(f_{\text{int}}\)(Grasp Matrix 零空间中的力分量)作为学习策略的额外观测输入或奖励信号。经典方法给出内力的物理定义和测量方法,学习方法利用这个信号优化策略——这正是"经典定义物理量、学习利用物理量"的融合范式。
自适应混合阻抗控制:近年的研究(如 Two-level Variable Impedance Control, 2025)提出用自适应变刚度阻抗控制同时跟踪内力和外力——在不确定性环境下,阻抗参数由在线学习模块根据力误差动态调整,结合了经典控制的结构保证和学习的自适应能力。
练习¶
-
[思考] 对于安全关键的双臂任务(如人机协作装配),你是否信任一个纯学习策略(如 ACT/Diffusion Policy)?列出至少 3 个需要经典方法保障的安全属性(如力限制、碰撞回避、约束满足)。
-
[调研] 查阅 2024-2025 年发表的双臂操作论文,找一篇同时使用经典协调控制和学习方法的工作。分析其层次化架构中各层分别使用了什么方法。
本章与后续章节的关系¶
| 后续章节 | 与 D01 的关系 | 从 D01 带走的关键知识 |
|---|---|---|
| D02 双臂协调规划 | D01 的约束流形理论 \(\to\) D02 的约束采样规划 | \(h(q) = 0\)、8 维流形、\(P\) 投影 |
| D03 双臂协调力控 | D01 的 \(J_{\text{rel}}\)、内力空间 \(\to\) D03 的内力管理 | \(J_{\text{abs}}\)、\(J_{\text{rel}}\)、Grasp Matrix |
| D04 双臂学习 | D01 的任务分类 \(\to\) D04 的数据收集和策略设计 | Koivo/Krebs 分类、平台特性 |
| D05-D07 遥操作 | D01 的双臂运动学 \(\to\) 遥操作中的运动映射 | \(J_{\text{aug}}\)、主从范式 |
| D09 系统集成 | D01 的 Pinocchio 代码 \(\to\) D09 的 MoveIt2 双臂配置 | 合并 URDF、帧 ID、Jacobian 计算 |
本章常见误解汇总¶
| 误解 | 正确理解 |
|---|---|
| 双臂 = 两个单臂各干各的 | 即使解耦任务也需碰撞回避协调;紧耦合任务中两臂运动学完全耦合 |
| 自由度越多越好 | 高维 C-space 导致规划维度灾难;双臂优势在于闭链新能力而非自由度数量 |
| \(J_{\text{aug}}\) 总是分块对角 | 仅固定基座双臂成立;移动双臂或人形双臂有共享关节列 |
| \(J_{\text{rel}} = J_R - J_L\) 可以直接相减 | 必须先用 adjoint/平移项将两个 twist 统一到同一参考点再做差 |
| \(x_{\text{abs}} = (x_L + x_R)/2\) 等于物体质心 | 仅当两抓取点关于质心对称时成立;非对称抓取有偏差 |
| 姿态平均可以算术平均 | \(\text{SO}(3)\) 上的算术平均不保证正交性;需用测地线中点 |
| 闭链约束是精确满足的 | 编码器精度、控制延迟、物体弹性使约束仅近似满足;需 Baumgarte 修正 |
| 约束流形是凸集 | 约束流形通常高度非凸,可能有多个连通分支 |
| 操作度越高越好 | 最大操作度构型可能不满足任务约束(如末端距离太远) |
| 力控平台总是更好的选择 | 学习型方法在低成本位控平台上同样有效;平台选择应由任务和方法决定 |
| Koivo 和 Krebs 分类是竞争关系 | 两套分类互补——Koivo 选控制大类,Krebs 细化参数设计 |
| 分类标签是静态的 | 真实任务在执行中动态切换分类(如 handover 从解耦到紧耦合再到解耦) |
本章小结¶
| 知识点 | 核心要点 | 对应小节 | 难度 |
|---|---|---|---|
| 单臂局限性 | 载荷、DOF、内力、反作用力四大局限 | D1.1 | ⭐ |
| Koivo 三分类 | 紧耦合/松耦合/解耦 → 力控/时序/碰撞 | D1.2 | ⭐ |
| Krebs 四维分类 | 协调\(\times\)交互\(\times\)角色\(\times\)对称 = 54 种组合 | D1.2 | ⭐⭐ |
| 运动学约束 | 开链/闭链/虚拟闭链 → 14/8/可调 DOF | D1.3 | ⭐⭐ |
| 增广 Jacobian | \(J_{\text{aug}} \in \mathbb{R}^{12 \times 14}\),2 维零空间 | D1.4 | ⭐⭐ |
| 相对 Jacobian | \(J_{\text{rel}} \in \mathbb{R}^{6 \times 14}\),约束 \(J_{\text{rel}} \dot{q} = 0\) | D1.5 | ⭐⭐ |
| 绝对/相对分解 | 共模/差模类比,Chiacchio 1996 | D1.6 | ⭐⭐⭐ |
| 约束流形 | 8 维光滑子流形,\(P = I - J_{\text{rel}}^+ J_{\text{rel}}\) | D1.7 | ⭐⭐⭐ |
| 三种建模范式 | 增广/绝对相对/主从 → 按任务选择 | D1.8 | ⭐⭐ |
| 双臂平台 | Franka/YuMi/ALOHA/iiwa 各有适用场景 | D1.9 | ⭐ |
| 协调奇异 | 单臂奇异 + 协调奇异 + 增广奇异 | D1.11 | ⭐⭐⭐ |
术语速查表¶
| 中文术语 | 英文术语 | 缩写 | 一句话定义 |
|---|---|---|---|
| 增广雅可比 | Augmented Jacobian | \(J_{\text{aug}}\) | 两臂末端 Jacobian 的垂直堆叠,\(12 \times 14\) 矩阵 |
| 相对雅可比 | Relative Jacobian | \(J_{\text{rel}}\) | 关节速度到两末端相对 twist 的映射,\(6 \times 14\) 矩阵 |
| 绝对雅可比 | Absolute Jacobian | \(J_{\text{abs}}\) | 关节速度到两末端平均 twist 的映射,\(6 \times 14\) 矩阵 |
| 零空间投影 | Null-space Projector | \(P\) | 将关节速度投影到约束兼容子空间的正交投影矩阵 |
| 紧耦合 | Tightly Coupled | TC | 两臂通过刚性物体形成物理闭链的任务类型 |
| 松耦合 | Loosely Coupled | LC | 两臂操作同一物体但无刚性闭链的任务类型 |
| 解耦 | Decoupled | DC | 两臂各自操作不同物体的任务类型 |
| 内力 | Internal Force | \(f_{\text{int}}\) | 不产生物体运动但维持抓取稳定性的力 |
| 约束流形 | Constraint Manifold | \(\mathcal{M}\) | 闭链约束将 C-space 切割出的低维子流形 |
| 操作度 | Manipulability | \(w\) | 衡量当前构型操纵末端能力的标量指标 |
| 抓取矩阵 | Grasp Matrix | \(G\) | 将接触力映射到物体质心 wrench 的矩阵 |
| 阻尼伪逆 | Damped Least-Squares | DLS | 通过正则化提升奇异附近数值稳定性的伪逆方法 |
累积项目:本章新增模块¶
项目:Mini-DualArm 双臂协调操作系统
本章新增基础模块:
mini_dualarm/
├── models/
│ └── dual_panda.urdf # 双 Franka Panda 合并 URDF
├── src/
│ ├── dual_arm_kinematics.cpp # J_aug, J_rel, J_abs 计算
│ ├── null_space_projector.cpp # 零空间投影 P = I - J_rel^+ J_rel
│ └── manipulability.cpp # w_aug, w_abs, w_rel 计算
├── python/
│ ├── classification_demo.py # 交互式任务分类标注工具
│ └── manipulability_viz.py # 操作度可视化
└── tests/
├── test_jacobian.cpp # 验证 J_aug/J_rel 数值正确性
└── test_projection.cpp # MP 投影幂等性 + DLS 任务泄漏验证
下一章(D02)将在此基础上加入约束规划模块:constrained_planner.cpp。
API 速查表¶
| API / 函数 | 库 | 签名 | 说明 |
|---|---|---|---|
buildModelFromUrdf |
Pinocchio | Model buildModelFromUrdf(path) |
从 URDF 加载模型(双臂需合并 URDF) |
forwardKinematics |
Pinocchio | void FK(model, data, q) |
正运动学,必须在 Jacobian 前调用 |
updateFramePlacements |
Pinocchio | void update(model, data) |
更新帧位姿(FK 后调用) |
computeFrameJacobian |
Pinocchio | void CJ(model, data, q, frame_id, ref, J) |
计算帧 Jacobian,ref 选 LOCAL_WORLD_ALIGNED |
getFrameJacobian |
Pinocchio | Matrix6x getJ(model, data, id, ref) |
Python 接口:返回 \(6 \times n_v\) Jacobian |
log6 |
Pinocchio | Motion log6(SE3) |
\(\text{SE}(3) \to \mathfrak{se}(3)\) 对数映射 |
JacobiSVD |
Eigen | JacobiSVD<MatrixXd> svd(A, flags) |
SVD 分解,用于伪逆和秩计算 |
ompl::base::Constraint |
OMPL | class Constraint(ambient, co) |
约束基类,需重写 function 和 jacobian |
延伸阅读¶
| 文献 | 类型 | 难度 | 推荐理由 |
|---|---|---|---|
| Smith et al. 2012, "Dual Arm Manipulation — A Survey", RAS | 综述 | ⭐⭐ | 最被引用的双臂综述,Section 2-3 的控制方案对比表是最好的入门材料 |
| Krebs & Asfour 2022, "A Bimanual Manipulation Taxonomy", RA-L | 论文 | ⭐⭐ | 新一代分类学,图 3 的分类树是教学工具 |
| Caccavale & Uchiyama 2008, "Cooperative Manipulators", Springer Handbook Ch.39 | 教材 | ⭐⭐⭐ | Handbook 级权威综述,标准符号体系 |
| Chiacchio et al. 1996, "Direct and Inverse Kinematics for Coordinated Motion", ASME | 论文 | ⭐⭐⭐ | 绝对/相对变量的原始定义,数学严谨 |
| Siciliano, Sciavicco et al. "Robotics: Modelling, Planning and Control", Ch.12 | 教材 | ⭐⭐ | 多臂协调的教材级讲解,适合系统学习 |
| Khatib 1987, "A Unified Approach for Motion and Force Control", IEEE J. RA | 论文 | ⭐⭐⭐⭐ | 操作空间动力学——所有双臂任务空间控制的母框架 |
| Grotz et al. 2024, "PerAct2: Benchmarking and Learning for Robotic Bimanual Manipulation Tasks", arXiv | 论文 | ⭐⭐⭐ | 双臂基准数据集和评估框架,含层次化任务标注 |
| Zhao et al. 2023, "Learning Fine-Grained Bimanual Manipulation with Low-Cost Hardware", RSS | 论文 | ⭐⭐ | ACT + ALOHA,改变双臂研究格局的标志性工作 |
🔧 故障排查手册¶
| 症状 | 可能原因 | 排查步骤 | 相关章节 |
|---|---|---|---|
| \(J_{\text{aug}}\) 计算结果全零 | 未调用 forwardKinematics |
1. 检查 FK 调用顺序 2. 打印 data.oMf 确认帧位姿 3. 确认帧 ID 正确 |
D1.4 |
| \(P^2 \neq P\)(幂等性验证失败) | 把阻尼伪逆滤波器误当正交投影,或 SVD 阈值不一致 | 1. 确认 \(P\) 用 Moore-Penrose/SVD 伪逆 2. 若使用阻尼伪逆,改查 \(\|J_{\text{rel}}P_\lambda\dot{q}_0\|\) 3. 检查 \(J_{\text{rel}}\) 条件数 | D1.5 |
| 协调 IK 两臂末端漂移 | 约束漂移未修正 | 1. 添加速度级 \(J_h\dot{q}=-\alpha h\) 或加速度级 Baumgarte 2. 检查控制频率(< 200Hz 时漂移明显)3. 打印 \(\|h(q)\|\) 监控约束违反 | D1.7 |
| 两臂在共持物体时产生大内力 | 位控模式下的 force fight | 1. 检查是否使用了独立位控 2. 切换到协调阻抗控制 3. 参考 D03 的内力管理方案 | D1.1, D03 |
| OMPL 约束规划长时间无解 | 投影失败率过高 | 1. 检查 maxIterations 参数 2. 尝试 AtlasStateSpace 替代 ProjectedStateSpace 3. 确认初始构型在约束流形上 |
D1.7, D02 |
| 操作度 \(w\) 突然趋近零 | 进入协调奇异构型 | 1. 打印 \(\sigma_{\min}(J_{\text{rel}})\) 2. 检查两末端是否共线 3. 启用 Nakamura 自适应阻尼或零空间奇异回避 | D1.11 |
| 合并 URDF 加载后帧数量不对 | URDF 合并方式错误 | 1. 检查 <joint type="fixed"> 连接是否正确 2. 打印 model.nframes 确认帧数量 3. 逐帧打印名称确认 ID 映射 |
D1.4 |
研究实践建议¶
| 层次 | 建议 | 适用读者 |
|---|---|---|
| 入门级 | 搭建双 Franka Panda 的 Pinocchio 模型,验证 \(J_{\text{aug}}\)、\(J_{\text{rel}}\) 的数值正确性 | 本科生、研一新生 |
| 进阶级 | 在 MuJoCo 中实现协调 IK(task-priority 级联),对比独立位控和协调位控的内力差异 | 硕士研究生 |
| 研究级 | 复现 Caccavale 2008 的 6-DOF 双臂阻抗控制器,在物体不确定性下评估性能 | 博士研究生 |
| 前沿级 | 将 ACT/Diffusion Policy 与底层协调力控结合,构建层次化双臂控制架构 | 博士高年级/博后 |
版本信息速查¶
| 库 / 工具 | 本章使用版本 | 备注 |
|---|---|---|
| Pinocchio | \(\ge\) 2.6.x | 需要 computeFrameJacobian 支持 LOCAL_WORLD_ALIGNED |
| Eigen | \(\ge\) 3.4 | JacobiSVD 需要 ComputeThinU \| ComputeThinV 标志 |
| OMPL | \(\ge\) 1.5.x | ompl::base::Constraint 需要 1.5+ 的约束采样框架 |
| MuJoCo | \(\ge\) 2.3.x | 双臂闭链仿真需要 equality constraint 支持 |