Day6 unitree_G1人形机器人GMR—— MotionInput

可以把 MotionInput 理解成一个"统一插座",GMR不需要知道前面的动作数据到底来自:

  • Axis Studio;
  • Noitom MocapApi;
  • UDP测试程序;
  • 以后增加的BVH文件。

GMR只需要知道:

你给我一个格式正确的 NoitomFrame,我就把它转换成G1动作。
不是 MotionInput 亲自转换,而是 MotionInput 的两个具体实现分别负责转换,然后统一返回 NoitomFrame

先看三者关系

复制代码
MotionInput                 统一规定
├── XsensUdpSource          具体干活
└── NoitomMocapApiSource    具体干活

MotionInput只是规定:

复制代码
无论你内部怎么接收数据,
最后都必须给我返回一个 NoitomFrame。

它类似一份工作要求,不是实际干活的人。

一、先理解原来的问题

系统有两种动作来源。

来源1:Xsens UDP测试输入

程序直接监听一个UDP端口:

复制代码
测试程序/Xsens
    ↓
UDP数据包
    ↓
XsensUdpSource

它收到的是UDP格式的数据,需要自己解析。

来源2:Noitom正式输入

真实动捕服先连接Axis Studio:

复制代码
动捕服
  ↓
Axis Studio
  ↓
TCP 127.0.0.1:7003
  ↓
Noitom MocapApi
  ↓
NoitomMocapApiSource

它收到的是Axis通过Noitom SDK提供的人体Avatar骨骼数据。

这两种输入方式完全不同:

项目 Xsens UDP Noitom MocapApi
网络方式 UDP TCP或UDP
数据格式 自定义UDP数据包 Noitom SDK事件
解析方式 自己解析数据包 调用MocapApi
主要用途 测试、兼容旧程序 正式动捕服
是否依赖Axis 不一定

如果没有统一接口,GMR里面可能会出现这种代码:

复制代码
if (使用Xsens) {
    接收UDP();
    解析UDP();
    执行GMR();
}

if (使用Noitom) {
    连接MocapApi();
    读取Avatar();
    执行GMR();
}

这样会造成:

  • GMR和网络代码混在一起;
  • 两条链路可能各写一套GMR;
  • 修复GMR时要改两个地方;
  • 以后增加第三种输入会越来越乱;
  • 很难判断问题发生在输入还是GMR。

所以我们在输入和GMR之间加了一层统一接口。

二、MotionInput到底是什么

MotionInput不是一种新的网络协议,也不是新的动作格式。

它只是C++里的一个统一接口,大致相当于:

复制代码
class MotionInput
{
public:
    virtual void open() = 0;

    virtual std::optional<NoitomFrame> pollFrame() = 0;

    virtual void close() = 0;
};

它只规定三件事:

复制代码
open()      打开动作数据源
pollFrame() 获取一帧人体动作
close()     关闭数据源

但它没有规定数据必须从哪里来。

这就像一个统一插座:

复制代码
插座规定:
必须提供电

但不关心电来自:
火电、水电、风电还是太阳能

同样:

复制代码
MotionInput规定:
必须提供NoitomFrame

但不关心数据来自:
UDP、Axis、文件还是其他设备

三、两个实现分别做什么

1. XsensUdpSource

它是 MotionInput 的一个实现。

复制代码
class XsensUdpSource : public MotionInput
{
public:
    void open() override;
    std::optional<NoitomFrame> pollFrame() override;
    void close() override;
};

它内部负责:

复制代码
监听UDP端口
  ↓
收到UDP数据包
  ↓
解析人体关节
  ↓
整理成NoitomFrame
  ↓
交给GMR

虽然它叫 XsensUdpSource,但它交给后面的数据已经被整理成统一格式。

输出是:

复制代码
NoitomFrame

2. NoitomMocapApiSource

它也是 MotionInput 的一个实现。

复制代码
class NoitomMocapApiSource : public MotionInput
{
public:
    void open() override;
    std::optional<NoitomFrame> pollFrame() override;
    void close() override;
};

它内部负责:

复制代码
通过MocapApi连接Axis
  ↓
监听TCP 127.0.0.1:7003
  ↓
等待AvatarUpdated事件
  ↓
读取人体骨骼位置和旋转
  ↓
整理成NoitomFrame
  ↓
交给GMR

它的输出同样是:

复制代码
NoitomFrame

四、最关键的地方:输入不同,输出相同

两条链路的原始数据不同:

复制代码
XsensUdpSource
收到的是UDP数据包

NoitomMocapApiSource
收到的是MocapApi Avatar事件

但是经过各自解析以后,它们都必须生成:

复制代码
NoitomFrame

例如,可以把统一后的 NoitomFrame 简单理解为:

复制代码
struct NoitomFrame
{
    uint64_t timestamp_ms;
    uint32_t posture_index;

    std::vector<BodyPose> bodies;
};

里面包含类似:

复制代码
Hips
Spine
Chest
Neck
Head
LeftShoulder
LeftArm
LeftForeArm
LeftHand
RightShoulder
RightArm
RightForeArm
RightHand
LeftUpLeg
LeftLeg
LeftFoot
RightUpLeg
RightLeg
RightFoot

每个身体部位都有:

复制代码
位置 position
旋转 rotation

因此,到达GMR时,两种输入已经没有区别了。

五、GMR看到的是什么

GMR不会看到:

复制代码
TCP端口7003
UDP端口8000
MocapApi
Axis Studio
网络数据包

它只会收到:

复制代码
NoitomFrame frame;

然后执行:

复制代码
RobotFrame robot_frame =
    retargeter.retarget(frame);

换成大白话就是:

复制代码
输入层说:
"这是这一时刻的人体骨骼。"

GMR说:
"我不管它来自哪套动捕设备,
只要关节名称、位置和旋转正确,
我就把它变成G1动作。"

六、运行时怎么选择输入

程序启动时通过参数选择。

正式使用Noitom

实际链路是:

复制代码
动捕服
  ↓
Axis Studio
  ↓
TCP 7003
  ↓
NoitomMocapApiSource
  ↓
NoitomFrame
  ↓
GMR

使用UDP测试

如果运行:

复制代码
-MotionInput udp

程序内部相当于:

复制代码
if (motion_input == "udp") {
    input = std::make_unique<XsensUdpSource>();
}

实际链路变成:

复制代码
UDP测试数据
  ↓
XsensUdpSource
  ↓
NoitomFrame
  ↓
GMR

注意:二者不会同时正式控制GMR。启动时选择谁,当前运行就使用谁。

七、用翻译员来理解

可以把两种输入理解成两个人:

复制代码
Xsens说英语
Noitom说中文

GMR只懂一种"标准语言":

复制代码
NoitomFrame

所以两名翻译员分别负责:

复制代码
XsensUdpSource:
英语 → 标准语言

NoitomMocapApiSource:
中文 → 标准语言

翻译完成以后,GMR只处理标准语言。

复制代码
Xsens原始数据 ─→ XsensUdpSource ─┐
                                ├→ NoitomFrame → GMR
Noitom原始数据 ─→ NoitomSource ──┘

八、为什么统一格式叫NoitomFrame

这个名称确实容易让人误会。

看到:

复制代码
NoitomFrame

容易认为:

它只能装Noitom的数据。

但在当前工程里,它实际上被当作:

复制代码
统一的人体骨骼帧

也就是说,Xsens UDP解析完成后,也会转换成 NoitomFrame

从架构命名角度,更理想的名字可能是:

复制代码
HumanMotionFrame

或者:

复制代码
SkeletonFrame

这样更容易理解:

复制代码
Xsens → SkeletonFrame
Noitom → SkeletonFrame

但我们没有随意改名,因为这个结构已经被GMR等代码使用。改名会扩大修改范围,增加不必要的风险。

所以当前可以这样记:

复制代码
NoitomFrame虽然名字里有Noitom,
但在本项目里承担的是"统一人体骨骼帧"的角色。

九、MotionInput解决了什么问题

没有MotionInput时

复制代码
GMR
├── 自己监听UDP
├── 自己连接MocapApi
├── 自己判断输入类型
├── 自己解析网络数据
└── 再执行IK

GMR要做的事情太多。

有MotionInput以后

复制代码
XsensUdpSource:负责UDP
NoitomMocapApiSource:负责Axis/MocapApi
GMR:只负责人体到机器人的转换
ZMQ:只负责发送转换后的动作

每个模块只负责一件事。

十、它和ZMQ有什么关系

MotionInput负责的是GMR之前:

复制代码
动作从哪里来

ZeroMQ负责的是GMR之后:

复制代码
GMR结果怎么发出去

两个模块位于系统的不同位置:

复制代码
输入侧                              输出侧
MotionInput                         ZMQ
    ↓                                ↑
NoitomFrame → GMR → RobotFrame → protobuf

不要把它们混在一起。

  • MotionInput:电脑怎样获得人体动作。
  • GMR:人体动作怎样变成G1动作。
  • protobuf:G1动作怎样组织成数据。
  • ZMQ:数据怎样传给MuJoCo或机器人。

十一、当前实际运行的正式链路

你刚才穿动捕服测试时,实际使用的是这一条:

复制代码
```mermaid
flowchart LR
    A["动捕服"] --> B["Axis Studio"]
    B --> C["TCP 127.0.0.1:7003"]
    C --> D["Noitom MocapApi"]
    D --> E["NoitomMocapApiSource"]
    E --> F["统一 NoitomFrame"]
    F --> G["输入 FIFO"]
    G --> H["C++ GMR IK"]
    H --> I["G1 RobotFrame"]
    I --> J["protobuf"]
    J --> K["ZeroMQ"]
    K --> L["MuJoCo"]
```

XsensUdpSource这次没有参与运行,它只是保留下来的测试入口:

复制代码
```mermaid
flowchart LR
    A["UDP测试数据"] --> B["XsensUdpSource"]
    B --> C["统一 NoitomFrame"]
    C --> D["同一套 C++ GMR"]
```

十二、最简单的记忆方法

只需要记住下面四句话:

复制代码
MotionInput是统一入口。

XsensUdpSource负责解析UDP测试数据。

NoitomMocapApiSource负责读取Axis正式数据。

无论数据来自哪里,最终都变成NoitomFrame,再交给同一套GMR。

最终最简化结构就是:

复制代码
不同的数据来源
      ↓
不同的输入适配器
      ↓
统一NoitomFrame
      ↓
同一个GMR
      ↓
同一个ZMQ发送链路

它的本质不是增加复杂度,而是把原来混在一起的事情分开。

相关推荐
火山引擎开发者社区1 小时前
基于 TLS 的 Coding Agent 全链路可观测体系:打破执行黑盒,实现全流程透明复盘
人工智能
ACP广源盛139246256731 小时前
DeepSeek‑V4‑Flash 公测@ACP#昇腾 950 国产算力组合落地,国产 PCIe 交换芯片 IX9104 有哪些硬件机会
大数据·数据库·人工智能·分布式·单片机·嵌入式硬件·microsoft
leoZ2311 小时前
实战复盘:用 Claude Code 从零搭一个 GitHub PR 统计工具
java·人工智能·python·深度学习·自然语言处理·github·llama
无忧智库1 小时前
数字化转型:打造智慧城市大脑,引领未来城市发展(PPT)
人工智能·智慧城市
weixin_452600691 小时前
D2632大电流低差压可调线性稳压器介绍,D2632可替换MIC29302
fpga开发·机器人·无人机·电动工具·车载摄像头
ltqvibe1 小时前
数据中台为什么走不通——本体语义给出的替代路径
人工智能·原型模式·本体语义平台
QYR-分析1 小时前
机器人主控制器行业全景分析:市场格局、技术迭代与发展前景
机器人
AI科技星1 小时前
曲率‑挠率与 $\boldsymbol{\omega/c}$ 的关系、精算验证及其物理意义
c语言·开发语言·线性代数·算法·决策树·机器学习·ai科技星
yuezhilangniao2 小时前
AI工具全家桶:从开发到运维,从数据库到产品经理-含魔塔社区简介
运维·数据库·人工智能