✨ 复制成功!

以文会友,打造好学人设!

📋 已复制到剪贴板:

行动派实操:用键盘让仿真机器人动起来

创建时间:2026-08-02 更新时间:2026-08-02 阅读次数:1002 次

上一节,你唤醒了一个灰白色的双足机器人,它在仿真窗口里静静地站着。你旋转视角、缩放画面,从各个角度打量它。但有一个问题始终悬而未决:它一动不动。

这一节,我们要终结这种静止。你要亲手写代码,向机器人的关节发送角度指令,看到它在屏幕上做出真实的动作。 这是你第一次扮演“大脑”的角色——你下达指令,虚拟身体执行指令。

核心概念:关节角度指令

在动手之前,我们需要建立一条最核心的认知:在仿真环境中,控制机器人就是控制关节的角度。

回想你自己的身体。你想弯肘,大脑不是直接命令“弯”,而是向下臂的屈肌发送一串神经信号,肌肉收缩,肘关节弯曲到一个特定角度。机器人的逻辑完全一样。你写的控制程序,本质上就是在不断回答同一个问题:每一个旋转关节,在下一个瞬间应该转多少度?

在MuJoCo中,关节的目标角度通过 data.ctrl 这个数组来设定。它是一个一维向量,长度等于机器人的驱动自由度数量。data.ctrl[0] 对应第一个驱动关节的目标角度,data.ctrl[1] 对应第二个,以此类推。你给这个数组赋值,仿真引擎就会在下一步尝试驱动电机去达到那些角度。

关节角度通常以弧度为单位。如果你更习惯角度,记住换算关系:

$$ \theta_{\text{rad}} = \theta_{\text{deg}} \times \frac{\pi}{180} $$

例如,$30^\circ$ 就是 $\frac{\pi}{6} \approx 0.5236$ 弧度。写代码时可以直接用 math.radians(30) 来转换。

第一步:让机器人感知到重力

在发送指令之前,我们需要先让前一小节的代码稍微进化一下。上一节的代码只是启动了仿真窗口并循环步进,但没有给任何控制信号——所以机器人只是被动地站在那里,任由重力把自己往下拉。

现在,我们要在仿真循环中主动设置 data.ctrl。先从一个最简单的实验开始:不发送任何指令,看看机器人会怎样。 把下面的代码跑起来:

import mujoco
import mujoco.viewer
import numpy as np

# 加载模型
model = mujoco.MjModel.from_xml_path('humanoid.xml')
data = mujoco.MjData(model)

# 打印驱动自由度数量
print(f"驱动关节数量: {model.nu}")

with mujoco.viewer.launch_passive(model, data) as viewer:
    while viewer.is_running():
        # 暂时不发送任何控制指令,保持默认值
        # data.ctrl 的默认值通常是0
        mujoco.mj_step(model, data)
        viewer.sync()

你会看到,机器人并没有僵住。它的手臂可能会在重力作用下微微下垂,身体可能有轻微的晃动。这是因为仿真引擎在计算重力、惯性力和关节力矩的平衡。那个静止的站姿,本身就是一个动态平衡的结果。

第二步:让一条手臂挥起来

现在,让我们干一件有仪式感的事情:让机器人对你挥手。

我们需要知道哪个关节控制手臂。对于 humanoid.xml 这个模型,肩部关节通常叫 arm_shoulder_y(控制前后摆动)和 arm_shoulder_x(控制左右展开)。你可以用上一节的关节遍历代码确认具体名称。

但更直接的方法,是通过关节名称找到它在 data.ctrl 中的索引。MuJoCo提供了这个功能:

import mujoco
import mujoco.viewer
import numpy as np
import math

model = mujoco.MjModel.from_xml_path('humanoid.xml')
data = mujoco.MjData(model)

# 通过关节名称找到控制索引
# 注意:关节名称和驱动器名称可能不同,需要查看 actuator 的名称
print("驱动器清单:")
for i in range(model.nu):
    actuator_name = model.actuator(i).name
    print(f"  索引 {i}: {actuator_name}")

运行这段代码。你会看到类似 shoulder_y_rshoulder_x_relbow_y_r 这样的名字(以右臂为例)。记下 shoulder_y_r 的索引号——假设是某个数字,我们后面会用到。

现在,我们要让这个关节按照正弦波来回摆动。正弦波是最自然的往复运动,手臂的前后摆动轨迹天然就是正弦曲线。目标角度随时间变化的公式为:

$$ \theta(t) = A \cdot \sin(2\pi \cdot f \cdot t) $$

其中 $A$ 是振幅(摆动的最大角度),$f$ 是频率(每秒摆动几次),$t$ 是当前时间。

写成代码如下:

import mujoco
import mujoco.viewer
import numpy as np
import math

model = mujoco.MjModel.from_xml_path('humanoid.xml')
data = mujoco.MjData(model)

# 找到右肩前后摆动关节的控制索引
shoulder_idx = -1
for i in range(model.nu):
    if 'shoulder_y_r' in model.actuator(i).name:
        shoulder_idx = i
        print(f"找到右肩关节驱动器: 索引 {i}, 名称 {model.actuator(i).name}")
        break

if shoulder_idx == -1:
    print("未找到 shoulder_y_r 驱动器,请检查驱动器清单中的实际名称")
    exit()

# 获取仿真时间步长
timestep = model.opt.timestep
sim_time = 0.0

with mujoco.viewer.launch_passive(model, data) as viewer:
    while viewer.is_running():
        # 计算挥手动作的目标角度
        # 振幅30度,频率1.5Hz
        amplitude = math.radians(30)
        frequency = 1.5
        target_angle = amplitude * math.sin(2 * math.pi * frequency * sim_time)

        # 将目标角度写入控制数组
        data.ctrl[shoulder_idx] = target_angle

        # 步进仿真
        mujoco.mj_step(model, data)
        viewer.sync()

        # 更新时间
        sim_time += timestep

运行这段代码。你会看到机器人开始对你挥手!它的右臂以前后30度的幅度,每秒1.5次的频率来回摆动。这个动作虽然简单,但它是你操控虚拟身体的第一步——从零到一的一步。

第三步:让机器人做深蹲

挥手是单关节运动。现在我们要做一个多关节协同的动作:深蹲。

深蹲需要同时弯曲髋关节、膝关节和踝关节。双足机器人的两条腿各有三到四个关节参与这个动作。我们需要找到所有相关的驱动器索引,然后让它们同时弯曲到一个特定角度。

import mujoco
import mujoco.viewer
import numpy as np
import math

model = mujoco.MjModel.from_xml_path('humanoid.xml')
data = mujoco.MjData(model)

# 找到双腿相关关节的驱动器索引
# 髋关节前后摆动 (hip_y),膝关节 (knee),踝关节 (ankle_y)
leg_joints = []
for i in range(model.nu):
    name = model.actuator(i).name
    if any(keyword in name for keyword in ['hip_y', 'knee', 'ankle_y']):
        leg_joints.append(i)
        print(f"找到腿部驱动器: 索引 {i}, 名称 {name}")

print(f"共找到 {len(leg_joints)} 个腿部关节驱动器")

# 仿真参数
timestep = model.opt.timestep
sim_time = 0.0
squat_duration = 2.0  # 下蹲用时2秒
hold_duration = 1.0   # 保持1秒
stand_duration = 2.0  # 起立用时2秒
cycle_duration = squat_duration + hold_duration + stand_duration

with mujoco.viewer.launch_passive(model, data) as viewer:
    while viewer.is_running():
        # 计算当前在运动周期中的位置
        phase_time = sim_time % cycle_duration

        if phase_time < squat_duration:
            # 下蹲阶段:关节角度从0线性增加到目标角度
            progress = phase_time / squat_duration
            # 髋关节和膝关节弯曲45度,踝关节弯曲20度(保持脚掌贴地)
            angle = progress * math.radians(45)
            ankle_angle = progress * math.radians(20)
        elif phase_time < squat_duration + hold_duration:
            # 保持阶段:维持最大弯曲角度
            angle = math.radians(45)
            ankle_angle = math.radians(20)
        else:
            # 起立阶段:关节角度线性减小回0
            progress = (phase_time - squat_duration - hold_duration) / stand_duration
            angle = (1 - progress) * math.radians(45)
            ankle_angle = (1 - progress) * math.radians(20)

        # 将计算出的角度写入腿部关节
        for idx in leg_joints:
            name = model.actuator(idx).name
            if 'ankle' in name:
                data.ctrl[idx] = ankle_angle
            else:
                data.ctrl[idx] = angle

        mujoco.mj_step(model, data)
        viewer.sync()
        sim_time += timestep

运行这段代码。机器人缓缓蹲下,保持一秒,再缓缓站起,然后循环往复。你刚刚实现了一个完整的深蹲周期——这是多关节协同控制的第一次实战。

有几个值得注意的细节。踝关节的角度和髋膝不同,因为它需要在身体下降时保持脚掌贴地。如果所有关节弯曲相同角度,脚后跟会翘起来,机器人的姿态就不自然了。这只是最粗糙的深蹲,真正的稳定行走还需要考虑重心投影、零力矩点等更精细的控制——那些我们会在后面的章节里学到。但此刻,看到你的机器人从静止到蹲下再站起,你已经感受到了控制的本质:给每一个关节,在每一个时刻,指定一个合适的角度。

第四步:用键盘实时操控机器人

挥手和深蹲都是脚本自动运行的。但真正好玩的是用键盘实时控制机器人,就像打游戏一样。

我们要用到 pygame 这个库来捕捉按键状态(需要先 pip install pygame)。逻辑很简单:按上键就下蹲,按下键就起立,按W键就双臂上举,按S键就双臂放下。

import mujoco
import mujoco.viewer
import numpy as np
import math
import pygame
import sys

# 初始化pygame用于键盘输入
pygame.init()
screen = pygame.display.set_mode((200, 200))
pygame.display.set_caption("键盘控制器")

model = mujoco.MjModel.from_xml_path('humanoid.xml')
data = mujoco.MjData(model)

# 找到关键关节的驱动器索引
arm_indices = {}   # 手臂关节
leg_indices = {}   # 腿部关节

for i in range(model.nu):
    name = model.actuator(i).name
    if 'shoulder' in name:
        arm_indices[name] = i
    if any(kw in name for kw in ['hip_y', 'knee', 'ankle']):
        leg_indices[name] = i

print("手臂控制关节:", arm_indices)
print("腿部控制关节:", leg_indices)

# 状态变量
arm_angle = 0.0       # 当前手臂角度
leg_angle = 0.0       # 当前腿部角度
angle_step = 0.02     # 每次按键改变的角度增量(弧度)

with mujoco.viewer.launch_passive(model, data) as viewer:
    while viewer.is_running():
        # 处理pygame事件(必须调用,否则窗口会卡住)
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                viewer.close()
                pygame.quit()
                sys.exit()

        # 检测按键状态
        keys = pygame.key.get_pressed()

        # 控制腿部:上键下蹲,下键起立
        if keys[pygame.K_UP]:
            leg_angle = min(leg_angle + angle_step, math.radians(60))
            print(f"下蹲: {math.degrees(leg_angle):.1f}度", end='\r')
        if keys[pygame.K_DOWN]:
            leg_angle = max(leg_angle - angle_step, 0)
            print(f"起立: {math.degrees(leg_angle):.1f}度", end='\r')

        # 控制手臂:W键上举,S键放下
        if keys[pygame.K_w]:
            arm_angle = min(arm_angle + angle_step, math.radians(90))
            print(f"举手: {math.degrees(arm_angle):.1f}度", end='\r')
        if keys[pygame.K_s]:
            arm_angle = max(arm_angle - angle_step, math.radians(-45))
            print(f"放手: {math.degrees(arm_angle):.1f}度", end='\r')

        # 将角度写入对应关节
        for name, idx in arm_indices.items():
            # 根据关节的旋转轴决定角度正负
            if 'y' in name:
                data.ctrl[idx] = -arm_angle
            else:
                data.ctrl[idx] = arm_angle

        for name, idx in leg_indices.items():
            if 'ankle' in name:
                data.ctrl[idx] = leg_angle * 0.4  # 踝关节弯曲较小
            else:
                data.ctrl[idx] = leg_angle

        mujoco.mj_step(model, data)
        viewer.sync()

pygame.quit()

运行这段代码。一个 pygame 小窗口会弹出(保持它在前台),同时仿真窗口也打开。现在按下方向键的上键——机器人开始下蹲;按下键——它缓缓站起。按W键——双臂上举;按S键——放下。

你在用键盘实时操控一具虚拟身体。 每一个按键按下,都会转化为特定关节的角度变化。这种实时反馈的操控感,是学习具身智能最上瘾的体验之一。尽情玩一会儿——让机器人做各种奇怪的动作,蹲到最低点再跳起来(虽然它不会真的跳),把一条手臂举到最高另一条放下。破坏性的实验在仿真里完全免费。

你刚刚获得了什么能力?

停下手指,回顾一下这一节你学会了什么。

你理解了关节角度控制是机器人运动控制的基本单元。你用正弦波让机器人挥手,用分段函数让它深蹲,用键盘实时操控它的手臂和腿部。这些看起来简单的操作,背后是全套的机器人控制流程:感知按键输入,决策目标角度,驱动执行器运动。

更重要的是,你建立了和这具虚拟身体的肌肉记忆。你知道了哪个关节管前后摆动,哪个管左右开合,知道了腿和手臂的运动范围大概在多少度以内。这些直觉,在后面学习强化学习和运动规划时,会成为你调试算法的第一手依据。

在下一节,我们将换一个视角。这一节你扮演的是遥控操作者,手动给出每一个关节的指令。但机器人未来需要自己做出决策。那么,机器人的“大脑”是如何从传感器数据中,自动计算出该执行什么动作的?我们将进入具身智能最核心的篇章:决策。

本教程共13节,当前为第10节!
本教程最新修订时间为:2026-08-06 21:35:58

📌 面试天下网:一款服务于大一新生的口袋书,让大家在无聊的公共课上可以学习大模型技术!
📌 网站公告:人人都需要的干眼克星上线啦>>>>>>
📌 网站公告:2026年7月招聘兼职>>>>>>