分层运动系统(Layered Motion System)
范围:机器人运动控制中的分层架构设计,实现主要动作与次要偏移的融合
综合自:reachy-mini-conversation-app
优先级:P0
概述
分层运动系统是一种用于机器人实时运动控制的架构模式。它将运动分为两层:
- 主要动作层(Primary Moves):顺序执行的互斥动作,如舞蹈、情绪表达、位置移动和呼吸动画
- 次要偏移层(Secondary Offsets):叠加在主要动作之上的实时偏移,如语音反应摇摆和面部追踪
这种设计的核心思想是:单一控制点 + 姿态融合。所有运动最终通过一个 set_target() 接口输出,在每一帧将主要姿态和次要偏移融合后发送给机器人。
这种模式对于 Embodied AI 应用至关重要,因为它解决了以下问题:
- 如何让机器人在执行预设动作的同时,保持对语音的实时反应
- 如何在不打断当前动作的情况下,叠加面部追踪功能
- 如何确保运动的平滑过渡,避免姿态跳变
问题描述
挑战
在机器人对话应用中,运动系统面临多重挑战:
- 并发运动需求:机器人需要在说话时摇摆头部(语音反应),同时可能正在执行舞蹈或情绪动作
- 实时性要求:语音反应需要 200ms 内的延迟,面部追踪需要持续更新
- 平滑过渡:动作之间的切换不能有跳变,需要插值
- 资源竞争:多个线程可能同时请求不同的运动
传统方案的局限
| 方案 | 问题 |
|---|---|
| 直接调用机器人 API | 无法协调多个运动源,导致冲突和跳变 |
| 状态机 | 难以处理叠加运动,状态爆炸 |
| 优先级抢占 | 低优先级运动被完全忽略,失去细微表现 |
核心概念
1. 双层运动架构
框架:reachy-mini-conversation-app
系统将运动分为两个层次:
# moves.py - 核心架构设计
"""
Design overview
- Primary moves (emotions, dances, goto, breathing) are mutually exclusive and run sequentially.
- Secondary moves (speech sway, face tracking) are additive offsets applied on top of the current primary pose.
- There is a single control point to the robot: `ReachyMini.set_target`.
- The control loop runs near 100 Hz and is phase-aligned via a monotonic clock.
"""
主要动作(Primary Moves):
- 互斥执行:一次只有一个主要动作在运行
- 队列管理:动作按顺序排队执行
- 类型:
BreathingMove、DanceQueueMove、EmotionQueueMove、GotoQueueMove
次要偏移(Secondary Offsets):
- 叠加模式:多个偏移可以同时生效
- 实时更新:来自音频或视觉线程的实时数据
- 类型:语音摇摆偏移(
speech_offsets)、面部追踪偏移(face_tracking_offsets)
@dataclass
class MovementState:
"""Movement system state tracking."""
# Primary move state
current_move: Move | None = None
move_start_time: float | None = None
last_activity_time: float = 0.0
# Secondary move state (offsets)
speech_offsets: Tuple[float, float, float, float, float, float] = (0.0, 0.0, 0.0, 0.0, 0.0, 0.0)
face_tracking_offsets: Tuple[float, float, float, float, float, float] = (0.0, 0.0, 0.0, 0.0, 0.0, 0.0)
设计理由:
- 主要动作需要完整控制机器人,不能同时执行两个舞蹈
- 次要偏移是"点缀",不会破坏主要动作的完整性
- 分层设计简化了并发控制
2. 姿态融合(Pose Composition)
框架:reachy-mini-conversation-app
姿态融合是将主要姿态和次 要偏移合并的核心机制:
# moves.py
def combine_full_body(primary_pose: FullBodyPose, secondary_pose: FullBodyPose) -> FullBodyPose:
"""Combine primary and secondary full body poses."""
primary_head, primary_antennas, primary_body_yaw = primary_pose
secondary_head, secondary_antennas, secondary_body_yaw = secondary_pose
# Head pose: use compose_world_offset for proper 3D transform composition
combined_head = compose_world_offset(primary_head, secondary_head, reorthonormalize=True)
# Antennas and body_yaw: simple addition
combined_antennas = (
primary_antennas[0] + secondary_antennas[0],
primary_antennas[1] + secondary_antennas[1],
)
combined_body_yaw = primary_body_yaw + secondary_body_yaw
return (combined_head, combined_antennas, combined_body_yaw)
关键点:
- 头部姿态:使用 4x4 变换矩阵,通过
compose_world_offset进行正确的 3D 变换组合 - 天线和身体偏航:简单加法,因为它们是单自由度的
# Full body pose type definition
FullBodyPose = Tuple[NDArray[np.float32], Tuple[float, float], float]
# (head_pose_4x4_matrix, (left_antenna, right_antenna), body_yaw)
权衡:
- 优点:数学上正确的姿态组合,避免万向节锁
- 缺点:需要理解 3D 变换矩阵,调试复杂度高