让机器人跟着抖音视频学跳舞:启元 Q1 闪身步全链路实战
让机器人跟着抖音视频学跳舞:启元 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)——躺着也能把训练跑通。
这一轮的目标更狠一点:能不能让机器人从一段普通人拍的视频里学会一个新动作?
区别在于,起身动作的参考数据是别人做好喂到嘴边的,而这次的"闪身步"需要我们自己从视频里重建。链路长了一大截,中间要解决三个问题:
- 看懂视频里的人在干什么——从 RGB 像素恢复出人体在三维空间中的姿态;
- 把人的动作搬到机器人身上——人和机器人骨骼结构、关节数、肢体比例都不同,要做动作重定向(retargeting),还要保证关节不越限、不穿模、不摔倒;
- 转成训练框架能读的格式——统一成 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 本地两处适配
- 求解器替换:
daqp、proxqp在 Python 3.15 下没有预编译 wheel、源码编译又缺 MSVC,改用有 wheel 的quadprog(初始化 GMR 时显式传solver="quadprog")。 - 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,字段也对不上。需要做五件事:
- 四元数从 xyzw 转为 wxyz;
- 配置空间重采样,30fps → 50fps(210 帧 → 349 帧,时长保持 6.98s);
- MuJoCo 正运动学(FK)算每个 body 的世界位置和姿态;
- 由重采样后轨迹差分计算线速度、角速度;
- 贴地抬升,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 |
收敛分三段,特征非常清晰:
- 快速起步(0–500 iter):reward 从 -1.343 转正并冲到 1.28,回合长度从 11 步拉到近 200 步——机器人先学会"不摔倒、基本跟随";
- 稳步提升(500–1500 iter):reward 从 1.28 到 2.22,跟踪精度持续改善;
- 收敛稳定(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 训练 → 策略实际执行」的端到端链路:
- 用 GVHMR 从单目视频恢复三维人体姿态,解决了 Blackwell 新显卡与官方软件栈的兼容性问题;
- 用 GMR 把人体动作可靠重定向到 Q1 的 22 个关节——关节零越界、骨长刚性恒定(1e-7 级)、贴地稳定;
- 转成 50fps 标准 npz,并通过横向位移量化确认动作真实可跟随,而不是原地开合;
- 在昇腾 NPU 上用 PPO 训练 3000 iter,约 47 分钟收敛(reward 2.287、回合回报 440.8、吞吐 2.66 万 step/s);
- 加载训练后策略实际执行,机器人 10 秒没摔倒,横向位移 0.311 m 与参考动作 0.300 m 一致,真实复现了闪身步。
这条链路最大的价值在于通用性:换一段新视频,按同样的流程就能让 Q1 学会任意新动作。从"用仓库现成动作"到"看视频自学",机器人动作库的建设成本被真正降下来了。
对复现者最有用的三条经验:
- 新硬件先核对架构与软件栈兼容性——sm_120 配 torch 2.3 是白干;
- 重定向后必须做骨长、限位、归一化校验——这是唯一能证明运动学没算错的手段;
- 判断是否学会,只看策略实际执行——训练曲线会骗人,10 秒不摔倒 + 位移数据不会。
项目地址:[https://atomgit.com/GewisLab/q1-task-bench
https://atomgit.com/PrimebotTech
至于 sim2real……奈何小编目前还没能把 Q1 请到实验室,暂时只能在数字世界里反复折腾。真机验证这一步,就先留个 TODO 了。🥲
启元机器人开发者社区 https://dev.primebot.com/。
更多推荐




所有评论(0)