跳到主要内容

机器人控制

本教程将深入介绍DISCOVERSE中的机器人控制系统,包括不同的控制模式、运动学计算、轨迹规划和实时控制。

🎯 学习目标

  • 掌握DISCOVERSE的所有控制模式
  • 理解正向和反向运动学
  • 学习轨迹规划和平滑控制
  • 实现复杂的机器人操作任务

🎮 控制模式详解

1. 关节位置控制 (pd_joint_pos)

最基础的控制模式,直接指定目标关节角度:

import discoverse as dv
import numpy as np

env = dv.make_env("airbot_play", control_mode="pd_joint_pos")
obs = env.reset()

# 获取当前关节位置
current_qpos = obs["qpos"] # 当前关节角度
print(f"当前关节位置: {current_qpos}")

# 设置目标关节位置
target_qpos = np.array([0.0, -0.5, 0.0, 1.57, 0.0, 1.0, 0.0])

# 执行控制
obs, reward, done, info = env.step(target_qpos)
env.render()

2. 关节增量控制 (pd_joint_delta_pos)

通过增量方式控制关节,适合精细操作:

env = dv.make_env("airbot_play", control_mode="pd_joint_delta_pos")
obs = env.reset()

# 平滑移动到目标位置
target = np.array([0.0, -0.5, 0.0, 1.57, 0.0, 1.0, 0.0])
current = obs["qpos"]

for step in range(100):
# 计算增量
delta = (target - current) * 0.1 # 10%的步长

# 执行增量控制
obs, reward, done, info = env.step(delta)
current = obs["qpos"]

# 检查是否到达目标
if np.linalg.norm(target - current) < 0.01:
print(f"在第{step}步到达目标")
break

env.render()

3. 末端执行器控制 (pd_ee_pose)

直接控制机器人末端执行器的位姿:

env = dv.make_env("airbot_play", control_mode="pd_ee_pose")
obs = env.reset()

# 获取当前末端执行器位姿
current_ee_pose = obs["ee_pose"] # [x, y, z, qx, qy, qz, qw]
print(f"当前末端位姿: {current_ee_pose}")

# 设置目标位姿 (位置 + 四元数)
target_position = [0.5, 0.2, 0.3] # x, y, z
target_quaternion = [0.0, 0.0, 0.0, 1.0] # qx, qy, qz, qw (无旋转)
target_pose = target_position + target_quaternion

# 执行控制
obs, reward, done, info = env.step(target_pose)
env.render()

4. 速度控制 (pd_joint_vel)

控制关节速度:

env = dv.make_env("airbot_play", control_mode="pd_joint_vel")
obs = env.reset()

# 设置关节速度 (rad/s)
joint_velocities = np.array([0.1, -0.2, 0.0, 0.3, 0.0, -0.1, 0.0])

for step in range(100):
obs, reward, done, info = env.step(joint_velocities)

# 可以动态调整速度
if step == 50:
joint_velocities *= -1 # 反向运动

env.render()

🔧 运动学计算

正向运动学 (Forward Kinematics)

从关节角度计算末端执行器位姿:

import discoverse as dv

env = dv.make_env("airbot_play")
robot = env.robot # 获取机器人对象

# 给定关节角度
joint_angles = np.array([0.0, -0.5, 0.0, 1.57, 0.0, 1.0, 0.0])

# 计算正向运动学
ee_pose = robot.forward_kinematics(joint_angles)
print(f"末端位置: {ee_pose[:3]}")
print(f"末端方向 (四元数): {ee_pose[3:]}")

# 也可以计算雅可比矩阵
jacobian = robot.compute_jacobian(joint_angles)
print(f"雅可比矩阵形状: {jacobian.shape}") # (6, 7) for 7-DOF arm

反向运动学 (Inverse Kinematics)

从目标末端位姿计算关节角度:

# 目标末端位姿
target_position = [0.5, 0.0, 0.3]
target_orientation = [0.0, 0.0, 0.0, 1.0] # 四元数
target_pose = np.array(target_position + target_orientation)

# 计算反向运动学
ik_solution = robot.inverse_kinematics(
target_pose,
initial_guess=joint_angles, # 初始猜测
tolerance=1e-4, # 求解精度
max_iterations=100 # 最大迭代次数
)

if ik_solution is not None:
print(f"IK解: {ik_solution}")

# 验证解的正确性
computed_pose = robot.forward_kinematics(ik_solution)
error = np.linalg.norm(computed_pose - target_pose)
print(f"位姿误差: {error:.6f}")
else:
print("未找到IK解")

多解处理

IK问题可能有多个解,DISCOVERSE提供了处理方法:

# 获取所有可能的IK解
all_solutions = robot.inverse_kinematics_all(
target_pose,
tolerance=1e-4
)

print(f"找到 {len(all_solutions)} 个解")

# 选择最接近当前关节角度的解
current_angles = obs["qpos"]
best_solution = None
min_distance = float('inf')

for solution in all_solutions:
distance = np.linalg.norm(solution - current_angles)
if distance < min_distance:
min_distance = distance
best_solution = solution

print(f"最优解: {best_solution}")

📈 轨迹规划

关节空间轨迹

在关节空间中规划平滑轨迹:

def plan_joint_trajectory(start_angles, end_angles, duration, freq=50):
"""规划关节空间轨迹"""
num_steps = int(duration * freq)
trajectory = []

for i in range(num_steps + 1):
t = i / num_steps
# 使用三次多项式插值
s = 3 * t**2 - 2 * t**3 # S曲线
angles = start_angles + s * (end_angles - start_angles)
trajectory.append(angles)

return np.array(trajectory)

# 规划轨迹
start = obs["qpos"]
goal = np.array([0.0, -0.5, 0.0, 1.57, 0.0, 1.0, 0.0])
trajectory = plan_joint_trajectory(start, goal, duration=3.0)

# 执行轨迹
env = dv.make_env("airbot_play", control_mode="pd_joint_pos")
obs = env.reset()

for waypoint in trajectory:
obs, reward, done, info = env.step(waypoint)
env.render()

if done:
break

笛卡尔空间轨迹

在笛卡尔空间中规划直线轨迹:

def plan_cartesian_trajectory(start_pose, end_pose, duration, freq=50):
"""规划笛卡尔空间直线轨迹"""
num_steps = int(duration * freq)
trajectory = []

start_pos, start_quat = start_pose[:3], start_pose[3:]
end_pos, end_quat = end_pose[:3], end_pose[3:]

for i in range(num_steps + 1):
t = i / num_steps

# 位置线性插值
pos = start_pos + t * (end_pos - start_pos)

# 四元数球面线性插值 (SLERP)
quat = slerp(start_quat, end_quat, t)

pose = np.concatenate([pos, quat])
trajectory.append(pose)

return np.array(trajectory)

def slerp(q1, q2, t):
"""四元数球面线性插值"""
q1, q2 = np.array(q1), np.array(q2)

# 确保选择最短路径
if np.dot(q1, q2) < 0:
q2 = -q2

dot = np.clip(np.dot(q1, q2), -1.0, 1.0)
omega = np.arccos(np.abs(dot))

if np.sin(omega) < 1e-6:
return q1 # 四元数几乎相同

return (np.sin((1-t)*omega) * q1 + np.sin(t*omega) * q2) / np.sin(omega)

# 执行笛卡尔轨迹
env = dv.make_env("airbot_play", control_mode="pd_ee_pose")
obs = env.reset()

start_pose = obs["ee_pose"]
end_pose = np.array([0.5, 0.2, 0.4, 0.0, 0.0, 0.0, 1.0])
cart_trajectory = plan_cartesian_trajectory(start_pose, end_pose, duration=2.0)

for waypoint in cart_trajectory:
obs, reward, done, info = env.step(waypoint)
env.render()

🎯 高级控制技术

阻抗控制

实现基于力的柔顺控制:

def impedance_control(target_pose, current_pose, current_vel, 
stiffness=1000, damping=50):
"""阻抗控制器"""
# 位置误差
pos_error = target_pose[:3] - current_pose[:3]

# 方向误差 (简化为角度误差)
orient_error = orientation_error(target_pose[3:], current_pose[3:])

# 合并位置和方向误差
pose_error = np.concatenate([pos_error, orient_error])

# 计算期望力/力矩
desired_wrench = stiffness * pose_error - damping * current_vel

return desired_wrench

def orientation_error(target_quat, current_quat):
"""计算四元数方向误差"""
# 转换为旋转矩阵
target_rot = quaternion_to_rotation_matrix(target_quat)
current_rot = quaternion_to_rotation_matrix(current_quat)

# 误差旋转矩阵
error_rot = target_rot @ current_rot.T

# 转换为轴角表示
return rotation_matrix_to_axis_angle(error_rot)

# 使用阻抗控制
env = dv.make_env("airbot_play", control_mode="pd_ee_pose")
obs = env.reset()

target_pose = np.array([0.5, 0.0, 0.3, 0.0, 0.0, 0.0, 1.0])

for step in range(200):
current_pose = obs["ee_pose"]
current_vel = obs["ee_vel"] # 如果可用

# 计算阻抗控制
wrench = impedance_control(target_pose, current_pose, current_vel)

# 这里需要转换为关节空间控制
# 实际实现中会使用雅可比矩阵转换

obs, reward, done, info = env.step(target_pose)
env.render()

力控制

实现基于力反馈的控制:

def force_control(target_force, current_force, dt=0.02):
"""简单的力控制器"""
force_error = target_force - current_force

# PID控制
kp, ki, kd = 0.1, 0.01, 0.05

# 这里应该维护积分和微分项的历史
integral_term = 0 # 需要累积
derivative_term = 0 # 需要计算变化率

control_output = (kp * force_error +
ki * integral_term +
kd * derivative_term)

return control_output

# 在有力传感器的环境中使用
env = dv.make_env("airbot_play_force_sensor")
obs = env.reset()

target_force = np.array([0, 0, -10]) # 向下10N的力

for step in range(100):
if "force_sensor" in obs:
current_force = obs["force_sensor"]
force_cmd = force_control(target_force, current_force)

# 将力指令转换为位置指令
# 实际实现需要更复杂的转换

obs, reward, done, info = env.step(action)
env.render()

🔄 控制循环优化

PID参数调优

class PIDController:
def __init__(self, kp, ki, kd, dt=0.02):
self.kp, self.ki, self.kd = kp, ki, kd
self.dt = dt
self.integral = 0
self.previous_error = 0

def update(self, error):
# 比例项
proportional = self.kp * error

# 积分项
self.integral += error * self.dt
integral = self.ki * self.integral

# 微分项
derivative = self.kd * (error - self.previous_error) / self.dt
self.previous_error = error

# PID输出
output = proportional + integral + derivative
return output

def reset(self):
self.integral = 0
self.previous_error = 0

# 为每个关节创建PID控制器
joint_controllers = [
PIDController(kp=100, ki=1, kd=10) for _ in range(7)
]

env = dv.make_env("airbot_play", control_mode="pd_joint_vel")
obs = env.reset()

target_positions = np.array([0.0, -0.5, 0.0, 1.57, 0.0, 1.0, 0.0])

for step in range(200):
current_positions = obs["qpos"]
joint_velocities = []

for i, controller in enumerate(joint_controllers):
error = target_positions[i] - current_positions[i]
velocity = controller.update(error)
joint_velocities.append(velocity)

obs, reward, done, info = env.step(np.array(joint_velocities))
env.render()

安全限制

class SafetyWrapper:
def __init__(self, env, joint_limits, velocity_limits, acceleration_limits):
self.env = env
self.joint_limits = joint_limits # [(min, max), ...]
self.velocity_limits = velocity_limits
self.acceleration_limits = acceleration_limits
self.previous_action = None
self.previous_time = None

def step(self, action):
# 关节位置限制
for i, (min_pos, max_pos) in enumerate(self.joint_limits):
action[i] = np.clip(action[i], min_pos, max_pos)

# 速度限制
if self.previous_action is not None:
dt = 0.02 # 假设20ms控制周期
velocity = (action - self.previous_action) / dt

for i, max_vel in enumerate(self.velocity_limits):
if abs(velocity[i]) > max_vel:
# 限制速度
sign = np.sign(velocity[i])
action[i] = self.previous_action[i] + sign * max_vel * dt

self.previous_action = action.copy()
return self.env.step(action)

def reset(self):
self.previous_action = None
return self.env.reset()

# 使用安全包装器
base_env = dv.make_env("airbot_play", control_mode="pd_joint_pos")
safe_env = SafetyWrapper(
base_env,
joint_limits=[(-3.14, 3.14)] * 7, # 关节角度限制
velocity_limits=[2.0] * 7, # 速度限制 (rad/s)
acceleration_limits=[10.0] * 7 # 加速度限制 (rad/s²)
)

📊 控制性能评估

轨迹跟踪精度

def evaluate_tracking_performance(target_trajectory, actual_trajectory):
"""评估轨迹跟踪性能"""
# 位置误差
position_errors = np.linalg.norm(
target_trajectory - actual_trajectory, axis=1
)

# 统计指标
mae = np.mean(position_errors) # 平均绝对误差
rmse = np.sqrt(np.mean(position_errors**2)) # 均方根误差
max_error = np.max(position_errors) # 最大误差

return {
'MAE': mae,
'RMSE': rmse,
'Max Error': max_error,
'Error Std': np.std(position_errors)
}

# 性能测试
target_traj = plan_joint_trajectory(start, goal, duration=3.0)
actual_traj = []

env = dv.make_env("airbot_play", control_mode="pd_joint_pos")
obs = env.reset()

for waypoint in target_traj:
obs, reward, done, info = env.step(waypoint)
actual_traj.append(obs["qpos"])

actual_traj = np.array(actual_traj)
performance = evaluate_tracking_performance(target_traj, actual_traj)

print("轨迹跟踪性能:")
for metric, value in performance.items():
print(f" {metric}: {value:.4f}")

🎯 实际应用示例

拾取物体任务

def pick_object_demo():
"""演示拾取物体的完整控制流程"""
env = dv.make_env("airbot_play_pick_task", control_mode="pd_ee_pose")
obs = env.reset()

# 1. 移动到物体上方
object_pos = obs["object_pose"][:3]
pre_grasp_pose = object_pos + [0, 0, 0.1] # 上方10cm
pre_grasp_pose = np.concatenate([pre_grasp_pose, [0, 0, 0, 1]])

print("移动到预抓取位置...")
for _ in range(50):
obs, reward, done, info = env.step(pre_grasp_pose)
env.render()

# 2. 下降到抓取位置
grasp_pose = object_pos + [0, 0, 0.02] # 略高于物体
grasp_pose = np.concatenate([grasp_pose, [0, 0, 0, 1]])

print("下降到抓取位置...")
for _ in range(30):
obs, reward, done, info = env.step(grasp_pose)
env.render()

# 3. 闭合夹爪
print("闭合夹爪...")
# 这里需要夹爪控制接口

# 4. 提升物体
lift_pose = grasp_pose.copy()
lift_pose[2] += 0.2 # 提升20cm

print("提升物体...")
for _ in range(50):
obs, reward, done, info = env.step(lift_pose)
env.render()

print("拾取任务完成!")

# 运行演示
pick_object_demo()

🎯 下一步

现在您已经掌握了机器人控制的基础,接下来可以学习:

👉 传感器配置

或者探索更高级的主题: