turtlebot3-burger_150.png
turtlebot3-waffle-pi_150.png
turtlebot3-arm_150.png
walking-y2_150.png
turbot3-multi_150.png
turbot3-dl-ros1_150.png
turbot3-ai.png
turbot3-dl-ros2_150.png
turbot3-slam_150.png
turbot3-arm_150.png
turtlebot4-lite_150.png
turtlebot4-pro_150.png
turbot4-dl_150.png
turbot4-ai_150.png
aidriving-racebot_150.png
aidriving-autodrive_150.png
turtlebot-arm_150.png
openmanipulator-x_150.png
Home » OpenDuckMini强化学习框架入门教程 » OpenDuckMini强化学习框架入门教程-MuJoCo推理

OpenDuckMini强化学习框架入门教程-MuJoCo推理

纠错,疑问,交流: 请进入讨论区请点击进入页面,扫码加入微信群或Q群进行交流

获取最新文章: 扫一扫加入“创客智造”公众号

欢迎加入我们的openduckmini交流群,微信扫描右侧二维码立即进群交流

群二维码

MuJoCo 推理

  • 理解MuJoCo推理,包含MJInferBase 基类,MjInfer 主类,用户输入处理,实时步态周期,动作平滑滤波器等

概述

MuJoCo 推理系统是 Open Duck Playground 的实时仿真运行环境。它通过 MjInfer 类加载训练好的 ONNX 策略模型,在 MuJoCo 物理引擎中进行实时仿真,并通过 mujoco.viewer 提供 3D 可视化界面。用户可以通过键盘或摇杆实时控制机器人的运动。

MJInferBase 基类

mujoco_infer_base.py 中的 MJInferBase 提供推理所需的基础设施:

模型加载

class MJInferBase:
    def __init__(self, model_path):
        self.model = mujoco.MjModel.from_xml_string(
            epath.Path(model_path).read_text(), assets=base.get_assets()
        )
        self.model.opt.timestep = 0.002  # 仿真步长
        self.data = mujoco.MjData(self.model)
        mujoco.mj_step(self.model, self.data)

关节分类

与训练环境相同,推理基类也对关节进行分类:

self.floating_base_name      # 浮动基座名称
self.actuator_names          # 执行器关节列表
self.joint_names             # 所有关节列表
self.backlash_joint_names    # 回差关节列表

self.num_dofs = self.model.nu  # 执行器数量 (14)

传感器访问

推理基类通过传感器地址直接访问数据,避免了字符串查找的开销:

def get_gyro(self, data):
    return data.sensordata[self.gyro_addr:self.gyro_addr + self.gyro_dimensions]

def get_accelerometer(self, data):
    return data.sensordata[self.accelerometer_addr:self.accelerometer_addr + 3]

def get_gravity(self, data):
    return data.sensordata[self.gravity_id:self.gravity_id + 3]

接触检测

足部接触检测通过 MuJoCo 的碰撞检测实现:

def get_feet_contacts(self, data):
    left_contact = self.check_contact(data, "foot_assembly", "floor")
    right_contact = self.check_contact(data, "foot_assembly_2", "floor")
    return left_contact, right_contact

MjInfer 主类

mujoco_infer.py 中的 MjInfer 是推理的主入口。

初始化

class MjInfer(MJInferBase):
    def __init__(self, model_path, reference_data, onnx_model_path, standing):
        # 策略加载
        self.policy = OnnxInfer(onnx_model_path, awd=True)
        
        # 参考运动(非站立模式)
        if not self.standing:
            self.PRM = PolyReferenceMotion(reference_data)
        
        # 动作滤波器
        self.action_filter = LowPassActionFilter(50, cutoff_frequency=37.5)
        
        # 命令范围
        self.COMMANDS_RANGE_X = [-0.15, 0.15]       # 前进速度
        self.COMMANDS_RANGE_Y = [-0.2, 0.2]         # 横向速度
        self.COMMANDS_RANGE_THETA = [-1.0, 1.0]     # 旋转速度
        
        # 动作历史
        self.last_action = np.zeros(self.num_dofs)
        self.last_last_action = np.zeros(self.num_dofs)
        self.last_last_last_action = np.zeros(self.num_dofs)
        
        # 默认姿态
        self.default_actuator = self.model.keyframe("home").ctrl
        self.motor_targets = self.default_actuator.copy()
        self.prev_motor_targets = self.default_actuator.copy()

观测构建

get_obs() 方法将仿真数据转换为与训练时相同的观测向量:

def get_obs(self, data, command):
    gyro = self.get_gyro(data)
    accelerometer = self.get_accelerometer(data)
    joint_angles = self.get_actuator_joints_qpos(data.qpos)
    joint_vel = self.get_actuator_joints_qvel(data.qvel)
    contacts = self.get_feet_contacts(data)
    
    obs = np.concatenate([
        gyro,           # 3
        accelerometer,  # 3
        command,        # 7
        joint_angles - self.default_actuator,  # 10
        joint_vel * self.dof_vel_scale,        # 10
        self.last_action,       # 10
        self.last_last_action,  # 10
        self.last_last_last_action,  # 10
        self.motor_targets,     # 10
        contacts,               # 2
        self.imitation_phase,   # 2
    ])
    return obs

主循环

def run(self):
    with mujoco.viewer.launch_passive(
        self.model, self.data, key_callback=self.key_callback
    ) as viewer:
        counter = 0
        while viewer.is_running():
            step_start = time.time()
            
            # 物理仿真步进(1 步 = 0.002s)
            mujoco.mj_step(self.model, self.data)
            counter += 1
            
            # 控制频率 (50 Hz, 每 10 仿真步)
            if counter % self.decimation == 0:
                # 1. 更新参考运动相位
                if not self.standing:
                    self.imitation_i += 1.0 * self.phase_frequency_factor
                    self.imitation_i %= self.PRM.nb_steps_in_period
                    self.imitation_phase = np.array([
                        cos(self.imitation_i / N * 2 * pi),
                        sin(self.imitation_i / N * 2 * pi),
                    ])
                
                # 2. 构建观测
                obs = self.get_obs(self.data, self.commands)
                self.saved_obs.append(obs)
                
                # 3. 策略推理
                action = self.policy.infer(obs)
                
                # 4. 更新动作历史
                self.last_last_last_action = self.last_last_action.copy()
                self.last_last_action = self.last_action.copy()
                self.last_action = action.copy()
                
                # 5. 计算电机目标
                self.motor_targets = (
                    self.default_actuator + action * self.action_scale
                )
                
                # 6. 电机速度限制
                if USE_MOTOR_SPEED_LIMITS:
                    self.motor_targets = np.clip(
                        self.motor_targets,
                        self.prev_motor_targets - max_vel * dt,
                        self.prev_motor_targets + max_vel * dt,
                    )
                    self.prev_motor_targets = self.motor_targets.copy()
                
                # 7. 应用动作
                self.data.ctrl = self.motor_targets.copy()
            
            # 同步渲染
            viewer.sync()
            
            # 保持实时帧率
            time_until_next_step = self.model.opt.timestep - (time.time() - step_start)
            if time_until_next_step > 0:
                time.sleep(time_until_next_step)

用户输入处理

键盘回调

key_callback 方法处理用户的键盘输入:

def key_callback(self, keycode):
    if keycode == 72:  # 'H' 键
        self.head_control_mode = not self.head_control_mode
    
    if not self.head_control_mode:
        # 摇杆模式:控制运动
        if keycode == 265:  # ↑ 前进
            lin_vel_x = COMMANDS_RANGE_X[1]
        elif keycode == 264:  # ↓ 后退
            lin_vel_x = COMMANDS_RANGE_X[0]
        # ... 横向 (←/→) 和 旋转 (A/E)
    else:
        # 头部控制模式
        # ↑/↓: 颈部俯仰, ←/→: 头部偏航, A/E: 头部滚动
        ...

控制模式

模式 切换方式 功能
摇杆模式 默认 控制机器人线速度和角速度
头部模式 按 H 切换 控制机器人头部 4 个自由度

实时步态周期

在不使用模仿奖励时,推理系统包含一个内置的步态相位机制:

imitation_phase = [cos(phase), sin(phase)]

其中 phase = (imitation_i / nb_steps_in_period) * 2 * pi

phase 范围 [0, 2π],通过 P/M 键可以调节步态频率。

动作平滑滤波器

LowPassActionFilter 在推理中可用于平滑动作输出:

self.action_filter = LowPassActionFilter(50, cutoff_frequency=37.5)

# 使用滤波器
self.action_filter.push(action)
action = self.action_filter.get_filtered_action()

目前默认启用了速度限制而非低通滤波,速度限制限制了每步关节位置的变化量(max_motor_velocity = 5.24 rad/s),更接近真实舵机的行为。

纠错,疑问,交流: 请进入讨论区请点击进入页面,扫码加入微信群或Q群进行交流

获取最新文章: 扫一扫加入“创客智造”公众号

欢迎加入我们的openduckmini交流群,微信扫描右侧二维码立即进群交流

群二维码

标签: OpenDuckMini强化学习框架