Day5 unitree_G1人形机器人“身外化身”基础学习
一、系统总体结构
当前目标结构是:
Noitom动捕服
↓
Noitom接收器/Hub
↓
Axis Studio / Axis Neuron
↓
MotionInput统一接口
├── NoitomMocapApiSource(正式)
└── XsensUdpSource(测试)
↓
NoitomFrame
↓
GMR IK重定向
↓
RobotFrame
↓
MotionStreamInterpolator
↓
MocapFrame protobuf
↓
MotionFrameHeader + CRC32
↓
ZeroMQ PUSH
↓ TCP 5555
ZeroMQ PULL
↓
MocapFrame protobuf
↓
MuJoCo qpos
↓
G1 29DOF可视化
这里存在两个网络段,不能混为一个:
第一段:Axis → xsens_core
第二段:xsens_core → MuJoCo Viewer
正式配置下:
Axis → MocapApi TCP → xsens_core
xsens_core → ZMQ TCP → MuJoCo
测试配置下:
Axis/测试工具 → UDP 8000 → xsens_core
xsens_core → ZMQ TCP 5555 → MuJoCo
二、物理层发生了什么
1. 动捕服传感器
人体穿戴多个惯性传感器。传感器测量的主要是:
- 加速度
- 角速度
- 磁场
- 传感器姿态
- 时间信息
这些还不是最终人体骨骼位置。
2. Noitom接收器
传感器通过无线方式将数据送到Noitom接收器或Hub。
电脑一般通过:
- USB
- 网线
- 专用接收器
访问这些数据。
3. Axis Studio / Axis Neuron
Axis软件负责:
- 识别传感器
- 标定人体
- 建立Avatar
- 姿态融合
- 骨骼约束
- 计算每个关节的姿态
- 计算骨骼位置
- 输出结构化人体动作
到这里,数据从原始IMU变成了:
Avatar
├── Hips
├── Spine
├── Spine1
├── Spine2
├── Neck
├── Head
├── LeftArm
├── LeftForeArm
├── LeftHand
├── RightArm
├── ...
├── LeftFoot
└── RightFoot
我们的程序没有自己实现IMU解算,而是使用Axis已经解算好的骨骼。
这很重要:
GMR输入不是原始惯导数据,而是Axis生成的人体骨骼世界姿态。
三、MotionInput统一输入层
核心定义:
class MotionInput
{
public:
virtual void open() = 0;
virtual void close() noexcept = 0;
virtual std::optional<NoitomFrame>
pollFrame() = 0;
virtual const char*
name() const noexcept = 0;
};
这个接口的意义是:
上层程序不关心数据来自哪里
上层只做:
input->open();
while (running)
{
auto frame = input->pollFrame();
if (!frame)
{
continue;
}
retargeter.retarget(*frame);
}
四、正式输入:NoitomMocapApiSource
它内部封装:
NoitomClient
然后使用 Noitom SDK:
MocapApi.h
MocapApi.lib
MocapApi.dll
1. 程序启动
默认:
--motion-input noitom
--noitom-transport tcp
--noitom-ip 127.0.0.1
--noitom-port 7001
含义是:
Axis与xsens_core在同一台电脑
Axis开放TCP Server端口7001
xsens_core通过MocapApi连接127.0.0.1:7001
如果Axis运行在另一台电脑:
Axis电脑:192.168.10.2
MuJoCo电脑:192.168.10.3
则参数应该是:
--noitom-ip 192.168.10.2
--noitom-port Axis实际端口
2. 初始化SDK
Noitom客户端先通过:
MCPGetGenericInterface(...)
取得多个SDK接口:
IMCPSettings
IMCPRenderSettings
IMCPApplication
IMCPAvatar
IMCPJoint
它们分别负责:
IMCPSettings:网络、BVH格式配置IMCPRenderSettings:单位和坐标输出IMCPApplication:连接、事件轮询IMCPAvatar:Avatar信息IMCPJoint:读取骨骼节点
3. 配置TCP
正式模式调用:
SetSettingsTCP(
server_ip,
server_port,
settings_handle
);
这里的关系是:
Axis = TCP Server
MocapApi客户端 = TCP Client
不是我们的程序监听7001,而是我们的程序主动连接Axis的7001。
4. 配置骨骼格式
SDK配置:
SetSettingsBvhRotation(BvhRotation_XYZ)
SetSettingsBvhTransformation(BvhTransformation_Enable)
SetSettingsBvhData(BvhDataType_Binary)
同时默认Render Settings把位置单位设置为:
meter
这避免把毫米直接送进MuJoCo。
如果单位不正确,典型表现是:
机器人根位置移动1000倍
模型瞬间飞走
5. 打开Application
执行:
CreateApplication()
SetApplicationSettings()
SetApplicationRenderSettings()
OpenApplication()
EnableApplicationCacheEvents()
其中 EnableApplicationCacheEvents() 很重要。
它表示SDK应缓存事件,让程序逐个读取,而不是只提供最后一次状态。
这更符合目前:
可以慢一点,但希望拿到完整数据
的要求。
6. 读取事件
程序循环调用:
PollApplicationNextEvent()
事件可能包括:
AvatarUpdated
RigidBodyUpdated
SensorModulesUpdated
Error
None
我们只处理:
MCPEvent_AvatarUpdated
每次Avatar更新后,读取:
avatarHandle
timestamp
postureIndex
PTP time
postureIndex 是Noitom动作帧序号。
程序会统计:
上一帧:100
当前帧:101 → 正常
上一帧:100
当前帧:104 → 缺少101、102、103
日志可能输出:
[NoitomJump]
prev_posture_index=100
posture_index=104
missing_posture_indices=[101,102,103]
这让我们可以判断数据是否在Noitom入口就已经缺失。
7. 读取Avatar关节
通过:
GetAvatarJoints()
GetJointTag()
GetJointGlobalPosition()
GetJointGlobalRotation()
获得每个关节的:
世界位置 x y z
世界四元数 x y z w
SDK返回四元数顺序是:
x, y, z, w
内部转换成GMR使用的:
w, x, y, z
8. Noitom节点映射
Noitom SDK节点被转换成统一名称,例如:
JointTag_Hips → Hips
JointTag_Spine → Spine
JointTag_Spine1 → Spine1
JointTag_Spine2 → Spine2
JointTag_Spine3 → Spine3
JointTag_Neck → Neck
JointTag_Head → Head
JointTag_LeftArm → LeftArm
JointTag_LeftForeArm → LeftForeArm
JointTag_LeftHand → LeftHand
JointTag_RightArm → RightArm
...
Noitom没有单独的Toe节点时,会使用Foot作为Toe回退:
LeftToe ← LeftFoot
RightToe ← RightFoot
如果节点缺失,程序会:
- 输出警告
- 填入单位姿态
- 保持统一的数据结构长度
9. 坐标系转换
Noitom SDK输出的数据经过:
位置:(x, y, z) → (x, -z, y)
四元数也进行对应的坐标旋转。
目的是把Axis坐标系统一到现有GMR/Lafan风格坐标系。
这里是第一次实际穿动捕服测试时必须重点观察的地方。
若转换错误,会出现:
- 人向前,模型横向移动
- 抬手变成手臂前后旋转
- 人站立,模型躺倒
- 左右动作相反
代码结构可行,但坐标转换必须通过实际设备确认。
五、测试输入:XsensUdpSource
文件:
- [xsens_udp_source.hpp (line 1)](C:/Users/RX01296/Desktop/全身控制7_28/gmr-master_PN - 副本/cpp_xsens_g1/include/xsens_udp_source.hpp:1)
- [xsens_udp_source.cpp (line 1)](C:/Users/RX01296/Desktop/全身控制7_28/gmr-master_PN - 副本/cpp_xsens_g1/src/xsens_udp_source.cpp:1)
启动:
--motion-input udp
--ip 0.0.0.0
--port 8000
这条路径:
UDP 8000
↓
XsensUdpParser
↓
坐标转换
↓
四元数连续性处理
↓
NoitomFrame
它期待固定格式:
24字节Header
+
23节点 × 8个大端float
它不是通用Noitom UDP解析器。
因此它的用途是:
- 保留旧输入方式
- 回滚测试
- 使用已知格式的录制/发生器测试
- Noitom SDK暂时不可用时排查GMR
不建议把它作为最终Noitom正式入口。
六、统一数据:NoitomFrame
两个输入源最后都输出:
NoitomFrame
{
timestamp_ms;
posture_index;
bodies[];
}
每个Body大致是:
BodyPose
{
name;
position;
rotation;
}
例如:
NoitomFrame
├── timestamp_ms = 123456
├── posture_index = 1001
└── bodies
├── Hips
│ ├── position
│ └── rotation
├── Spine2
├── Head
├── LeftArm
├── LeftForeArm
├── LeftHand
├── RightArm
├── LeftUpLeg
└── ...
这就是输入层与GMR之间的边界。
只要两种输入源能产生正确的 NoitomFrame,GMR完全不需要知道数据来源。
七、GMR IK做了什么
调用点:
[xsens_core_main.cpp (line 460)](C:/Users/RX01296/Desktop/全身控制7_28/gmr-master_PN - 副本/cpp_xsens_g1/src/xsens_core_main.cpp:460)
auto robot_frame =
retargeter.retarget(*frame);
完整实现来自:
[cpp_gmr_retargeter.cpp (line 1)](C:/Users/RX01296/Desktop/全身控制7_28/gmr-master_PN_xsenscpp最终版/gmr-master_PN/cpp_noitom_g1/src/cpp_gmr_retargeter.cpp:1)
它使用:
- MuJoCo模型
- Jacobian
- Forward Kinematics
- DAQP约束QP
- 关节角限制
- 两阶段IK任务
- 人体身高缩放
机器人模型:
[g1_mocap_29dof.xml (line 1)](C:/Users/RX01296/Desktop/全身控制7_28/gmr-master_PN - 副本/assets/unitree_g1/g1_mocap_29dof.xml:1)
IK配置:
[bvh_xsens_to_g1.json (line 1)](C:/Users/RX01296/Desktop/全身控制7_28/gmr-master_PN - 副本/general_motion_retargeting/ik_configs/bvh_xsens_to_g1.json:1)
GMR匹配关系
例如:
人体Hips
↓
机器人pelvis
人体Spine2
↓
机器人torso_link
人体LeftArm
↓
机器人left_shoulder_yaw_link
人体LeftForeArm
↓
机器人left_elbow_link
人体LeftHand
↓
机器人left_wrist_yaw_link
人体LeftUpLeg
↓
机器人left_hip_yaw_link
人体LeftLeg
↓
机器人left_knee
人体LeftFoot
↓
机器人left_ankle
GMR求解的目标不是“人体关节角直接复制给机器人”。
而是:
让G1的关键身体部位
尽量跟随人体关键身体部位的位置和旋转
同时满足G1关节角限制
输出为 RobotFrame,主要包含:
- 根位置
- 根姿态
- 29个关节位置
- 相关身体状态
八、插值层
人体帧率和输出帧率可能不同,例如:
Noitom输入:60Hz
MuJoCo输出:60Hz
或者:
Noitom输入:30Hz
输出:50Hz
程序通过:
MotionStreamInterpolator
将 RobotFrame 转为连续的 MotionFrame。
这里必须设置真实帧率:
--input-fps
--output-fps
如果Axis实际60Hz,但配置30Hz,可能导致:
- 速度计算错误
- 插值时间错误
- 动作节奏不正确
- GMR输出不连续
- 机器人动作过快或过慢
建议首次测试:
InputFps = Axis实际帧率
OutputFps = Axis实际帧率
先用:
60 → 60
确认正确后,再测试其他输出频率。
九、protobuf数据层
GMR输出被转换成:
MocapFrame
主要字段:
index
body_pos_w 3
body_lin_vel_w 3
body_quat_w 4
body_ang_vel_w 3
joint_pos 29
joint_vel 29
left_hand 7
right_hand 7
当前MuJoCo主要使用:
body_pos_wbody_quat_wjoint_pos
Viewer会严格检查:
body_pos_w_size == 3
body_quat_w_size == 4
joint_pos_size == 29
joint_vel_size == 29
字段长度错误时会拒绝该帧,避免数组越界。
十、ZMQ传输层
当前不是PUB/SUB,而是根据“不能主动丢帧”的新要求改为:
PUSH/PULL
PC端:
xsens_core
ZMQ_PUSH
bind tcp://*:5555
MuJoCo端:
ZMQ_PULL
connect tcp://127.0.0.1:5555
如果以后MuJoCo在另一台电脑:
connect tcp://<xsens_core电脑IP>:5555
为什么改成PUSH/PULL
PUB/SUB的特点是:
- 适合实时广播
- 订阅者慢时可能丢数据
- 新订阅者看不到历史数据
CONFLATE会主动覆盖旧帧
现在的要求是:
允许等待,但不主动丢任何帧
所以当前采用:
- 阻塞发送
SNDHWM = 10000RCVHWM = 10000LINGER = -1- 不使用
DONTWAIT - 不使用
CONFLATE
当接收端变慢时:
不会主动覆盖旧帧
而是缓存并最终阻塞发送端
代价是:
延迟可能逐步升高
这符合目前“完整性优先于实时性”的要求。
十一、传输包格式
每个ZMQ消息结构:
"MOCAP_FRAME"
+
44字节 MotionFrameHeader
+
protobuf MocapFrame
Header包含:
magic
version
message_type
frame_id
capture_timestamp_us
send_timestamp_us
payload_size
joint_count
reserved
crc32
接收端检查:
- magic正确
- 版本正确
- 类型正确
- 关节数量为29
- payload长度匹配
- CRC32匹配
- frame ID是否连续
因此可以识别:
损坏数据
协议版本错误
关节数量错误
protobuf不完整
frame ID跳变
十二、MuJoCo Viewer
文件:
[xsens_zmq_mujoco_viewer_main.cpp (line 1)](C:/Users/RX01296/Desktop/全身控制7_28/gmr-master_PN - 副本/cpp_xsens_g1/src/xsens_zmq_mujoco_viewer_main.cpp:1)
接收流程:
ZMQ PULL
↓
Header/CRC校验
↓
protobuf反序列化
↓
字段长度检查
↓
FIFO pending队列
↓
逐帧取出
↓
写入MuJoCo qpos
↓
mj_forward
↓
渲染
MuJoCo qpos 写入:
qpos[0:3] 根位置
qpos[3:7] 根四元数
qpos[7:36] 29个关节位置
这里使用:
mj_forward(model, data);
而不是物理控制仿真的:
mj_step(model, data);
所以当前Viewer属于:
运动学动作可视化
它验证的是:
- 人体动作是否被正确读取
- GMR输出是否合理
- 29DOF是否正确映射
- 姿态是否连续
- 坐标系是否正确
- 网络帧是否完整
它暂时不验证:
- G1动力学平衡
- 地面接触控制
- 电机扭矩
- ONNX控制策略
- LowCmd
- 机器人是否会摔倒
这正适合当前第一阶段。
十三、一键启动过程
执行:
.\run_zmq_mujoco.ps1 `
-MotionInput noitom `
-NoitomTransport tcp `
-NoitomIp 127.0.0.1 `
-NoitomPort 7001 `
-InputFps 60 `
-OutputFps 60 `
-HumanHeight 1.75
脚本会:
- 检查
xsens_core.exe。 - 检查
xsens_zmq_mujoco_viewer.exe。 - 检查
MocapApi.dll。 - 检查G1 XML。
- 检查IK JSON。
- 启动MuJoCo Viewer。
- Viewer连接
tcp://127.0.0.1:5555。 - 启动
xsens_core.exe。 xsens_core连接Axis TCP端口。- 收到Avatar事件。
- 生成
NoitomFrame。 - 执行GMR IK。
- protobuf序列化。
- ZMQ发送。
- MuJoCo逐帧显示。
十四、完整性能够保证到什么程度
需要区分三个等级。
程序不主动丢帧
当前已经做到:
- Noitom事件缓存
- GMR输出FIFO
- 阻塞ZMQ发送
- MuJoCo输入FIFO
- 未启用CONFLATE
- 未使用latest-frame覆盖
- frame ID缺口统计
正常连接期间顺序完整
TCP和ZMQ在进程持续运行、连接正常时,可以保证字节按序可靠传输。
断线/崩溃后绝对恢复
当前不能绝对保证。
如果发生:
- 网线拔掉
- Axis崩溃
xsens_core崩溃- MuJoCo进程崩溃
- 电脑断电
内存队列会丢失。
严格持久化零丢帧还需要:
原始动作落盘
frame ID确认
接收端ACK
未确认帧重传
断点续传
但对于当前“穿动捕服做MuJoCo模拟”的阶段,现有机制足够用于正常在线验证。
十五、软件层面可行性判断
Noitom输入层:高可行
理由:
- SDK文件完整。
- DLL约2.9 MB,不是空文件。
- import library存在。
- 已有完整
NoitomClient实现。 - 代码已经实现TCP/UDP模式。
- Avatar、Joint、Posture Index接口齐全。
- 新增适配器源码通过语法检查。
主要风险:
- Axis实际端口不一定是7001。
- Axis需要启用对应网络输出。
- Axis版本与SDK r70必须兼容。
- TCP Server和UDP输出设置方式需要以实际Axis界面为准。
判断:约85%可行,剩余必须通过实际Axis连接验证。
MotionInput抽象:高可行
- 接口简单。
- 两条输入路径完全隔离。
- 同一时间只实例化一种输入。
- 输出类型统一。
- 不修改GMR接口。
判断:约95%可行。
GMR IK:高可行
- 完整源码存在。
- MuJoCo Jacobian存在。
- DAQP求解器存在。
- XML和IK JSON存在。
- 源码集成验证已通过。
主要风险:
- 人体身高参数。
- Noitom坐标系。
- 初始站姿。
- 关节方向。
- 人体比例缩放。
判断:算法和源码约90%可行;实际动作质量必须靠设备校准。
ZMQ传输:高可行
- protobuf字段匹配。
- Header编解码测试通过。
- CRC校验存在。
- FIFO存在。
- PUSH/PULL配置符合完整性优先要求。
- 本机回环TCP结构简单。
判断:约95%可行。
MuJoCo显示:高可行
- Viewer源码已经完成。
- 29DOF映射清晰。
- 使用现有G1 XML。
- qpos结构与模型预期一致。
- FIFO显示逻辑存在。
主要风险:
- 依赖是否正确链接。
- XML中的关节顺序是否与GMR输出顺序完全一致。
- 根四元数和坐标方向。
- GLFW运行库部署。
判断:约90%可行。
当前整体可行性
单看软件设计:
总体可行性:高
目前更准确的判断是:
源码结构完成度:约90%
构建环境完成度:取决于当前安装
无设备静态验证:通过
实际Noitom联机验证:尚未进行
实际MuJoCo窗口验证:尚未进行
十六、第一次运行最可能出现的问题
按概率排序:
- Axis TCP端口设置不一致。
- Axis没有启用BVH/TCP输出。
MocapApi.dll找不到或版本不兼容。- 防火墙阻止Axis TCP连接。
- Noitom坐标系方向不正确。
- Axis实际帧率与
InputFps不一致。 - 人体身高配置不正确。
- MuJoCo/GLFW DLL没有复制到EXE目录。
- GMR初始姿态与穿戴标定姿态不一致。
- Viewer队列持续增长,说明GMR输出快于显示消费速度。
十七、首次联调建议
不要一开始就做复杂动作。
依次做:
- 自然站立。
- 左手慢慢抬起。
- 右手慢慢抬起。
- 左腿小幅抬起。
- 右腿小幅抬起。
- 身体向前倾。
- 身体向左转。
- 缓慢下蹲。
每一步检查:
人体左 → 模型左
人体前 → 模型前
抬手 → 对应肩肘腕
抬腿 → 对应髋膝踝
站立 → 模型没有横躺
静止 → 关节没有明显抖动
第一阶段成功标准是:
Axis收到动捕
Noitom posture index连续
GMR持续输出
ZMQ frame ID连续
CRC invalid=0
MuJoCo动作方向正确
pending队列不持续增长
达到这些标准后,软件链路才算完成第一轮闭环验证。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐


所有评论(0)