Chenyj Space
返回博客
·52 分钟

具身智能运动控制:让机器人灵活运动

具身智能运动控制强化学习路径规划机器人学AI

运动控制概述

运动控制是具身智能的核心能力之一,决定了智能体如何执行物理动作。关键挑战包括:

  • 高维控制:多关节协调
  • 非线性动力学:复杂物理约束
  • 实时性:毫秒级决策
  • 安全性:避免碰撞和伤害

运动学基础

1. 正运动学

python
class ForwardKinematics:
    def __init__(self, robot_model):
        self.robot = robot_model
        self.dh_params = robot_model.dh_parameters

    def compute(self, joint_angles):
        """计算正运动学"""
        T = np.eye(4)  # 齐次变换矩阵

        for i, (a, alpha, d, theta) in enumerate(self.dh_params):
            # DH 参数变换
            theta_i = joint_angles[i] + theta

            # 单关节变换矩阵
            Ti = np.array([
                [np.cos(theta_i), -np.sin(theta_i)*np.cos(alpha),
                 np.sin(theta_i)*np.sin(alpha), a*np.cos(theta_i)],
                [np.sin(theta_i), np.cos(theta_i)*np.cos(alpha),
                 -np.cos(theta_i)*np.sin(alpha), a*np.sin(theta_i)],
                [0, np.sin(alpha), np.cos(alpha), d],
                [0, 0, 0, 1]
            ])

            T = T @ Ti

        return T

    def get_end_effector_pose(self, joint_angles):
        """获取末端执行器位姿"""
        T = self.compute(joint_angles)

        position = T[:3, 3]
        rotation = Rotation.from_matrix(T[:3, :3])

        return {
            'position': position,
            'orientation': rotation.as_euler('xyz')
        }

2. 逆运动学

python
class InverseKinematics:
    def __init__(self, robot_model):
        self.robot = robot_model
        self.fk = ForwardKinematics(robot_model)

    def solve_numerical(self, target_pose, initial_guess, max_iter=100, tol=1e-6):
        """数值法求解逆运动学"""
        q = initial_guess.copy()

        for _ in range(max_iter):
            # 当前位姿
            current_pose = self.fk.get_end_effector_pose(q)

            # 位姿误差
            pos_error = target_pose['position'] - current_pose['position']
            orient_error = self.compute_orientation_error(
                target_pose['orientation'],
                current_pose['orientation']
            )

            error = np.concatenate([pos_error, orient_error])

            # 检查收敛
            if np.linalg.norm(error) < tol:
                return q, True

            # 雅可比矩阵
            J = self.compute_jacobian(q)

            # 伪逆法更新
            dq = np.linalg.pinv(J) @ error
            q = q + dq

        return q, False

    def solve_analytical(self, target_pose):
        """解析法求解逆运动学(特定构型)"""
        # 以 6-DOF 机器人为例
        p = target_pose['position']
        R = Rotation.from_euler('xyz', target_pose['orientation']).as_matrix()

        # 计算腕部位置
        d6 = self.robot.d6
        pw = p - d6 * R[:, 2]

        # 求解前三个关节(位置)
        q1 = np.arctan2(pw[1], pw[0])

        # 使用几何法求解 q2, q3
        r = np.sqrt(pw[0]**2 + pw[1]**2)
        s = pw[2] - self.robot.d1
        D = (r**2 + s**2 - self.robot.a2**2 - self.robot.a3**2) / (2 * self.robot.a2 * self.robot.a3)

        q3 = np.arctan2(np.sqrt(1 - D**2), D)
        q2 = np.arctan2(s, r) - np.arctan2(
            self.robot.a3 * np.sin(q3),
            self.robot.a2 + self.robot.a3 * np.cos(q3)
        )

        # 求解后三个关节(姿态)
        R03 = self.fk.compute_rotation([q1, q2, q3, 0, 0, 0])
        R36 = R03.T @ R

        q4 = np.arctan2(R36[1, 2], R36[0, 2])
        q5 = np.arccos(R36[2, 2])
        q6 = np.arctan2(R36[2, 1], R36[2, 0])

        return np.array([q1, q2, q3, q4, q5, q6])

    def compute_jacobian(self, q):
        """计算雅可比矩阵"""
        n = len(q)
        J = np.zeros((6, n))

        T = np.eye(4)
        z = np.array([0, 0, 1])
        p_n = self.fk.compute(q)[:3, 3]

        for i in range(n):
            # 关节轴
            z_i = T[:3, 2]
            p_i = T[:3, 3]

            if self.robot.joint_types[i] == 'revolute':
                # 旋转关节
                J[:3, i] = np.cross(z_i, p_n - p_i)
                J[3:, i] = z_i
            else:
                # 移动关节
                J[:3, i] = z_i
                J[3:, i] = np.zeros(3)

            # 更新变换矩阵
            Ti = self.fk.compute_single_joint(q[i], i)
            T = T @ Ti

        return J

动力学控制

1. 动力学模型

python
class DynamicsModel:
    def __init__(self, robot_model):
        self.robot = robot_model
        self.num_joints = robot_model.num_joints

    def compute_dynamics(self, q, qd, qdd):
        """计算动力学:M(q)qdd + C(q,qd)qd + G(q) = tau"""
        # 惯性矩阵 M(q)
        M = self.compute_inertia_matrix(q)

        # 科氏力和离心力 C(q,qd)qd
        C = self.compute_coriolis(q, qd)

        # 重力项 G(q)
        G = self.compute_gravity(q)

        # 计算力矩
        tau = M @ qdd + C @ qd + G

        return tau

    def compute_inertia_matrix(self, q):
        """计算惯性矩阵"""
        M = np.zeros((self.num_joints, self.num_joints))

        for i in range(self.num_joints):
            for j in range(self.num_joints):
                # 使用牛顿-欧拉递推
                M[i, j] = self.inertia_element(q, i, j)

        return M

    def compute_coriolis(self, q, qd):
        """计算科氏力和离心力"""
        C = np.zeros(self.num_joints)

        for i in range(self.num_joints):
            for j in range(self.num_joints):
                for k in range(self.num_joints):
                    # Christoffel 符号
                    c_ijk = self.christoffel(q, i, j, k)
                    C[i] += c_ijk * qd[j] * qd[k]

        return C

    def compute_gravity(self, q):
        """计算重力项"""
        G = np.zeros(self.num_joints)

        for i in range(self.num_joints):
            G[i] = self.gravity_term(q, i)

        return G

2. 计算力矩控制

python
class ComputedTorqueController:
    def __init__(self, robot_model, dynamics_model):
        self.robot = robot_model
        self.dynamics = dynamics_model

        # PID 增益
        self.Kp = np.diag([100, 100, 100, 50, 50, 50])
        self.Kd = np.diag([20, 20, 20, 10, 10, 10])
        self.Ki = np.diag([1, 1, 1, 0.5, 0.5, 0.5])

        self.integral_error = np.zeros(6)

    def compute(self, q_desired, qd_desired, qdd_desired,
                q_actual, qd_actual, dt):
        """计算控制力矩"""
        # 跟踪误差
        e = q_desired - q_actual
        ed = qd_desired - qd_actual

        # 积分项
        self.integral_error += e * dt

        # 期望加速度(PD + 前馈)
        qdd_command = (
            qdd_desired +
            self.Kd @ ed +
            self.Kp @ e +
            self.Ki @ self.integral_error
        )

        # 计算动力学补偿
        M = self.dynamics.compute_inertia_matrix(q_actual)
        C = self.dynamics.compute_coriolis(q_actual, qd_actual)
        G = self.dynamics.compute_gravity(q_actual)

        # 计算力矩
        tau = M @ qdd_command + C @ qd_actual + G

        return tau

强化学习控制

1. 策略网络

python
class RLController:
    def __init__(self, state_dim, action_dim):
        self.policy = PolicyNetwork(state_dim, action_dim)
        self.value = ValueNetwork(state_dim)
        self.optimizer = torch.optim.Adam(self.policy.parameters())

    def select_action(self, state):
        """选择动作"""
        state_tensor = torch.FloatTensor(state)

        # 策略前向传播
        action_mean, action_std = self.policy(state_tensor)

        # 采样动作
        dist = torch.distributions.Normal(action_mean, action_std)
        action = dist.sample()
        log_prob = dist.log_prob(action)

        return action.detach().numpy(), log_prob

    def update(self, trajectories):
        """更新策略"""
        states, actions, rewards, next_states, dones = trajectories

        # 计算优势函数
        values = self.value(states)
        next_values = self.value(next_states)
        advantages = self.compute_advantages(rewards, values, next_values, dones)

        # 策略梯度
        action_means, action_stds = self.policy(states)
        dist = torch.distributions.Normal(action_means, action_stds)
        log_probs = dist.log_prob(actions)

        # PPO 损失
        ratio = torch.exp(log_probs - self.old_log_probs)
        surr1 = ratio * advantages
        surr2 = torch.clamp(ratio, 1 - 0.2, 1 + 0.2) * advantages
        policy_loss = -torch.min(surr1, surr2).mean()

        # 价值损失
        value_loss = F.mse_loss(values, rewards + 0.99 * next_values * (1 - dones))

        # 更新
        loss = policy_loss + 0.5 * value_loss
        self.optimizer.zero_grad()
        loss.backward()
        self.optimizer.step()

2. 奖励设计

python
class RewardDesigner:
    def __init__(self, task_type):
        self.task_type = task_type

    def compute_reward(self, state, action, next_state, goal):
        """计算奖励"""
        if self.task_type == 'reaching':
            return self.reaching_reward(state, action, next_state, goal)
        elif self.task_type == 'manipulation':
            return self.manipulation_reward(state, action, next_state, goal)
        elif self.task_type == 'locomotion':
            return self.locomotion_reward(state, action, next_state)

    def reaching_reward(self, state, action, next_state, goal):
        """到达任务奖励"""
        # 距离奖励
        current_dist = np.linalg.norm(state['ee_pos'] - goal)
        next_dist = np.linalg.norm(next_state['ee_pos'] - goal)
        distance_reward = current_dist - next_dist

        # 到达奖励
        reach_reward = 10.0 if next_dist < 0.05 else 0.0

        # 动作惩罚(鼓励平滑动作)
        action_penalty = -0.01 * np.linalg.norm(action)

        # 碰撞惩罚
        collision_penalty = -1.0 if next_state['collision'] else 0.0

        total_reward = (
            distance_reward +
            reach_reward +
            action_penalty +
            collision_penalty
        )

        return total_reward

    def manipulation_reward(self, state, action, next_state, goal):
        """操作任务奖励"""
        # 物体位置奖励
        object_dist = np.linalg.norm(
            next_state['object_pos'] - goal['object_pos']
        )
        object_reward = -object_dist

        # 抓取奖励
        grasp_reward = 5.0 if next_state['grasped'] else 0.0

        # 稳定性奖励
        stability_reward = -np.linalg.norm(
            next_state['object_velocity']
        ) if next_state['grasped'] else 0.0

        return object_reward + grasp_reward + stability_reward

路径规划

1. 运动规划

python
class MotionPlanner:
    def __init__(self, robot, environment):
        self.robot = robot
        self.env = environment

    def plan_rrt(self, start, goal, max_iter=1000):
        """RRT 路径规划"""
        tree = RRTTree(start)

        for _ in range(max_iter):
            # 随机采样
            if random.random() < 0.1:
                sample = goal
            else:
                sample = self.random_sample()

            # 最近节点
            nearest = tree.nearest(sample)

            # 扩展
            new_node = self.extend(nearest, sample)

            if new_node and self.is_collision_free(nearest, new_node):
                tree.add_node(new_node, parent=nearest)

                # 检查是否到达目标
                if self.reached_goal(new_node, goal):
                    path = tree.get_path(new_node)
                    return self.smooth_path(path)

        return None

    def plan_prm(self, start, goal, num_samples=1000):
        """PRM 路径规划"""
        roadmap = PRMGraph()

        # 采样阶段
        for _ in range(num_samples):
            sample = self.random_sample()
            if self.is_valid(sample):
                roadmap.add_node(sample)

        # 连接阶段
        for node in roadmap.nodes:
            neighbors = roadmap.find_neighbors(node, radius=1.0)
            for neighbor in neighbors:
                if self.is_collision_free(node, neighbor):
                    roadmap.add_edge(node, neighbor)

        # 搜索阶段
        path = roadmap.search(start, goal)
        return path

    def smooth_path(self, path):
        """路径平滑"""
        smoothed = [path[0]]

        i = 0
        while i < len(path) - 1:
            # 尝试直线连接
            j = len(path) - 1
            while j > i + 1:
                if self.is_collision_free(path[i], path[j]):
                    smoothed.append(path[j])
                    i = j
                    break
                j -= 1
            else:
                smoothed.append(path[i + 1])
                i += 1

        return smoothed

2. 轨迹优化

python
class TrajectoryOptimizer:
    def __init__(self, robot, dynamics):
        self.robot = robot
        self.dynamics = dynamics

    def optimize(self, waypoints, duration, num_samples=100):
        """轨迹优化"""
        # 初始轨迹(线性插值)
        t_samples = np.linspace(0, duration, num_samples)
        trajectory = self.interpolate_waypoints(waypoints, t_samples)

        # 优化目标
        def objective(x):
            # x 包含所有时间点的关节位置
            q = x.reshape(num_samples, self.robot.num_joints)

            # 平滑性代价
            smoothness = self.smoothness_cost(q, t_samples)

            # 动力学可行性代价
            dynamics_cost = self.dynamics_feasibility(q, t_samples)

            # 障碍物避让代价
            obstacle_cost = self.obstacle_cost(q)

            return smoothness + dynamics_cost + obstacle_cost

        # 约束
        constraints = self.build_constraints(waypoints, t_samples)

        # 优化
        result = minimize(
            objective,
            trajectory.flatten(),
            constraints=constraints,
            method='SLSQP'
        )

        return result.x.reshape(num_samples, self.robot.num_joints)

    def smoothness_cost(self, q, t):
        """平滑性代价"""
        # 最小化加加速度(jerk)
        dt = t[1] - t[0]
        qd = np.gradient(q, dt, axis=0)
        qdd = np.gradient(qd, dt, axis=0)
        jerk = np.gradient(qdd, dt, axis=0)

        return np.sum(jerk**2) * dt

    def dynamics_feasibility(self, q, t):
        """动力学可行性代价"""
        dt = t[1] - t[0]
        qd = np.gradient(q, dt, axis=0)
        qdd = np.gradient(qd, dt, axis=0)

        cost = 0
        for i in range(len(t)):
            tau = self.dynamics.compute_dynamics(q[i], qd[i], qdd[i])
            # 力矩约束
            tau_max = self.robot.max_torques
            cost += np.sum(np.maximum(0, np.abs(tau) - tau_max)**2)

        return cost

步态规划(双足机器人)

1. ZMP 稳定性

python
class ZMPPlanner:
    def __init__(self, robot):
        self.robot = robot
        self.gravity = 9.81

    def compute_zmp(self, q, qd, qdd):
        """计算零力矩点(ZMP)"""
        # 使用逆动力学计算地面反力
        total_force = np.zeros(3)
        total_torque = np.zeros(3)
        com_position = np.zeros(3)

        for i in range(self.robot.num_links):
            # 连杆质心位置
            link_com = self.robot.get_link_com(i, q)

            # 连杆加速度
            link_acc = self.robot.get_link_acceleration(i, q, qd, qdd)

            # 连杆质量
            mass = self.robot.link_masses[i]

            # 惯性力
            force = mass * (link_acc + np.array([0, 0, self.gravity]))

            # 力矩
            torque = np.cross(link_com, force)

            total_force += force
            total_torque += torque
            com_position += mass * link_com

        # 质心位置
        com_position /= self.robot.total_mass

        # ZMP 计算
        zmp_x = -total_torque[1] / total_force[2]
        zmp_y = total_torque[0] / total_force[2]

        return np.array([zmp_x, zmp_y, 0])

    def is_stable(self, zmp, support_polygon):
        """检查 ZMP 是否在支撑多边形内"""
        return self.point_in_polygon(zmp[:2], support_polygon)

2. 步态生成

python
class GaitGenerator:
    def __init__(self, robot):
        self.robot = robot
        self.zmp_planner = ZMPPlanner(robot)

    def generate_walk_gait(self, step_length, step_duration):
        """生成行走步态"""
        # 步态参数
        double_support_duration = step_duration * 0.2
        single_support_duration = step_duration * 0.8

        # 生成参考轨迹
        trajectories = {
            'left_foot': [],
            'right_foot': [],
            'com': [],
            'zmp': []
        }

        t = 0
        while t < 2 * step_duration:
            # 支撑腿判定
            if t % (2 * step_duration) < step_duration:
                support_leg = 'left'
                swing_leg = 'right'
            else:
                support_leg = 'right'
                swing_leg = 'left'

            # 摆动腿轨迹
            swing_trajectory = self.swing_foot_trajectory(
                swing_leg, step_length, t, step_duration
            )

            # 质心轨迹
            com_trajectory = self.com_trajectory(
                t, step_duration, double_support_duration
            )

            # ZMP 轨迹
            zmp_trajectory = self.zmp_trajectory(
                support_leg, t, step_duration, double_support_duration
            )

            trajectories[swing_leg].append(swing_trajectory)
            trajectories['com'].append(com_trajectory)
            trajectories['zmp'].append(zmp_trajectory)

            t += 0.01  # 100Hz

        return trajectories

    def swing_foot_trajectory(self, leg, step_length, t, duration):
        """摆动腿轨迹(贝塞尔曲线)"""
        # 起点和终点
        if leg == 'left':
            start = np.array([0, 0.1, 0])
            end = np.array([step_length, 0.1, 0])
        else:
            start = np.array([0, -0.1, 0])
            end = np.array([step_length, -0.1, 0])

        # 控制点(抬腿高度)
        height = 0.05
        mid = (start + end) / 2 + np.array([0, 0, height])

        # 贝塞尔插值
        s = t / duration
        position = (1-s)**2 * start + 2*(1-s)*s * mid + s**2 * end

        return position

全身控制

1. 全身控制器

python
class WholeBodyController:
    def __init__(self, robot):
        self.robot = robot
        self.num_joints = robot.num_joints
        self.num_contacts = robot.num_contacts

    def compute(self, desired_acceleration, contact_forces):
        """全身控制"""
        # 构建优化问题
        # 最小化: ||M*qdd + h - J^T*f - tau||^2
        # 约束: 接触约束、摩擦锥、力矩限制

        M = self.robot.get_mass_matrix()
        h = self.robot.get_bias_forces()
        J_c = self.robot.get_contact_jacobian()

        # 决策变量: [qdd, f, tau]
        n_vars = self.num_joints + 3 * self.num_contacts + self.num_joints

        # 目标函数
        H = np.zeros((n_vars, n_vars))
        g = np.zeros(n_vars)

        # 动力学一致性
        H[:self.num_joints, :self.num_joints] = M.T @ M
        g[:self.num_joints] = M.T @ h

        # 二次成本
        H += np.eye(n_vars) * 1e-6  # 正则化

        # 约束
        A_eq, b_eq = self.equality_constraints(desired_acceleration)
        A_ineq, b_ineq = self.inequality_constraints()

        # 求解 QP
        solution = self.solve_qp(H, g, A_eq, b_eq, A_ineq, b_ineq)

        # 提取关节力矩
        tau = solution[2*self.num_joints + 3*self.num_contacts:]

        return tau

    def equality_constraints(self, desired_qdd):
        """等式约束(动力学一致性)"""
        M = self.robot.get_mass_matrix()
        h = self.robot.get_bias_forces()
        J_c = self.robot.get_contact_jacobian()

        # M*qdd + h = J_c^T*f + tau
        A = np.zeros((self.num_joints, self.n_vars))
        A[:, :self.num_joints] = M
        A[:, 2*self.num_joints:2*self.num_joints+3*self.num_contacts] = -J_c.T
        A[:, 2*self.num_joints+3*self.num_contacts:] = -np.eye(self.num_joints)

        b = -h

        return A, b

    def inequality_constraints(self):
        """不等式约束(摩擦锥、力矩限制)"""
        # 摩擦锥约束
        # |f_tangent| <= mu * f_normal

        # 力矩约束
        # tau_min <= tau <= tau_max

        # 构建约束矩阵...

        return A_ineq, b_ineq

总结

具身智能运动控制的关键技术:

  1. 运动学:正/逆运动学求解
  2. 动力学:力矩计算和控制
  3. 强化学习:从试错中学习控制策略
  4. 路径规划:避障和轨迹优化
  5. 步态规划:双足/多足运动
  6. 全身控制:多任务协调

随着计算能力的提升和算法的进步,机器人的运动能力将越来越接近人类水平。


延伸阅读