让机器人跟着抖音视频学跳舞:启元 Q1 闪身步全链路实战

作者:叶毅翔(天津商业大学 · GewisLab)
平台:本地 RTX 5070 Laptop + AtomGit CANNLab 云端昇腾 NPU
关键词:启元 PrimeBot Q1 / GVHMR / GMR 动作重定向 / 昇腾 Ascend 910 / PPO / MuJoCo
博客文档由 GewisLab leader lhwLHWjackl的个人 a小lo爱吃棒棒糖 的博客代发,博客撰写叶毅翔同学

给机器人一段普通抖音舞蹈视频,它能不能自己学会这个动作?

这次我们把整条链路完整跑通并落地了:

抖音视频 → 裁剪片段 → GVHMR 三维姿态提取 → GMR 重定向到 Q1 22 关节
        → 50fps 标准动作 npz → 昇腾 NPU PPO 训练 3000 iter → 策略实际执行

结果:

  • 云端 NPU 训练 3000 iter、约 47 分钟收敛,最终 mean reward 2.287、回合回报 440.8、吞吐约 2.66 万 env-step/s;
  • 加载训练后策略实际执行:完整跑满 10 秒 501 步没摔倒;
  • 横向位移 0.311 m(参考动作 0.300 m,几乎一致),双脚最大分腿 0.784 m,全程贴地。

机器人真的学会了闪身步,而且不是原地瞎晃。


一、背景:从"用现成动作"到"自己造动作"

上一轮我们验证了在昇腾 NPU 上训练 Q1 的**起身(getUp)**动作。但那个动作用的是仓库里现成的参考动作数据(q1_getUp.npz)——躺着也能把训练跑通。

这一轮的目标更狠一点:能不能让机器人从一段普通人拍的视频里学会一个新动作?

区别在于,起身动作的参考数据是别人做好喂到嘴边的,而这次的"闪身步"需要我们自己从视频里重建。链路长了一大截,中间要解决三个问题:

  1. 看懂视频里的人在干什么——从 RGB 像素恢复出人体在三维空间中的姿态;
  2. 把人的动作搬到机器人身上——人和机器人骨骼结构、关节数、肢体比例都不同,要做动作重定向(retargeting),还要保证关节不越限、不穿模、不摔倒;
  3. 转成训练框架能读的格式——统一成 50fps 的标准 npz,交给 PPO 在 NPU 上训练。

选的目标动作是视频里的「闪身步」——一种以横向移动、双脚周期性分合为特征的舞步。选它是有讲究的:横向位移 + 大幅分腿,是最容易"糊弄"的动作(原地腿部开合也能看起来像),所以必须用量化数据证明机器人真的在横向移动。


二、整体链路

整条链路分本地和云端两半,共六个关键环节:

抖音闪身步视频(32.37s,多动作教学合集)
   │  ① 下载 + ffmpeg 裁剪干净片段(7.0s / 30fps / 210 帧)
   ▼
GVHMR 姿态估计(WSL2,GPU 加速)
   │  ② 输出 hmr4d_results.pt(210 帧 SMPL-X 世界坐标参数)
   ▼
GMR 动作重定向(headless,quadprog 求解器)
   │  ③ 输出 q1_shanshen.pkl(210 帧,22 个机器人关节角)
   ▼
构建 mjlab 标准 npz(配置空间重采样 30→50fps + MuJoCo FK + 速度 + 贴地)
   │  ④ 输出 q1_shanshen_mjlab.npz(349 帧,50fps,7 个核心字段)
   ▼
回放验证(MuJoCo 离屏渲染 + 骨长/限位/位移校验)
   │  ⑤
   ▼
昇腾 NPU PPO 训练(CANNLab,1024 envs,3000 iter)
   │  ⑥ 输出收敛策略 model_*.pt + 训练曲线
   ▼
策略实际执行验证(evaluate + 运动学量化)

这套流程是通用的:换任意一段新视频,沿用相同流程就能让 Q1 学会新动作。


三、第一步:从抖音把视频"抠"出来

3.1 下载:yt-dlp 会 403

源视频是抖音 ID 7688616111110203758,一段包含三个动作的教学合集。

第一个坑来得很快:yt-dlp 直接下载返回 403。原因是抖音的关键鉴权 Cookie 是 HttpOnly 的,普通方式导出的 cookies.txt 根本拿不到;浏览器手动导出也不完整。

可行办法是曲线救国——在已登录、能正常播放视频的页面上,用 JavaScript 读 <video> 标签的真实播放地址:

// 浏览器控制台执行
document.querySelector("video").currentSrc

拿到 douyinvod.com 带签名的临时直链后,用 Python 带上 Referer 和 User-Agent 直接下载(签名有时效,得尽快用):

import urllib.request
req = urllib.request.Request(url, headers={
    "Referer": "https://www.douyin.com/",
    "User-Agent": "Mozilla/5.0",
})
with open("source_video.mp4", "wb") as f:
    f.write(urllib.request.urlopen(req).read())

得到 source_video.mp4(919,899 字节,820×576,32.37 秒)。

3.2 定位动作区间

原视频是个教学合集,包含三个动作。用 ffmpeg 每 1.5 秒抽一帧做成时间轴联系表,人工确认起止点:

在这里插入图片描述

动作 时间段 说明
闪身步 2.6 – 9.6 s 第一位老师,约 4 次横向闪身、双腿分合
浪子踢球 12 – 16.5 s 单动作
狗熊哆嗦毛 19.5 – 31.5 s 第二位老师

3.3 裁出干净片段

从 2.5 秒起截 7.0 秒,只保留闪身步:

ffmpeg -ss 2.5 -i source_video.mp4 -t 7.0 -c:v libx264 -an shanshen_clip.mp4

产物:210 帧、7.0 秒、30fps,末帧是干净的远景站立姿态:

在这里插入图片描述

裁完务必抽首帧/中间帧/末帧看一眼,确认没混进下一个动作的转场画面。


四、第二步:GVHMR 提取三维人体姿态

4.1 为什么放在 WSL2 里跑

官方 GVHMR 顶层依赖 pytorch3d,而它只有 Linux 的预编译 wheel,Windows 原生装不上。WSL2 既能用 Linux 软件栈,又能直通笔记本 GPU,是唯一选择。

4.2 最大的坑:RTX 5070 是 Blackwell 架构

笔记本是 RTX 5070 Laptop(8GB),属于 Blackwell 架构,compute capability sm_120。

而官方 GVHMR 要求的 torch 2.3.0 + CUDA 12.1 最高只支持到 sm_90——一上 GPU 运算就报:

no kernel image is available

翻译一下:你的显卡太新了,旧版 PyTorch 里没有能给它的架构编译的 kernel。

解决方案:PyTorch 2.7.0 及以上、CUDA 12.8 构建才支持 Blackwell sm_120。最终改用 PyPI 上的现代化分支 gvhmr 1.6.1,它的关键优势:

  • 支持 Python ≥ 3.11,可用 torch 2.7.x + cu128,完美支持 Blackwell;
  • pytorch3d 只在"渲染"可选项里需要,核心推理不需要;
  • 输出仍是 hmr4d_results.pt,格式与官方一致,后续 GMR 直接能用。
conda create -n gvhmr2 python=3.11 -y
conda activate gvhmr2

# 先显式装支持 Blackwell 的 torch,避免被错误解析成过新版本
pip install torch==2.7.1+cu128 torchvision==0.22.1+cu128 \
  --index-url https://mirrors.aliyun.com/pytorch-wheels/cu128/

pip install gvhmr[preproc]

4.3 两个额外的坑

  • hf-mirror 断点续传失效:CDN 每次请求 etag 都会变,导致续传永远重来;单连接 wget 限速约 150KB/s。改用 aria2c 多连接可到 7–9 MiB/s(快约 45 倍)。
  • 模型文件"看起来正常"其实坏了:曾在 WSL I/O 挂起期间,vitpose、hmr2 两个模型大小正确但内容损坏,torch.load 报 failed finding central directory。而 gvhmr info 只查存在性和大小、不验证内容——必须单独 torch.load 验证。

4.4 跑姿态提取

固定机位用 -s(SimpleVO),不需要渲染加 --no-render:

gvhmr demo shanshen_clip.mp4 -s --no-render

又一个坑:用 nohup 后台跑会在预处理后静默退出,改成前台运行才完整正常(退出码 0)。

产物 hmr4d_results.pt,210 帧与视频完全一致:

字段 形状 含义
body_pose (210, 63) 21 个身体关节的轴角
global_orient (210, 3) 根节点朝向
transl (210, 3) 根节点平移
betas (210, 10) 体型参数

五、第三步:GMR 把人的动作重定向到机器人

5.1 要解决什么问题

GMR(General Motion Retargeting)用逐帧逆运动学(IK),把 SMPL-X 人体的姿态映射到 Q1 的 22 个关节。它基于 mink 构建优化问题:对每个身体链接设定位置/朝向目标,在满足关节限位的前提下最小化跟踪误差。

难点在于:人的骨盆高度、腿长、关节范围和机器人完全不同,硬搬会直接穿模或摔倒。

5.2 本地两处适配

  1. 求解器替换:daqp、proxqp 在 Python 3.15 下没有预编译 wheel、源码编译又缺 MSVC,改用有 wheel 的 quadprog(初始化 GMR 时显式传 solver="quadprog")。
  2. smpl.py 两处兼容修复:expression 默认 (1,10) 未随 batch 扩展,显式补 expression=torch.zeros(num_frames, 10);GMR 把 betas 填到 16 维而 smplx 默认 num_betas=10,需在 smplx.create 时显式指定。

5.3 跑无界面重定向

GMR 自带的 gvhmr_to_robot.py 会开 GUI,且按 tuple 处理返回值的方式与当前版本不匹配,于是写了独立的 retarget_headless.py。

产物 q1_shanshen.pkl:210 帧、22 关节、fps 30。

校验结果很好:无 NaN;22 个关节角度范围 -1.06 ~ 1.26,全部在关节限位内;pelvis 高度 z 在 0.433 ~ 0.488 m 之间,符合人体骨盆高度。


六、第四步:转成训练能读的标准 npz

6.1 为什么还要转一次

训练框架要求的是 50fps、7 个标准字段的 mjlab npz,而 GMR 输出是 30fps 的 pkl,字段也对不上。需要做五件事:

  1. 四元数从 xyzw 转为 wxyz;
  2. 配置空间重采样,30fps → 50fps(210 帧 → 349 帧,时长保持 6.98s);
  3. MuJoCo 正运动学(FK)算每个 body 的世界位置和姿态;
  4. 由重采样后轨迹差分计算线速度、角速度;
  5. 贴地抬升,pelvis 首帧锚定到原点。

build_mjlab.py 直接 import 仓库里已验证过的 resample、nlerp_quat、forward_kinematics、angular_velocity 等函数,一步构建。

6.2 严格校验:骨长刚性

这一步是整个链路里最能证明"运动学算对了"的检查:

校验项 结果
7 个字段形状 全部正确,无 NaN/Inf
四元数模长 严格等于 1.0
22 个关节越界 零越界
25 条刚性父子连杆骨长波动 全部 0.0000(机器精度 1e-7 级)

最后一条的意思是:除了自由基座相对原点的距离会随动作变化,其余所有连杆的长度在整个动作过程中一动不动——证明 FK 计算正确、骨架刚性恒等。如果 IK 或 FK 有 bug,骨长一定会漂。


七、第五步:回放验证——先确认动作"看起来对"

正式训练前,必须确认重定向出来的动作在机器人身上站得稳、看起来对。

用带完整场景(地面、灯光、22 个 actuator)的 q1_v3_mc.xml 离屏渲染全部 349 帧:

在这里插入图片描述

机器人周期性地「直立 → 分腿 → 回站立」交替,贴地稳定、无穿模。

完整动态回放:

参考动作回放

关键一步:量化"是不是真的横向移动"

前面说过,闪身步太容易被"原地腿部开合"糊弄过去。所以用 check_lateral.py 做了运动学量化:

指标 参考动作实测
pelvis 横向(x)位移幅度 0.3004 m
pelvis 前后(y)幅度 0.1461 m
pelvis 高度(z)幅度 0.0547 m
双脚间距范围 0.129 ~ 0.5649 m

横向位移 0.30 m、双脚最大分开 0.56 m——确认是真实的横向闪身移动,不是原地晃腿。


八、第六步:昇腾 NPU 上训练

8.1 怎么把 npz 送到云端

WebIDE 没有文件上传入口,xterm 粘贴大文本又不可靠(长命令被截断、cat 交互式粘贴时序难控)。最后用 git 传输解决:本地把 npz 复制为 .bin(仓库 .gitignore 忽略 *.npz 但不忽略 *.bin),commit push 到个人 fork,云端 fetch 后只 checkout 这个文件,再改回 .npz。校验 472,682 字节、np.load 正常。

8.2 训练配置

和上一轮起身训练完全一致——换动作不用改训练代码,只需把 Q1_MOTION 指向新 npz:

参数 值
num_envs / num_workers 1024 / 32
max_iterations 3000
steps_per_env 24
device npu:0
观测 / 动作维度 119 / 22

有一个时序细节:tracking_env 的 MOTION 路径是 import 时读取的模块级变量,所以必须在 import q1_ascend 之前设置环境变量:

import os
os.environ["Q1_MOTION"] = "/mnt/workspace/motions/q1_shanshen_mjlab.npz"

from q1_ascend.training.ppo import PPOConfig, train_ppo

奖励函数:6 项跟踪奖励(位置/朝向/线速度/角速度的指数误差)+ 动作平滑 + 关节限位 + 自碰撞惩罚。

8.3 训练曲线

约 47 分钟跑完 3000 iter,双轴曲线如下:

在这里插入图片描述

关键节点数据(来自 train.csv 3000 行真实记录):

iter mean reward 回合回报 回合长度 吞吐 sps
1 -1.343 -11.0 11.0 26,539
100 0.915 94.0 108.1 26,690
500 1.276 374.3 196.8 27,397
1000 1.907 350.2 187.6 27,337
1500 2.216 404.4 186.1 27,171
2000 2.130 391.6 191.6 27,170
2500 2.176 437.0 193.2 26,767
3000 2.287 440.8 194.8 26,646

收敛分三段,特征非常清晰:

  1. 快速起步(0–500 iter):reward 从 -1.343 转正并冲到 1.28,回合长度从 11 步拉到近 200 步——机器人先学会"不摔倒、基本跟随";
  2. 稳步提升(500–1500 iter):reward 从 1.28 到 2.22,跟踪精度持续改善;
  3. 收敛稳定(1500–3000 iter):reward 在 2.2 附近小幅波动,最终 2.287,回合回报稳定在 400–450。

稳态吞吐 2.6–2.7 万 env-step/s,与官方起身训练的 ~2.8 万吻合,NPU 利用率正常。


九、效果验证:机器人真的学会了吗

训练指标好看不等于学会。判断标准只有一个:加载训练后策略,让它在环境里实际跑。

加载 model_3000.pt,用 evaluate.py 以确定性策略跑完整回合:

  • 单回合完整跑满 501 步(10 秒)未提前结束,全程没有摔倒;
  • 回合总回报 2279.9;
  • 平均锚点位置误差 0.409 m、朝向误差 1.523 rad。

再对策略实际轨迹做运动学量化,和参考动作对比:

指标 策略实测 参考动作
pelvis 横向(x)位移幅度 0.311 m 0.300 m
pelvis 前后(y)幅度 0.111 m 0.146 m
pelvis 高度(z)幅度 0.029 m 0.055 m
双脚间距范围 0.084–0.784 m 0.129–0.565 m
双脚最低高度 0.0 m(贴地) 贴地

三个关键读数:

  • 横向位移 0.311 m vs 参考 0.300 m——几乎一致,机器人确实在横向闪身,不是原地晃;
  • 双脚最大分腿 0.784 m,比参考动作的 0.565 m 还大,说明策略学会了用大幅分腿来保持平衡;
  • pelvis 高度波动仅 0.029 m 且双脚始终贴地——整个过程中重心很稳。

也就是说,机器人在保持平衡的前提下,真实完成了「横向闪身 + 分腿」这个组合动作。

在这里插入图片描述

完整动态效果:

策略实际跟踪闪身步


十、踩坑汇总

问题 原因 解决办法
yt-dlp 下载抖音 403 关键 Cookie 是 HttpOnly,cookies.txt 拿不到 读 <video>.currentSrc 拿签名直链下载
GPU 运算报 no kernel image RTX 5070 是 sm_120,旧 torch 不支持 用 torch ≥ 2.7 + cu128,改用 gvhmr fork
GVHMR 无法在 Windows 原生装 顶层依赖 pytorch3d,无 Windows wheel 放到 WSL2 运行
hf 断点续传失效 hf-mirror CDN 每次 etag 变化 用 aria2c 多连接直链下载
gvhmr 后台运行静默退出 nohup 与预处理冲突 改前台运行
模型文件显示 ✓ 但加载失败 工具只看大小不验内容 单独 torch.load 验证,损坏则重下
daqp/proxqp 装不上 Python 3.15 无预编译 wheel 改用 quadprog
MuJoCo 报 ParseXML Error 中文/非 ASCII 路径 用英文路径副本,chdir 后加载
WebIDE 无法上传文件 无上传入口、xterm 粘贴不可靠 用 git 传输(npz 封装为 .bin)
长命令在终端被截断 xterm 输入缓冲限制 写成独立脚本 + 短命令运行

十一、总结

这次完整验证了「普通视频 → 机器人可训练动作 → 昇腾 NPU 训练 → 策略实际执行」的端到端链路:

  1. 用 GVHMR 从单目视频恢复三维人体姿态,解决了 Blackwell 新显卡与官方软件栈的兼容性问题;
  2. 用 GMR 把人体动作可靠重定向到 Q1 的 22 个关节——关节零越界、骨长刚性恒定(1e-7 级)、贴地稳定;
  3. 转成 50fps 标准 npz,并通过横向位移量化确认动作真实可跟随,而不是原地开合;
  4. 在昇腾 NPU 上用 PPO 训练 3000 iter,约 47 分钟收敛(reward 2.287、回合回报 440.8、吞吐 2.66 万 step/s);
  5. 加载训练后策略实际执行,机器人 10 秒没摔倒,横向位移 0.311 m 与参考动作 0.300 m 一致,真实复现了闪身步。

这条链路最大的价值在于通用性:换一段新视频,按同样的流程就能让 Q1 学会任意新动作。从"用仓库现成动作"到"看视频自学",机器人动作库的建设成本被真正降下来了。

对复现者最有用的三条经验:

  1. 新硬件先核对架构与软件栈兼容性——sm_120 配 torch 2.3 是白干;
  2. 重定向后必须做骨长、限位、归一化校验——这是唯一能证明运动学没算错的手段;
  3. 判断是否学会,只看策略实际执行——训练曲线会骗人,10 秒不摔倒 + 位移数据不会。

项目地址:[https://atomgit.com/GewisLab/q1-task-bench
https://atomgit.com/PrimebotTech

至于 sim2real……奈何小编目前还没能把 Q1 请到实验室,暂时只能在数字世界里反复折腾。真机验证这一步,就先留个 TODO 了。🥲
启元机器人开发者社区 https://dev.primebot.com/。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐