AI机器人控制与感知完全教程

教程简介

本教程全面讲解AI机器人控制与感知的核心技术,涵盖机器人视觉/触觉/力觉感知、SLAM自主导航、机械臂运动学与控制、抓取规划与力控、强化学习机器人控制、仿真环境(Isaac Sim/Gazebo/MuJoCo)、人机协作与安全等核心内容,帮助开发者构建智能机器人系统。

AI机器人控制与感知完全教程

1. AI机器人技术概述与架构

AI机器人是人工智能与物理世界交互的核心载体,融合了感知、决策、控制等多个技术栈。现代机器人系统通常采用分层架构:

应用层    → 任务规划、人机交互、场景理解
决策层    → 行为决策、任务调度、异常处理
感知层    → 视觉感知、触觉感知、力觉感知、SLAM
控制层    → 运动控制、力控制、轨迹规划
执行层    → 电机驱动、传感器采集、通信总线

常见的机器人类型包括:

  • 工业机械臂:固定在工作台上,执行焊接、装配、搬运等任务
  • 移动机器人(AGV/AMR):在仓库、工厂中自主移动
  • 人形机器人:双足行走,具备类人操作能力
  • 协作机器人(Cobot):设计为与人类安全共处
  • 无人机(UAV):空中作业,用于巡检、物流等

开发框架方面,ROS2(Robot Operating System 2)是事实上的标准中间件:

# ROS2节点示例(Python)
import rclpy
from rclpy.node import Node
from sensor_msgs.msg import Image, JointState
from geometry_msgs.msg import Twist

class RobotController(Node):
    def __init__(self):
        super().__init__('robot_controller')
        
        # 订阅关节状态
        self.joint_sub = self.create_subscription(
            JointState, '/joint_states', self.joint_callback, 10
        )
        
        # 发布速度指令
        self.cmd_pub = self.create_publisher(Twist, '/cmd_vel', 10)
        
        # 定时器控制循环
        self.timer = self.create_timer(0.01, self.control_loop)  # 100Hz
        
        self.current_joint_state = None
        self.get_logger().info('机器人控制器已启动')
    
    def joint_callback(self, msg):
        self.current_joint_state = msg
    
    def control_loop(self):
        if self.current_joint_state is None:
            return
        
        cmd = Twist()
        cmd.linear.x = 0.5   # 前进速度 m/s
        cmd.angular.z = 0.0  # 角速度 rad/s
        self.cmd_pub.publish(cmd)

def main():
    rclpy.init()
    controller = RobotController()
    rclpy.spin(controller)
    controller.destroy_node()
    rclpy.shutdown()

if __name__ == '__main__':
    main()

2. 机器人感知系统(视觉/触觉/力觉)

2.1 视觉感知

机器人视觉是最主要的感知通道,核心技术包括物体检测、姿态估计、深度感知等。

import numpy as np

class HandEyeCalibration:
    """手眼标定:求解相机与机器人末端执行器之间的变换关系"""
    
    def __init__(self):
        self.calibration_data = []
    
    def add_measurement(self, T_gripper_base, T_target_camera):
        """
        T_gripper_base: 夹爪在基座坐标系下的4x4变换矩阵
        T_target_camera: 标定板在相机坐标系下的4x4变换矩阵
        """
        self.calibration_data.append((T_gripper_base, T_target_camera))
    
    def solve_ax_xb(self):
        """
        求解 AX = XB 问题(手眼标定核心方程)
        X: 相机在夹爪坐标系下的变换(eye-in-hand配置)
        """
        n = len(self.calibration_data)
        if n < 2:
            raise ValueError("至少需要2组标定数据")
        
        # 收集旋转和平移方程
        A_rots, B_rots = [], []
        A_trans, B_trans = [], []
        
        for i in range(n - 1):
            A = np.linalg.inv(self.calibration_data[i+1][0]) @ self.calibration_data[i][0]
            B = self.calibration_data[i+1][1] @ np.linalg.inv(self.calibration_data[i][1])
            
            A_rots.append(A[:3, :3])
            B_rots.append(B[:3, :3])
            A_trans.append(A[:3, 3])
            B_trans.append(B[:3, 3])
        
        # 使用Tsai-Lenz方法求解旋转
        R_X = self._solve_rotation(A_rots, B_rots)
        
        # 求解平移
        t_X = self._solve_translation(A_rots, A_trans, B_trans, R_X)
        
        T_camera_gripper = np.eye(4)
        T_camera_gripper[:3, :3] = R_X
        T_camera_gripper[:3, 3] = t_X
        
        return T_camera_gripper
    
    def _solve_rotation(self, A_rots, B_rots):
        """使用四元数方法求解旋转"""
        from scipy.spatial.transform import Rotation
        
        # 构建 Kronecker 积方程组
        M = np.zeros((3 * len(A_rots), 4))
        for i, (Ra, Rb) in enumerate(zip(A_rots, B_rots)):
            qa = Rotation.from_matrix(Ra).as_quat()  # [x,y,z,w]
            qb = Rotation.from_matrix(Rb).as_quat()
            # 构建四元数乘法矩阵
            M[3*i:3*i+3] = self._quat_multiply_matrix(qa)[:, :3] - \
                           self._quat_multiply_matrix(qb)[:, :3]
        
        # SVD求解
        _, _, Vt = np.linalg.svd(M)
        q_X = Vt[-1]
        q_X = q_X / np.linalg.norm(q_X)
        
        return Rotation.from_quat(np.append(q_X, 0)).as_matrix()[:3, :3]
    
    def _quat_multiply_matrix(self, q):
        """四元数左乘矩阵"""
        x, y, z, w = q
        return np.array([
            [w, -z, y, x],
            [z, w, -x, y],
            [-y, x, w, z],
            [-x, -y, -z, w]
        ])
    
    def _solve_translation(self, A_rots, A_trans, B_trans, R_X):
        """求解平移向量"""
        C = []
        d = []
        for i in range(len(A_rots)):
            C.append(A_rots[i] - np.eye(3))
            d.append(R_X @ B_trans[i] - A_trans[i])
        C = np.vstack(C)
        d = np.concatenate(d)
        t_X, _, _, _ = np.linalg.lstsq(C, d, rcond=None)
        return t_X

2.2 触觉感知与力觉感知

触觉传感器使机器人能够感知接触力、物体纹理和形状。力觉传感器则用于精确的力控制。

import numpy as np
from collections import deque

class ForceTorqueSensor:
    """六维力/力矩传感器"""
    
    def __init__(self, bias=None, noise_std=0.1):
        self.bias = bias if bias is not None else np.zeros(6)
        self.noise_std = noise_std
        self.filter_window = 10
        self.readings = deque(maxlen=self.filter_window)
    
    def read_raw(self, true_force):
        """读取原始力数据(含噪声和偏置)"""
        noise = np.random.randn(6) * self.noise_std
        return true_force + self.bias + noise
    
    def read_filtered(self, true_force):
        """中值滤波后的力数据"""
        raw = self.read_raw(true_force)
        self.readings.append(raw)
        if len(self.readings) < 3:
            return raw
        return np.median(list(self.readings), axis=0)
    
    def calibrate(self, zero_force_readings):
        """零点标定"""
        self.bias = np.mean(zero_force_readings, axis=0)
    
    def detect_contact(self, force, threshold=2.0):
        """接触检测"""
        force_magnitude = np.linalg.norm(force[:3])
        return force_magnitude > threshold

class TactileProcessor:
    """触觉数据处理"""
    
    def __init__(self, sensor_size=(16, 16)):
        self.sensor_size = sensor_size
        self.baseline = None
    
    def set_baseline(self, tactile_image):
        """设置基线(无接触状态)"""
        self.baseline = tactile_image.copy()
    
    def compute_contact_map(self, tactile_image):
        """计算接触图"""
        if self.baseline is None:
            self.baseline = np.zeros(self.sensor_size)
        
        diff = tactile_image - self.baseline
        contact_map = np.maximum(diff, 0)
        return contact_map
    
    def estimate_contact_force(self, contact_map, calibration_factor=0.1):
        """估计接触力"""
        total_force = np.sum(contact_map) * calibration_factor
        return total_force
    
    def estimate_contact_center(self, contact_map):
        """估计接触中心位置"""
        if np.sum(contact_map) < 1e-6:
            return None
        
        rows, cols = np.meshgrid(
            np.arange(self.sensor_size[0]),
            np.arange(self.sensor_size[1]),
            indexing='ij'
        )
        center_r = np.sum(rows * contact_map) / np.sum(contact_map)
        center_c = np.sum(cols * contact_map) / np.sum(contact_map)
        return (center_r, center_c)
    
    def estimate_slip(self, prev_contact, curr_contact, prev_center, curr_center):
        """滑移检测"""
        if prev_center is None or curr_center is None:
            return False, 0.0
        
        displacement = np.sqrt(
            (curr_center[0] - prev_center[0])**2 +
            (curr_center[1] - prev_center[1])**2
        )
        
        # 接触面积变化
        prev_area = np.sum(prev_contact > 0.1)
        curr_area = np.sum(curr_contact > 0.1)
        area_change = abs(curr_area - prev_area) / max(prev_area, 1)
        
        is_slipping = displacement > 1.0 or area_change > 0.3
        return is_slipping, displacement

3. SLAM与自主导航

SLAM(Simultaneous Localization and Mapping)使机器人能够在未知环境中同时定位自身和构建地图。

3.1 基于图优化的SLAM

import numpy as np
from scipy.optimize import least_squares

class PoseGraphSLAM:
    """基于位姿图的SLAM"""
    
    def __init__(self):
        self.poses = []            # 位姿节点 [(x, y, theta), ...]
        self.odometry_edges = []   # 里程计边 [(i, j, measurement, information)]
        self.loop_edges = []       # 回环检测边
    
    def add_pose(self, pose):
        self.poses.append(np.array(pose))
    
    def add_odometry(self, from_idx, to_idx, measurement, information_matrix):
        """
        添加里程计约束
        measurement: (dx, dy, dtheta) 相对位移
        information_matrix: 3x3信息矩阵(协方差的逆)
        """
        self.odometry_edges.append((from_idx, to_idx, 
                                     np.array(measurement), 
                                     np.array(information_matrix)))
    
    def add_loop_closure(self, from_idx, to_idx, measurement, information_matrix):
        """添加回环约束"""
        self.loop_edges.append((from_idx, to_idx,
                                 np.array(measurement),
                                 np.array(information_matrix)))
    
    def optimize(self, max_iterations=100):
        """使用Levenberg-Marquardt优化位姿图"""
        n_poses = len(self.poses)
        if n_poses < 2:
            return
        
        # 将所有位姿展平为一维向量
        x0 = np.concatenate(self.poses)
        
        def residuals(x):
            poses = x.reshape(-1, 3)
            all_residuals = []
            
            # 里程计残差
            for (i, j, meas, info) in self.odometry_edges:
                pred = self._compute_relative_pose(poses[i], poses[j])
                error = pred - meas
                # 信息矩阵加权
                L = np.linalg.cholesky(info)
                weighted_error = L @ error
                all_residuals.extend(weighted_error)
            
            # 回环残差
            for (i, j, meas, info) in self.loop_edges:
                pred = self._compute_relative_pose(poses[i], poses[j])
                error = pred - meas
                L = np.linalg.cholesky(info)
                weighted_error = L @ error
                all_residuals.extend(weighted_error)
            
            return np.array(all_residuals)
        
        # 优化
        result = least_squares(residuals, x0, method='lm', max_nfev=max_iterations)
        
        # 更新位姿
        optimized_poses = result.x.reshape(-1, 3)
        self.poses = [p for p in optimized_poses]
        
        return result.cost
    
    def _compute_relative_pose(self, pose1, pose2):
        """计算两个位姿之间的相对变换"""
        dx = pose2[0] - pose1[0]
        dy = pose2[1] - pose1[1]
        dtheta = pose2[2] - pose1[2]
        
        # 转换到pose1的局部坐标系
        c, s = np.cos(pose1[2]), np.sin(pose1[2])
        local_dx = c * dx + s * dy
        local_dy = -s * dx + c * dy
        
        return np.array([local_dx, local_dy, dtheta])

class SimpleOccupancyGrid:
    """占用栅格地图"""
    
    def __init__(self, width=100, height=100, resolution=0.1):
        self.width = width
        self.height = height
        self.resolution = resolution  # 每格代表的米数
        self.grid = np.full((height, width), 0.5)  # 0.5表示未知
        self.log_odds = np.zeros((height, width))
    
    def update(self, robot_pose, scan_points, max_range=10.0):
        """用激光扫描更新地图"""
        rx, ry, rtheta = robot_pose
        
        # 机器人位置对应的栅格坐标
        gx = int(rx / self.resolution) + self.width // 2
        gy = int(ry / self.resolution) + self.height // 2
        
        for point in scan_points:
            # 计算终点栅格坐标
            angle = rtheta + point[1]
            dist = min(point[0], max_range)
            ex = int((rx + dist * np.cos(angle)) / self.resolution) + self.width // 2
            ey = int((ry + dist * np.sin(angle)) / self.resolution) + self.height // 2
            
            # Bresenham光线追踪
            cells = self._bresenham(gx, gy, ex, ey)
            
            for i, (cx, cy) in enumerate(cells):
                if not (0 <= cx < self.width and 0 <= cy < self.height):
                    continue
                
                if i == len(cells) - 1 and dist < max_range:
                    # 终点:占用
                    self.log_odds[cy, cx] += 0.85
                else:
                    # 光线路径:空闲
                    self.log_odds[cy, cx] -= 0.4
        
        # 转换为概率
        self.grid = 1.0 / (1.0 + np.exp(-self.log_odds))
    
    def _bresenham(self, x0, y0, x1, y1):
        """Bresenham直线算法"""
        cells = []
        dx = abs(x1 - x0)
        dy = abs(y1 - y0)
        sx = 1 if x1 > x0 else -1
        sy = 1 if y1 > y0 else -1
        err = dx - dy
        
        while True:
            cells.append((x0, y0))
            if x0 == x1 and y0 == y1:
                break
            e2 = 2 * err
            if e2 > -dy:
                err -= dy
                x0 += sx
            if e2 < dx:
                err += dx
                y0 += sy
        
        return cells
    
    def is_occupied(self, x, y):
        """检查某点是否被占用"""
        gx = int(x / self.resolution) + self.width // 2
        gy = int(y / self.resolution) + self.height // 2
        if 0 <= gx < self.width and 0 <= gy < self.height:
            return self.grid[gy, gx] > 0.65
        return True  # 未知区域视为占用

3.2 自主导航

import numpy as np
import heapq

class DWAPlanner:
    """DWA(Dynamic Window Approach)局部规划器"""
    
    def __init__(self, max_speed=1.0, max_yaw_rate=1.0, 
                 max_accel=0.5, max_delta_yaw_rate=1.0,
                 v_resolution=0.05, yaw_resolution=0.05,
                 dt=0.1, predict_time=2.0):
        self.max_speed = max_speed
        self.max_yaw_rate = max_yaw_rate
        self.max_accel = max_accel
        self.max_delta_yaw_rate = max_delta_yaw_rate
        self.v_resolution = v_resolution
        self.yaw_resolution = yaw_resolution
        self.dt = dt
        self.predict_time = predict_time
        
        # 评价函数权重
        self.alpha = 0.15   # 航向评价权重
        self.beta = 0.2     # 障碍物距离评价权重
        self.gamma = 0.1    # 速度评价权重
    
    def plan(self, robot_state, goal, obstacles):
        """
        robot_state: (x, y, theta, v, yaw_rate)
        goal: (gx, gy)
        obstacles: [(ox, oy), ...]
        """
        x, y, theta, v, yaw_rate = robot_state
        
        # 动态窗口
        vs = self._compute_dynamic_window(v, yaw_rate)
        
        best_score = -float('inf')
        best_control = (0, 0)
        best_trajectory = None
        
        # 搜索最优控制量
        for v_candidate in np.arange(vs[0], vs[1], self.v_resolution):
            for yaw_candidate in np.arange(vs[2], vs[3], self.yaw_resolution):
                trajectory = self._predict_trajectory(
                    robot_state, v_candidate, yaw_candidate
                )
                
                # 计算评价分数
                heading_score = self._heading_score(trajectory, goal)
                clearance_score = self._clearance_score(trajectory, obstacles)
                velocity_score = v_candidate / self.max_speed
                
                total_score = (self.alpha * heading_score + 
                             self.beta * clearance_score + 
                             self.gamma * velocity_score)
                
                if total_score > best_score:
                    best_score = total_score
                    best_control = (v_candidate, yaw_candidate)
                    best_trajectory = trajectory
        
        return best_control, best_trajectory
    
    def _compute_dynamic_window(self, current_v, current_yaw_rate):
        """计算动态窗口"""
        v_min = max(0, current_v - self.max_accel * self.dt)
        v_max = min(self.max_speed, current_v + self.max_accel * self.dt)
        yaw_min = max(-self.max_yaw_rate, 
                      current_yaw_rate - self.max_delta_yaw_rate * self.dt)
        yaw_max = min(self.max_yaw_rate,
                      current_yaw_rate + self.max_delta_yaw_rate * self.dt)
        return (v_min, v_max, yaw_min, yaw_max)
    
    def _predict_trajectory(self, state, v, yaw_rate):
        """预测轨迹"""
        trajectory = [state[:3]]
        x, y, theta = state[0], state[1], state[2]
        
        steps = int(self.predict_time / self.dt)
        for _ in range(steps):
            theta += yaw_rate * self.dt
            x += v * np.cos(theta) * self.dt
            y += v * np.sin(theta) * self.dt
            trajectory.append((x, y, theta))
        
        return trajectory
    
    def _heading_score(self, trajectory, goal):
        """航向评价:末端朝向与目标方向的一致性"""
        end_x, end_y, end_theta = trajectory[-1]
        dx = goal[0] - end_x
        dy = goal[1] - end_y
        goal_angle = np.arctan2(dy, dx)
        angle_diff = abs(end_theta - goal_angle)
        angle_diff = min(angle_diff, 2 * np.pi - angle_diff)
        return np.pi - angle_diff
    
    def _clearance_score(self, trajectory, obstacles):
        """障碍物距离评价"""
        min_dist = float('inf')
        for (x, y, _) in trajectory:
            for (ox, oy) in obstacles:
                dist = np.sqrt((x - ox)**2 + (y - oy)**2)
                min_dist = min(min_dist, dist)
        return min_dist

4. 机械臂运动学与控制

4.1 正运动学(D-H参数法)

import numpy as np

class DHRobot:
    """基于D-H参数的机械臂运动学"""
    
    def __init__(self, dh_params):
        """
        dh_params: [(a, alpha, d, theta_offset), ...]
        a: 连杆长度
        alpha: 连杆扭角
        d: 连杆偏距
        theta_offset: 关节角偏移
        """
        self.dh_params = dh_params
        self.n_joints = len(dh_params)
    
    def forward_kinematics(self, joint_angles):
        """正运动学:从关节角计算末端位姿"""
        T = np.eye(4)
        transforms = [T.copy()]
        
        for i, (a, alpha, d, offset) in enumerate(self.dh_params):
            theta = joint_angles[i] + offset
            
            # 标准D-H变换矩阵
            ct, st = np.cos(theta), np.sin(theta)
            ca, sa = np.cos(alpha), np.sin(alpha)
            
            Ti = np.array([
                [ct, -st*ca,  st*sa, a*ct],
                [st,  ct*ca, -ct*sa, a*st],
                [0,   sa,     ca,    d   ],
                [0,   0,      0,     1   ]
            ])
            
            T = T @ Ti
            transforms.append(T.copy())
        
        return T, transforms
    
    def jacobian(self, joint_angles):
        """计算几何雅可比矩阵"""
        T, transforms = self.forward_kinematics(joint_angles)
        p_e = T[:3, 3]  # 末端位置
        
        J = np.zeros((6, self.n_joints))
        
        for i in range(self.n_joints):
            z_i = transforms[i][:3, 2]   # 关节轴方向
            p_i = transforms[i][:3, 3]   # 关节位置
            
            # 旋转关节
            J[:3, i] = np.cross(z_i, p_e - p_i)  # 线速度
            J[3:, i] = z_i                          # 角速度
        
        return J
    
    def inverse_kinematics_numerical(self, target_pose, initial_angles=None, 
                                      max_iter=1000, tol=1e-4):
        """数值法逆运动学"""
        if initial_angles is None:
            q = np.zeros(self.n_joints)
        else:
            q = np.array(initial_angles, dtype=float)
        
        for iteration in range(max_iter):
            T, _ = self.forward_kinematics(q)
            
            # 位姿误差
            pos_error = target_pose[:3, 3] - T[:3, 3]
            
            # 旋转误差(使用对数映射)
            R_error = target_pose[:3, :3] @ T[:3, :3].T
            # 罗德里格斯公式提取旋转轴角
            angle = np.arccos(np.clip((np.trace(R_error) - 1) / 2, -1, 1))
            if abs(angle) < 1e-10:
                rot_error = np.zeros(3)
            else:
                axis = np.array([
                    R_error[2, 1] - R_error[1, 2],
                    R_error[0, 2] - R_error[2, 0],
                    R_error[1, 0] - R_error[0, 1]
                ]) / (2 * np.sin(angle))
                rot_error = axis * angle
            
            error = np.concatenate([pos_error, rot_error])
            
            if np.linalg.norm(error) < tol:
                print(f"IK收敛于第{iteration+1}次迭代")
                return q
            
            # 阻尼最小二乘法(DLS)
            J = self.jacobian(q)
            damping = 0.01
            delta_q = J.T @ np.linalg.solve(
                J @ J.T + damping**2 * np.eye(6), error
            )
            
            q += delta_q
        
        print(f"IK未收敛,最终误差: {np.linalg.norm(error):.6f}")
        return q

# 创建6轴机械臂(类UR5参数)
dh_params = [
    (0,      -np.pi/2, 0.0892, 0),   # 关节1
    (-0.425, 0,        0,      0),    # 关节2
    (-0.392, 0,        0,      0),    # 关节3
    (0,      -np.pi/2, 0.1093, 0),   # 关节4
    (0,      np.pi/2,  0,      0),    # 关节5
    (0,      0,        0.0823, 0),    # 关节6
]

robot = DHRobot(dh_params)

# 正运动学示例
joint_angles = [0, -np.pi/4, np.pi/2, 0, np.pi/4, 0]
T, _ = robot.forward_kinematics(joint_angles)
print(f"末端位置: {T[:3, 3]}")
print(f"末端旋转矩阵:\n{T[:3, :3]}")

4.2 轨迹规划

import numpy as np

class TrajectoryPlanner:
    """关节空间轨迹规划"""
    
    @staticmethod
    def cubic_polynomial(q0, qf, v0, vf, T, dt=0.01):
        """三次多项式插值"""
        # 系数求解
        a0 = q0
        a1 = v0
        a2 = (3*(qf - q0) - (2*v0 + vf)*T) / T**2
        a3 = (2*(q0 - qf) + (v0 + vf)*T) / T**3
        
        t = np.arange(0, T + dt, dt)
        q = a0 + a1*t + a2*t**2 + a3*t**3
        v = a1 + 2*a2*t + 3*a3*t**2
        a = 2*a2 + 6*a3*t
        
        return t, q, v, a
    
    @staticmethod
    def quintic_polynomial(q0, qf, v0, vf, a0, af, T, dt=0.01):
        """五次多项式插值(保证加速度连续)"""
        h = qf - q0
        
        # 五次多项式系数
        a_0 = q0
        a_1 = v0
        a_2 = a0 / 2
        a_3 = (20*h - (8*vf + 12*v0)*T - (3*a0 - af)*T**2) / (2*T**3)
        a_4 = (-30*h + (14*vf + 16*v0)*T + (3*a0 - 2*af)*T**2) / (2*T**4)
        a_5 = (12*h - 6*(vf + v0)*T + (af - a0)*T**2) / (2*T**5)
        
        t = np.arange(0, T + dt, dt)
        q = a_0 + a_1*t + a_2*t**2 + a_3*t**3 + a_4*t**4 + a_5*t**5
        v = a_1 + 2*a_2*t + 3*a_3*t**2 + 4*a_4*t**3 + 5*a_5*t**4
        a = 2*a_2 + 6*a_3*t + 12*a_4*t**2 + 20*a_5*t**3
        
        return t, q, v, a
    
    @staticmethod
    def trapezoidal_velocity_profile(q0, qf, v_max, a_max, dt=0.01):
        """梯形速度规划"""
        direction = 1 if qf > q0 else -1
        dist = abs(qf - q0)
        
        # 判断是否能达到最大速度
        t_accel = v_max / a_max
        d_accel = 0.5 * a_max * t_accel**2
        
        if 2 * d_accel > dist:
            # 无法达到最大速度,三角形速度规划
            t_accel = np.sqrt(dist / a_max)
            t_total = 2 * t_accel
            t_const = 0
            v_peak = a_max * t_accel
        else:
            d_const = dist - 2 * d_accel
            t_const = d_const / v_max
            t_total = 2 * t_accel + t_const
            v_peak = v_max
        
        t = np.arange(0, t_total + dt, dt)
        q = np.zeros_like(t)
        v = np.zeros_like(t)
        
        for i, ti in enumerate(t):
            if ti <= t_accel:
                v[i] = a_max * ti
                q[i] = 0.5 * a_max * ti**2
            elif ti <= t_accel + t_const:
                v[i] = v_peak
                q[i] = d_accel + v_peak * (ti - t_accel)
            else:
                t_decel = ti - t_accel - t_const
                v[i] = v_peak - a_max * t_decel
                q[i] = dist - 0.5 * a_max * (t_total - ti)**2
        
        q = q0 + direction * q
        v = direction * v
        
        return t, q, v

class CartesianTrajectoryPlanner:
    """笛卡尔空间轨迹规划"""
    
    @staticmethod
    def linear_interpolation(T_start, T_end, n_points=100):
        """直线插值"""
        trajectories = []
        
        for i in range(n_points + 1):
            s = i / n_points
            
            # 位置线性插值
            pos = (1 - s) * T_start[:3, 3] + s * T_end[:3, 3]
            
            # 旋转球面线性插值(SLERP)
            R = TrajectoryPlanner._slerp(T_start[:3, :3], T_end[:3, :3], s)
            
            T = np.eye(4)
            T[:3, :3] = R
            T[:3, 3] = pos
            trajectories.append(T)
        
        return trajectories
    
    @staticmethod
    def _slerp(R1, R2, t):
        """旋转矩阵的球面线性插值"""
        from scipy.spatial.transform import Rotation, Slerp
        
        rots = Rotation.from_matrix([R1, R2])
        slerp = Slerp([0, 1], rots)
        interp_rot = slerp(t)
        return interp_rot.as_matrix()

5. 抓取规划与力控

5.1 抓取规划

import numpy as np

class GraspPlanner:
    """抓取规划器"""
    
    def __init__(self, gripper_width=0.08, gripper_force=50.0):
        self.gripper_width = gripper_width  # 最大开口宽度(m)
        self.gripper_force = gripper_force  # 最大抓取力(N)
    
    def generate_grasp_candidates(self, object_pose, object_size, n_candidates=16):
        """
        生成候选抓取姿态
        object_pose: (x, y, z, roll, pitch, yaw)
        object_size: (length, width, height)
        """
        x, y, z = object_pose[:3]
        candidates = []
        
        for i in range(n_candidates):
            angle = 2 * np.pi * i / n_candidates
            
            # 抓取点在物体表面
            grasp_x = x + object_size[0]/2 * np.cos(angle)
            grasp_y = y + object_size[1]/2 * np.sin(angle)
            grasp_z = z + object_size[2] / 2  # 从上方抓取
            
            # 夹爪朝向物体中心
            approach_angle = np.arctan2(y - grasp_y, x - grasp_x)
            
            grasp_pose = {
                'position': np.array([grasp_x, grasp_y, grasp_z]),
                'orientation': self._angle_to_rotation(approach_angle),
                'width': min(self.gripper_width, 
                           min(object_size[0], object_size[1])),
                'score': 0.0
            }
            candidates.append(grasp_pose)
        
        return candidates
    
    def score_grasp(self, grasp_pose, object_pose, obstacles=None):
        """评估抓取质量"""
        score = 0.0
        
        # 1. 抓取方向与物体主轴的对齐度
        obj_center = np.array(object_pose[:3])
        grasp_pos = grasp_pose['position']
        direction = obj_center - grasp_pos
        direction = direction / (np.linalg.norm(direction) + 1e-6)
        
        # 偏好从上方抓取
        vertical_score = abs(direction[2])  # z方向分量
        score += 0.3 * vertical_score
        
        # 2. 抓取稳定性(对称抓取得分更高)
        symmetry_score = 1.0  # 简化
        score += 0.3 * symmetry_score
        
        # 3. 障碍物碰撞惩罚
        if obstacles:
            min_dist = float('inf')
            for obs in obstacles:
                dist = np.linalg.norm(grasp_pos - obs[:3])
                min_dist = min(min_dist, dist)
            collision_score = min(1.0, min_dist / 0.1)
            score += 0.4 * collision_score
        else:
            score += 0.4
        
        grasp_pose['score'] = score
        return score
    
    def select_best_grasp(self, candidates):
        """选择最优抓取"""
        if not candidates:
            return None
        return max(candidates, key=lambda g: g['score'])
    
    def _angle_to_rotation(self, angle):
        """将绕z轴旋转角转换为旋转矩阵"""
        c, s = np.cos(angle), np.sin(angle)
        return np.array([
            [c, -s, 0],
            [s,  c, 0],
            [0,  0, 1]
        ])

class ImpedanceController:
    """阻抗控制器:柔顺力控"""
    
    def __init__(self, mass=1.0, damping=10.0, stiffness=100.0):
        """
        M * (x_ddot - x_ddot_d) + D * (x_dot - x_dot_d) + K * (x - x_d) = F_ext
        """
        self.M = mass
        self.D = damping
        self.K = stiffness
    
    def compute_force(self, current_pos, desired_pos, 
                      current_vel, desired_vel, external_force):
        """
        计算阻抗控制输出力
        """
        pos_error = current_pos - desired_pos
        vel_error = current_vel - desired_vel
        
        # 阻抗控制律
        F_command = external_force - (
            self.K * pos_error + self.D * vel_error
        )
        
        return F_command
    
    def admittance_control(self, measured_force, desired_pos, dt=0.001):
        """
        导纳控制:根据力反馈调整期望位置
        """
        # 计算虚拟加速度
        acceleration = (measured_force - self.K * 0) / self.M
        # 简化:直接计算位置修正
        position_correction = measured_force / self.K
        
        new_desired_pos = desired_pos + position_correction
        return new_desired_pos

class HybridForcePositionController:
    """力/位混合控制器"""
    
    def __init__(self, selection_matrix=None):
        """
        selection_matrix: 对角矩阵,1表示力控方向,0表示位置控方向
        """
        if selection_matrix is None:
            self.S = np.diag([0, 0, 1, 0, 0, 0])  # z方向力控
        else:
            self.S = selection_matrix
        
        self.position_pid = {'kp': 500, 'ki': 10, 'kd': 50}
        self.force_pid = {'kp': 0.01, 'ki': 0.001, 'kd': 0.0005}
    
    def compute(self, desired_wrench, measured_wrench,
                desired_pose, current_pose, dt=0.001):
        """
        计算混合力/位控制输出
        desired_wrench: 期望力/力矩 [Fx, Fy, Fz, Tx, Ty, Tz]
        """
        # 位置控制部分(S的对角线为0的方向)
        pos_error = desired_pose[:3] - current_pose[:3]
        I_minus_S = np.eye(6) - self.S
        
        # 力控制部分(S的对角线为1的方向)
        force_error = desired_wrench - measured_wrench
        
        # 合并输出
        position_contribution = I_minus_S[:3, :3] @ pos_error * self.position_pid['kp']
        force_contribution = self.S[:3, :3] @ force_error[:3] * self.force_pid['kp']
        
        output_wrench = position_contribution + force_contribution
        
        return output_wrench

6. 强化学习机器人控制

6.1 基于PPO的机器人控制

import numpy as np
import torch
import torch.nn as nn
import torch.optim as optim
from torch.distributions import Normal

class PolicyNetwork(nn.Module):
    """策略网络(Actor)"""
    
    def __init__(self, state_dim, action_dim, hidden_dim=256):
        super().__init__()
        self.network = nn.Sequential(
            nn.Linear(state_dim, hidden_dim),
            nn.ReLU(),
            nn.Linear(hidden_dim, hidden_dim),
            nn.ReLU(),
        )
        self.mean_head = nn.Linear(hidden_dim, action_dim)
        self.log_std = nn.Parameter(torch.zeros(action_dim))
    
    def forward(self, state):
        features = self.network(state)
        mean = self.mean_head(features)
        std = torch.exp(self.log_std).expand_as(mean)
        return mean, std
    
    def get_action(self, state):
        mean, std = self.forward(state)
        dist = Normal(mean, std)
        action = dist.sample()
        log_prob = dist.log_prob(action).sum(dim=-1)
        return action, log_prob
    
    def evaluate(self, state, action):
        mean, std = self.forward(state)
        dist = Normal(mean, std)
        log_prob = dist.log_prob(action).sum(dim=-1)
        entropy = dist.entropy().sum(dim=-1)
        return log_prob, entropy

class ValueNetwork(nn.Module):
    """价值网络(Critic)"""
    
    def __init__(self, state_dim, hidden_dim=256):
        super().__init__()
        self.network = nn.Sequential(
            nn.Linear(state_dim, hidden_dim),
            nn.ReLU(),
            nn.Linear(hidden_dim, hidden_dim),
            nn.ReLU(),
            nn.Linear(hidden_dim, 1),
        )
    
    def forward(self, state):
        return self.network(state)

class PPOAgent:
    """PPO强化学习智能体"""
    
    def __init__(self, state_dim, action_dim, lr=3e-4, gamma=0.99, 
                 clip_epsilon=0.2, epochs=10, batch_size=64):
        self.policy = PolicyNetwork(state_dim, action_dim)
        self.value = ValueNetwork(state_dim)
        self.policy_optimizer = optim.Adam(self.policy.parameters(), lr=lr)
        self.value_optimizer = optim.Adam(self.value.parameters(), lr=lr)
        
        self.gamma = gamma
        self.clip_epsilon = clip_epsilon
        self.epochs = epochs
        self.batch_size = batch_size
    
    def select_action(self, state):
        state_tensor = torch.FloatTensor(state).unsqueeze(0)
        with torch.no_grad():
            action, log_prob = self.policy.get_action(state_tensor)
        return action.squeeze(0).numpy(), log_prob.item()
    
    def update(self, states, actions, old_log_probs, returns, advantages):
        states = torch.FloatTensor(states)
        actions = torch.FloatTensor(actions)
        old_log_probs = torch.FloatTensor(old_log_probs)
        returns = torch.FloatTensor(returns)
        advantages = torch.FloatTensor(advantages)
        
        for _ in range(self.epochs):
            # 随机打乱数据
            indices = np.random.permutation(len(states))
            
            for start in range(0, len(states), self.batch_size):
                end = start + self.batch_size
                batch_idx = indices[start:end]
                
                batch_states = states[batch_idx]
                batch_actions = actions[batch_idx]
                batch_old_log_probs = old_log_probs[batch_idx]
                batch_returns = returns[batch_idx]
                batch_advantages = advantages[batch_idx]
                
                # 计算新的log概率
                new_log_probs, entropy = self.policy.evaluate(
                    batch_states, batch_actions
                )
                
                # PPO裁剪目标
                ratio = torch.exp(new_log_probs - batch_old_log_probs)
                surr1 = ratio * batch_advantages
                surr2 = torch.clamp(ratio, 1 - self.clip_epsilon, 
                                   1 + self.clip_epsilon) * batch_advantages
                policy_loss = -torch.min(surr1, surr2).mean() - 0.01 * entropy.mean()
                
                # 价值函数损失
                values = self.value(batch_states).squeeze()
                value_loss = nn.MSELoss()(values, batch_returns)
                
                # 更新
                self.policy_optimizer.zero_grad()
                policy_loss.backward()
                nn.utils.clip_grad_norm_(self.policy.parameters(), 0.5)
                self.policy_optimizer.step()
                
                self.value_optimizer.zero_grad()
                value_loss.backward()
                nn.utils.clip_grad_norm_(self.value.parameters(), 0.5)
                self.value_optimizer.step()
    
    def compute_gae(self, rewards, values, dones, next_value, lam=0.95):
        """广义优势估计(GAE)"""
        advantages = np.zeros_like(rewards)
        last_gae = 0
        
        for t in reversed(range(len(rewards))):
            if t == len(rewards) - 1:
                next_val = next_value
            else:
                next_val = values[t + 1]
            
            delta = rewards[t] + self.gamma * next_val * (1 - dones[t]) - values[t]
            advantages[t] = last_gae = delta + self.gamma * lam * (1 - dones[t]) * last_gae
        
        returns = advantages + values
        return advantages, returns

6.2 机器人强化学习环境封装

import numpy as np

class RobotReachEnv:
    """机器人到达任务环境(简化版)"""
    
    def __init__(self, n_joints=6, target_range=0.5):
        self.n_joints = n_joints
        self.target_range = target_range
        self.max_steps = 200
        self.step_count = 0
        
        # 关节限制
        self.joint_limits = [(-np.pi, np.pi)] * n_joints
        
        # 动作空间:关节角增量
        self.action_dim = n_joints
        # 状态空间:关节角 + 目标位置 + 末端位置
        self.state_dim = n_joints + 6
    
    def reset(self):
        """重置环境"""
        self.step_count = 0
        self.joint_angles = np.zeros(self.n_joints)
        
        # 随机目标位置
        self.target = np.random.uniform(-self.target_range, self.target_range, 3)
        
        return self._get_observation()
    
    def step(self, action):
        """执行一步"""
        self.step_count += 1
        
        # 应用动作(关节角增量)
        action = np.clip(action, -0.1, 0.1)
        self.joint_angles += action
        
        # 关节限位
        for i, (low, high) in enumerate(self.joint_limits):
            self.joint_angles[i] = np.clip(self.joint_angles[i], low, high)
        
        # 计算正运动学(简化)
        end_effector_pos = self._forward_kinematics(self.joint_angles)
        
        # 计算奖励
        distance = np.linalg.norm(end_effector_pos - self.target)
        reward = -distance  # 距离奖励
        
        # 到达奖励
        if distance < 0.05:
            reward += 10.0
        
        # 关节运动惩罚(鼓励平滑运动)
        reward -= 0.01 * np.sum(action**2)
        
        # 终止条件
        done = False
        if distance < 0.05:
            done = True
        if self.step_count >= self.max_steps:
            done = True
        
        obs = self._get_observation()
        info = {'distance': distance, 'end_effector': end_effector_pos}
        
        return obs, reward, done, info
    
    def _get_observation(self):
        end_effector = self._forward_kinematics(self.joint_angles)
        obs = np.concatenate([
            self.joint_angles,
            self.target,
            end_effector
        ])
        return obs.astype(np.float32)
    
    def _forward_kinematics(self, joint_angles):
        """简化正运动学"""
        pos = np.array([0.0, 0.0, 0.5])  # 基座位置
        link_length = 0.3
        
        cumulative_angle = 0
        for i, angle in enumerate(joint_angles):
            cumulative_angle += angle
            pos[0] += link_length * np.cos(cumulative_angle)
            pos[1] += link_length * np.sin(cumulative_angle)
            pos[2] += 0.05 * np.sin(angle)  # 简化的z轴运动
        
        return pos

# 训练循环
def train_reach_task():
    env = RobotReachEnv(n_joints=6)
    agent = PPOAgent(
        state_dim=env.state_dim,
        action_dim=env.action_dim
    )
    
    n_episodes = 1000
    max_steps = 200
    
    for episode in range(n_episodes):
        states, actions, rewards, log_probs, dones = [], [], [], [], []
        
        state = env.reset()
        
        for step in range(max_steps):
            action, log_prob = agent.select_action(state)
            next_state, reward, done, info = env.step(action)
            
            states.append(state)
            actions.append(action)
            rewards.append(reward)
            log_probs.append(log_prob)
            dones.append(done)
            
            state = next_state
            if done:
                break
        
        # 计算GAE
        values = [agent.value(torch.FloatTensor(s)).item() for s in states]
        next_value = agent.value(torch.FloatTensor(next_state)).item() if not done else 0
        advantages, returns = agent.compute_gae(
            np.array(rewards), np.array(values), np.array(dones), next_value
        )
        
        # 更新
        agent.update(states, actions, log_probs, returns, advantages)
        
        if (episode + 1) % 50 == 0:
            avg_reward = np.sum(rewards)
            print(f"Episode {episode+1}, Total Reward: {avg_reward:.2f}, "
                  f"Final Distance: {info['distance']:.4f}")

# train_reach_task()  # 取消注释以运行训练

7. 仿真环境(Isaac Sim/Gazebo/MuJoCo)

7.1 MuJoCo环境使用

import numpy as np

# MuJoCo XML模型定义(简化机械臂)
ARM_XML = """
<mujoco model="robot_arm">
  <compiler angle="radian" />
  <option timestep="0.001" gravity="0 0 -9.81" />
  
  <default>
    <joint damping="0.5" />
    <geom rgba="0.5 0.5 0.5 1" />
  </default>
  
  <asset>
    <texture name="grid" type="2d" builtin="checker" rgb1=".8 .8 .8" rgb2=".6 .6 .6" width="300" height="300"/>
    <material name="grid" texture="grid" texrepeat="5 5" reflectance="0.1"/>
  </asset>
  
  <worldbody>
    <geom name="floor" type="plane" size="1 1 0.01" material="grid"/>
    <light pos="0 0 3" dir="0 0 -1" diffuse="0.8 0.8 0.8"/>
    
    <body name="base" pos="0 0 0.1">
      <geom type="cylinder" size="0.05 0.05" rgba="0.3 0.3 0.8 1"/>
      
      <body name="link1" pos="0 0 0.1">
        <joint name="joint1" type="hinge" axis="0 0 1" range="-3.14 3.14"/>
        <geom type="capsule" fromto="0 0 0 0 0 0.3" size="0.03" rgba="0.8 0.2 0.2 1"/>
        
        <body name="link2" pos="0 0 0.3">
          <joint name="joint2" type="hinge" axis="0 1 0" range="-1.57 1.57"/>
          <geom type="capsule" fromto="0 0 0 0.3 0 0" size="0.03" rgba="0.2 0.8 0.2 1"/>
          
          <body name="link3" pos="0.3 0 0">
            <joint name="joint3" type="hinge" axis="0 1 0" range="-1.57 1.57"/>
            <geom type="capsule" fromto="0 0 0 0.25 0 0" size="0.025" rgba="0.2 0.2 0.8 1"/>
            
            <body name="gripper" pos="0.25 0 0">
              <geom type="box" size="0.03 0.04 0.01" rgba="0.8 0.8 0.2 1"/>
              <site name="grip_site" pos="0 0 0" size="0.01"/>
            </body>
          </body>
        </body>
      </body>
    </body>
    
    <!-- 目标物体 -->
    <body name="target_object" pos="0.5 0.0 0.15">
      <geom type="box" size="0.03 0.03 0.03" rgba="0.9 0.1 0.1 1" mass="0.1"/>
      <freejoint name="object_joint"/>
    </body>
    
    <!-- 目标位置 -->
    <body name="target_site" pos="0.4 0.3 0.2">
      <site name="target" type="sphere" size="0.02" rgba="0 1 0 0.3"/>
    </body>
  </worldbody>
  
  <actuator>
    <motor name="act1" joint="joint1" ctrlrange="-50 50"/>
    <motor name="act2" joint="joint2" ctrlrange="-50 50"/>
    <motor name="act3" joint="joint3" ctrlrange="-50 50"/>
  </actuator>
</mujoco>
"""

class MuJoCoRobotEnv:
    """MuJoCo机器人环境封装"""
    
    def __init__(self):
        try:
            import mujoco
            self.model = mujoco.MjModel.from_xml_string(ARM_XML)
            self.data = mujoco.MjData(self.model)
            self.renderer = None
        except ImportError:
            print("MuJoCo未安装,使用模拟模式")
            self.model = None
            self.data = None
    
    def reset(self):
        if self.data is None:
            return np.zeros(9)
        
        mujoco.mj_resetData(self.model, self.data)
        # 随机化目标位置
        self.data.qpos[-7:-4] = np.random.uniform(-0.3, 0.3, 3)
        mujoco.mj_forward(self.model, self.data)
        return self._get_obs()
    
    def step(self, action):
        if self.data is None:
            return np.zeros(9), 0, False, {}
        
        # 施加控制力矩
        self.data.ctrl[:3] = np.clip(action, -50, 50)
        mujoco.mj_step(self.model, self.data)
        
        obs = self._get_obs()
        reward = self._compute_reward()
        done = self._check_done()
        
        return obs, reward, done, {}
    
    def _get_obs(self):
        if self.data is None:
            return np.zeros(9)
        
        joint_pos = self.data.qpos[:3].copy()
        joint_vel = self.data.qvel[:3].copy()
        target_pos = self.data.qpos[-7:-4].copy()
        
        return np.concatenate([joint_pos, joint_vel, target_pos])
    
    def _compute_reward(self):
        if self.data is None:
            return 0
        
        # 末端执行器位置
        grip_site_id = mujoco.mj_name2id(self.model, mujoco.mjtObj.mjOBJ_SITE, "grip_site")
        grip_pos = self.data.site_xpos[grip_site_id].copy()
        target_pos = self.data.qpos[-7:-4].copy()
        
        distance = np.linalg.norm(grip_pos - target_pos)
        reward = -distance
        
        if distance < 0.05:
            reward += 10.0
        
        return reward
    
    def _check_done(self):
        if self.data is None:
            return True
        return self.data.time > 5.0
    
    def render(self):
        if self.data is None:
            return None
        
        if self.renderer is None:
            import mujoco
            self.renderer = mujoco.Renderer(self.model, height=480, width=640)
        
        self.renderer.update_scene(self.data)
        return self.renderer.render()

7.2 Gazebo仿真

# Gazebo ROS2 Launch配置示例
import os
from launch import LaunchDescription
from launch.actions import IncludeLaunchDescription
from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch_ros.actions import Node
from ament_index_python.packages import get_package_share_directory

def generate_launch_description():
    # 启动Gazebo仿真
    gazebo = IncludeLaunchDescription(
        PythonLaunchDescriptionSource([
            os.path.join(get_package_share_directory('gazebo_ros'), 'launch'),
            '/gazebo.launch.py'
        ]),
        launch_arguments={'world': 'robot_arm_world.sdf'}.items()
    )
    
    # 机器人状态发布
    robot_state_publisher = Node(
        package='robot_state_publisher',
        executable='robot_state_publisher',
        parameters=[{
            'robot_description': open('robot.urdf').read(),
            'use_sim_time': True
        }]
    )
    
    # 运动规划节点
    move_group = Node(
        package='moveit_servo',
        executable='servo_node',
        parameters=[{
            'use_gazebo': True,
            'status_topic': '/servo_server/status'
        }]
    )
    
    return LaunchDescription([
        gazebo,
        robot_state_publisher,
        move_group,
    ])

8. 人机协作与安全

协作机器人(Cobot)需要在与人类共享工作空间时保证安全。核心安全技术包括:

8.1 安全监控系统

import numpy as np
import time

class SafetyMonitor:
    """人机协作安全监控系统"""
    
    def __init__(self, robot_workspace_bounds, safety_distances=None):
        """
        robot_workspace_bounds: ((x_min, x_max), (y_min, y_max), (z_min, z_max))
        safety_distances: 安全距离阈值
        """
        self.bounds = robot_workspace_bounds
        self.safety_distances = safety_distances or {
            'emergency_stop': 0.3,    # 紧急停止距离
            'reduced_speed': 0.6,     # 减速距离
            'normal_operation': 1.0,  # 正常操作距离
        }
        
        self.safety_state = 'NORMAL'  # NORMAL, REDUCED, STOPPED
        self.human_positions = []
    
    def update_human_positions(self, positions):
        """更新人类位置(来自视觉检测)"""
        self.human_positions = positions
    
    def evaluate_safety(self, robot_end_effector, robot_joints_vel):
        """评估安全状态"""
        if not self.human_positions:
            self.safety_state = 'NORMAL'
            return self.safety_state
        
        # 计算最近人类距离
        min_distance = float('inf')
        closest_human = None
        for human_pos in self.human_positions:
            dist = np.linalg.norm(robot_end_effector - human_pos)
            if dist < min_distance:
                min_distance = dist
                closest_human = human_pos
        
        # 速度加权距离
        robot_speed = np.linalg.norm(robot_joints_vel)
        effective_distance = min_distance / (1 + robot_speed * 0.5)
        
        # 判断安全等级
        if effective_distance < self.safety_distances['emergency_stop']:
            self.safety_state = 'STOPPED'
        elif effective_distance < self.safety_distances['reduced_speed']:
            self.safety_state = 'REDUCED'
        else:
            self.safety_state = 'NORMAL'
        
        return {
            'state': self.safety_state,
            'min_distance': min_distance,
            'effective_distance': effective_distance,
            'closest_human': closest_human,
            'robot_speed': robot_speed
        }
    
    def get_speed_limit(self):
        """根据安全状态返回速度限制"""
        limits = {
            'NORMAL': 1.0,     # 全速
            'REDUCED': 0.25,   # 25%速度
            'STOPPED': 0.0     # 停止
        }
        return limits[self.safety_state]
    
    def compute_safe_trajectory(self, desired_trajectory, safety_eval):
        """修改轨迹以确保安全"""
        speed_factor = self.get_speed_limit()
        
        if speed_factor == 0:
            return None  # 必须停止
        
        # 缩放速度
        safe_trajectory = desired_trajectory.copy()
        for i in range(len(safe_trajectory)):
            if i > 0:
                delta = safe_trajectory[i] - safe_trajectory[i-1]
                safe_trajectory[i] = safe_trajectory[i-1] + delta * speed_factor
        
        return safe_trajectory

class CollisionAvoidance:
    """碰撞避免模块"""
    
    def __init__(self, min_distance=0.1):
        self.min_distance = min_distance
    
    def check_collision(self, robot_points, obstacle_points):
        """
        robot_points: 机器人各连杆的采样点
        obstacle_points: 障碍物点云
        """
        for r_point in robot_points:
            distances = np.linalg.norm(obstacle_points - r_point, axis=1)
            if np.min(distances) < self.min_distance:
                return True, np.min(distances)
        return False, float('inf')
    
    def compute_repulsive_force(self, robot_point, obstacles, influence_range=0.5):
        """计算人工势场的排斥力"""
        force = np.zeros(3)
        
        for obs in obstacles:
            diff = robot_point - obs
            dist = np.linalg.norm(diff)
            
            if dist < influence_range and dist > 1e-6:
                # 排斥力大小与距离成反比
                magnitude = (1/dist - 1/influence_range) / dist**2
                force += magnitude * (diff / dist)
        
        return force

9. 多机器人协调

9.1 多机器人任务分配

import numpy as np
from scipy.optimize import linear_sum_assignment

class MultiRobotCoordinator:
    """多机器人协调控制器"""
    
    def __init__(self, n_robots):
        self.n_robots = n_robots
        self.robot_states = [{'position': np.zeros(3), 'status': 'idle'} 
                           for _ in range(n_robots)]
        self.task_queue = []
        self.assignments = {}
    
    def add_task(self, task_id, position, priority=1, required_robots=1):
        self.task_queue.append({
            'id': task_id,
            'position': np.array(position),
            'priority': priority,
            'required_robots': required_robots,
            'status': 'pending'
        })
    
    def assign_tasks(self):
        """使用匈牙利算法进行任务分配"""
        pending_tasks = [t for t in self.task_queue if t['status'] == 'pending']
        idle_robots = [i for i, s in enumerate(self.robot_states) 
                      if s['status'] == 'idle']
        
        if not pending_tasks or not idle_robots:
            return
        
        # 构建代价矩阵
        n = max(len(idle_robots), len(pending_tasks))
        cost_matrix = np.full((n, n), 1e6)
        
        for i, robot_idx in enumerate(idle_robots):
            for j, task in enumerate(pending_tasks):
                dist = np.linalg.norm(
                    self.robot_states[robot_idx]['position'] - task['position']
                )
                # 考虑任务优先级
                cost_matrix[i, j] = dist / task['priority']
        
        # 匈牙利算法
        row_indices, col_indices = linear_sum_assignment(cost_matrix)
        
        for r, c in zip(row_indices, col_indices):
            if r < len(idle_robots) and c < len(pending_tasks):
                robot_idx = idle_robots[r]
                task = pending_tasks[c]
                
                self.assignments[task['id']] = robot_idx
                self.robot_states[robot_idx]['status'] = 'busy'
                task['status'] = 'assigned'
    
    def update_robot_state(self, robot_idx, position, status=None):
        self.robot_states[robot_idx]['position'] = np.array(position)
        if status:
            self.robot_states[robot_idx]['status'] = status
    
    def collision_free_path(self, robot_paths):
        """时空协调:避免机器人间碰撞"""
        time_resolution = 0.1
        safe_distance = 0.3
        
        for i in range(len(robot_paths)):
            for j in range(i + 1, len(robot_paths)):
                path_i = robot_paths[i]
                path_j = robot_paths[j]
                
                # 检查时间重叠
                min_len = min(len(path_i), len(path_j))
                for t in range(min_len):
                    dist = np.linalg.norm(
                        np.array(path_i[t]) - np.array(path_j[t])
                    )
                    if dist < safe_distance:
                        # 冲突:让一个机器人等待
                        # 在路径j的t时刻添加等待
                        path_j.insert(t, path_j[t])
                        print(f"机器人{i}和{j}在时刻{t}存在碰撞风险,已协调避让")
        
        return robot_paths

class FormationController:
    """编队控制器"""
    
    def __init__(self, formation_offsets):
        """
        formation_offsets: [(dx, dy, dz), ...] 各机器人相对编队中心的偏移
        """
        self.offsets = [np.array(o) for o in formation_offsets]
        self.kp = 2.0  # 位置增益
    
    def compute_velocities(self, robot_positions, target_center, target_heading=0):
        """
        计算各机器人的编队控制速度
        """
        velocities = []
        
        for i, (pos, offset) in enumerate(zip(robot_positions, self.offsets)):
            # 旋转偏移量到目标朝向
            c, s = np.cos(target_heading), np.sin(target_heading)
            R = np.array([[c, -s, 0], [s, c, 0], [0, 0, 1]])
            rotated_offset = R @ offset
            
            # 期望位置
            desired_pos = target_center + rotated_offset
            
            # 位置误差
            error = desired_pos - pos
            velocity = self.kp * error
            
            # 限速
            speed = np.linalg.norm(velocity)
            max_speed = 1.0
            if speed > max_speed:
                velocity = velocity / speed * max_speed
            
            velocities.append(velocity)
        
        return velocities

10. 实战案例:机械臂智能抓取系统

综合前述所有技术,构建一个完整的机械臂智能抓取系统:

import numpy as np

class IntelligentGraspSystem:
    """智能抓取系统:感知-规划-控制一体化"""
    
    def __init__(self):
        # 感知模块
        self.object_detector = None
        self.pose_estimator = None
        self.force_sensor = ForceTorqueSensor()
        
        # 规划模块
        self.grasp_planner = GraspPlanner(gripper_width=0.08)
        self.trajectory_planner = TrajectoryPlanner()
        
        # 控制模块
        self.arm_controller = None
        self.impedance_ctrl = ImpedanceController(mass=1.0, damping=15.0, stiffness=200.0)
        self.safety_monitor = SafetyMonitor(
            robot_workspace_bounds=((-0.5, 0.5), (-0.5, 0.5), (0, 0.8))
        )
        
        # 状态
        self.current_state = 'IDLE'
        self.object_pose = None
        self.grasp_target = None
    
    def perceive(self, rgb_image, depth_image):
        """感知阶段:检测物体并估计位姿"""
        # 模拟物体检测
        detected_objects = [
            {
                'class': 'cube',
                'bbox': [200, 150, 300, 250],
                'position_3d': np.array([0.35, 0.1, 0.15]),
                'size': (0.05, 0.05, 0.05),
                'confidence': 0.95
            }
        ]
        
        if detected_objects:
            obj = detected_objects[0]
            self.object_pose = {
                'position': obj['position_3d'],
                'orientation': np.eye(3),
                'size': obj['size']
            }
            return True
        return False
    
    def plan_grasp(self):
        """规划阶段:生成并评估抓取姿态"""
        if self.object_pose is None:
            return False
        
        object_pose_tuple = (
            self.object_pose['position'][0],
            self.object_pose['position'][1],
            self.object_pose['position'][2],
            0, 0, 0
        )
        
        # 生成候选抓取
        candidates = self.grasp_planner.generate_grasp_candidates(
            object_pose_tuple, self.object_pose['size']
        )
        
        # 评估每个候选
        for candidate in candidates:
            self.grasp_planner.score_grasp(candidate, object_pose_tuple)
        
        # 选择最优抓取
        best_grasp = self.grasp_planner.select_best_grasp(candidates)
        
        if best_grasp:
            self.grasp_target = best_grasp
            return True
        return False
    
    def plan_trajectory(self, robot_state):
        """轨迹规划"""
        # 预抓取位置(物体上方)
        pre_grasp_pos = self.grasp_target['position'].copy()
        pre_grasp_pos[2] += 0.15  # 上方15cm
        
        # 规划到预抓取位置的轨迹
        t, q, v, a = self.trajectory_planner.quintic_polynomial(
            q0=robot_state['joint_angles'][0],
            qf=pre_grasp_pos[0],
            v0=0, vf=0, a0=0, af=0,
            T=2.0
        )
        
        return {'approach': (t, q, v, a), 'target': pre_grasp_pos}
    
    def execute_grasp(self, robot_interface):
        """执行抓取(力控)"""
        target_force = -5.0  # 向下的抓取力
        max_force = 15.0
        
        # 力控下降阶段
        for step in range(200):
            # 读取力传感器
            force = self.force_sensor.read_filtered(np.array([0, 0, target_force, 0, 0, 0]))
            
            # 检测接触
            if self.force_sensor.detect_contact(force, threshold=3.0):
                print(f"检测到接触,力: {np.linalg.norm(force[:3]):.2f}N")
                break
            
            # 阻抗控制
            current_pos = robot_interface.get_end_effector_position()
            desired_pos = self.grasp_target['position']
            correction = self.impedance_ctrl.admittance_control(
                force[:3], desired_pos
            )
            robot_interface.move_to(correction)
        
        # 闭合夹爪
        robot_interface.close_gripper(force=20.0)
        
        # 检测是否成功抓取
        grasp_force = self.force_sensor.read_filtered(np.zeros(6))
        if np.linalg.norm(grasp_force[:3]) > 1.0:
            print("抓取成功")
            return True
        else:
            print("抓取失败")
            return False
    
    def run(self, rgb_image=None, depth_image=None, robot_interface=None):
        """主循环"""
        print(f"当前状态: {self.current_state}")
        
        if self.current_state == 'IDLE':
            if self.perceive(rgb_image, depth_image):
                self.current_state = 'PLANNING'
        
        elif self.current_state == 'PLANNING':
            if self.plan_grasp():
                trajectory = self.plan_trajectory({'joint_angles': [0]*6})
                self.current_state = 'EXECUTING'
        
        elif self.current_state == 'EXECUTING':
            # 安全检查
            safety = self.safety_monitor.evaluate_safety(
                robot_end_effector=np.zeros(3),
                robot_joints_vel=np.zeros(6)
            )
            
            if safety['state'] == 'STOPPED':
                print("安全事件:紧急停止")
                robot_interface.emergency_stop()
                return
            
            success = self.execute_grasp(robot_interface)
            if success:
                self.current_state = 'COMPLETE'
            else:
                self.current_state = 'IDLE'  # 重试

# 使用示例
system = IntelligentGraspSystem()
system.perceive(rgb_image=None, depth_image=None)
system.plan_grasp()
print(f"目标抓取位置: {system.grasp_target['position']}")
print(f"抓取得分: {system.grasp_target['score']:.3f}")

11. 工业应用场景

11.1 智能质检系统

import numpy as np

class QualityInspectionSystem:
    """基于AI的工业质检系统"""
    
    def __init__(self):
        self.defect_types = ['scratch', 'dent', 'discoloration', 'crack', 'missing_part']
        self.thresholds = {
            'scratch': {'min_area': 5, 'max_depth': 0.1},
            'dent': {'min_area': 10, 'min_depth': 0.2},
            'discoloration': {'min_area': 20, 'color_diff': 15},
            'crack': {'min_length': 3, 'max_width': 0.5},
        }
    
    def inspect_surface(self, surface_image, point_cloud=None):
        """表面检测"""
        defects = []
        
        # 图像预处理
        processed = self._preprocess(surface_image)
        
        # 缺陷检测(模拟)
        potential_defects = self._detect_anomalies(processed)
        
        for defect in potential_defects:
            # 缺陷分类
            defect_type = self._classify_defect(defect, surface_image)
            
            # 严重程度评估
            severity = self._evaluate_severity(defect, defect_type)
            
            defects.append({
                'type': defect_type,
                'location': defect['position'],
                'size': defect['size'],
                'severity': severity,
                'pass': severity < 0.7
            })
        
        # 综合判定
        overall_pass = all(d['pass'] for d in defects)
        
        return {
            'defects': defects,
            'overall_pass': overall_pass,
            'num_defects': len(defects),
            'critical_defects': sum(1 for d in defects if d['severity'] > 0.8)
        }
    
    def _preprocess(self, image):
        """图像预处理"""
        return image  # 简化
    
    def _detect_anomalies(self, image):
        """异常检测"""
        return []  # 简化
    
    def _classify_defect(self, defect, image):
        """缺陷分类"""
        return 'scratch'  # 简化
    
    def _evaluate_severity(self, defect, defect_type):
        """评估缺陷严重程度"""
        return 0.5  # 简化

class PickAndPlaceSystem:
    """工业拾放系统"""
    
    def __init__(self, robot_arm, conveyor_speed=0.1):
        self.robot = robot_arm
        self.conveyor_speed = conveyor_speed
        self.pick_offset = np.array([0, 0, 0.02])  # 抓取偏移
    
    def track_object_on_conveyor(self, object_pose, camera_trigger_time):
        """跟踪传送带上的物体"""
        current_time = time.time()
        elapsed = current_time - camera_trigger_time
        
        # 补偿传送带运动
        compensated_pose = object_pose.copy()
        compensated_pose[0] += self.conveyor_speed * elapsed
        
        return compensated_pose
    
    def compute_intercept_point(self, object_pose, object_velocity, robot_reach_time):
        """计算拦截点"""
        intercept_point = object_pose + object_velocity * robot_reach_time
        return intercept_point
    
    def execute_dynamic_grasp(self, object_pose, place_pose):
        """动态抓取与放置"""
        # 1. 预测物体到达抓取点的时间
        object_velocity = np.array([self.conveyor_speed, 0, 0])
        
        # 2. 规划拦截轨迹
        robot_pose = self.robot.get_current_pose()[:3]
        intercept_point = self.compute_intercept_point(
            object_pose, object_velocity, robot_reach_time=0.5
        )
        
        # 3. 执行抓取
        pre_grasp = intercept_point + self.pick_offset
        
        # 使用时间最优轨迹
        trajectory = self._time_optimal_trajectory(
            robot_pose, pre_grasp, max_vel=2.0, max_accel=5.0
        )
        
        self.robot.execute_trajectory(trajectory)
        
        # 4. 接近并抓取
        self.robot.move_to(intercept_point, speed=0.3)
        self.robot.close_gripper(force=30)
        
        # 5. 抬起并放置
        lift_pose = place_pose + np.array([0, 0, 0.1])
        self.robot.move_to(lift_pose, speed=1.0)
        self.robot.move_to(place_pose, speed=0.5)
        self.robot.open_gripper()
        
        return True
    
    def _time_optimal_trajectory(self, start, end, max_vel, max_accel):
        """时间最优轨迹规划"""
        distance = np.linalg.norm(end - start)
        
        # 计算各段时间
        t_accel = max_vel / max_accel
        d_accel = 0.5 * max_accel * t_accel**2
        
        if 2 * d_accel > distance:
            t_accel = np.sqrt(distance / max_accel)
            t_total = 2 * t_accel
        else:
            d_const = distance - 2 * d_accel
            t_const = d_const / max_vel
            t_total = 2 * t_accel + t_const
        
        # 生成轨迹点
        dt = 0.01
        t_points = np.arange(0, t_total, dt)
        trajectory = []
        
        direction = (end - start) / distance
        
        for t in t_points:
            if t <= t_accel:
                s = 0.5 * max_accel * t**2
            elif t <= t_total - t_accel:
                s = d_accel + max_vel * (t - t_accel)
            else:
                t_decel = t_total - t
                s = distance - 0.5 * max_accel * t_decel**2
            
            pos = start + direction * s
            trajectory.append(pos)
        
        return trajectory

11.2 协作装配系统

import numpy as np

class CollaborativeAssemblySystem:
    """人机协作装配系统"""
    
    def __init__(self):
        self.safety_monitor = SafetyMonitor(
            robot_workspace_bounds=((-0.8, 0.8), (-0.8, 0.8), (0, 1.0))
        )
        self.assembly_steps = []
        self.current_step = 0
    
    def define_assembly_task(self, steps):
        """
        定义装配任务
        steps: [{'action': 'insert', 'part': 'shaft', 'target': pos, ...}, ...]
        """
        self.assembly_steps = steps
    
    def human_intent_recognition(self, human_hand_pose, human_gaze_target):
        """识别工人意图"""
        # 基于手势和注视方向推断意图
        intent = {
            'action': 'handover',   # 递送、指向、确认
            'target_object': None,
            'urgency': 'normal',
            'confidence': 0.8
        }
        
        # 简化的意图识别逻辑
        if human_gaze_target is not None:
            intent['target_object'] = human_gaze_target
        
        return intent
    
    def adaptive_speed_control(self, human_proximity, task_complexity):
        """根据人类接近程度和任务复杂度自适应调整速度"""
        base_speed = 1.0
        
        # 距离因子
        if human_proximity < 0.5:
            distance_factor = human_proximity / 0.5
        else:
            distance_factor = 1.0
        
        # 复杂度因子
        complexity_factor = 1.0 / (1 + task_complexity * 0.5)
        
        return base_speed * distance_factor * complexity_factor
    
    def execute_collaborative_step(self, step, human_state):
        """执行协作装配步骤"""
        action = step['action']
        
        if action == 'handover':
            # 递送零件给人
            return self._handover_to_human(step, human_state)
        elif action == 'insert':
            # 精密插入(力控)
            return self._precision_insert(step)
        elif action == 'hold':
            # 固定部件供人操作
            return self._hold_for_human(step, human_state)
        elif action == 'wait_human':
            # 等待人完成操作
            return self._wait_for_human(step, human_state)
        
        return False
    
    def _handover_to_human(self, step, human_state):
        """递送零件给人"""
        # 计算安全递送点(人手前方)
        handover_point = human_state['hand_position'] + np.array([0, 0, 0.1])
        
        # 以安全速度移动到递送点
        speed = self.adaptive_speed_control(
            human_state['proximity'], task_complexity=0.3
        )
        
        print(f"移动到递送点,速度: {speed:.2f}m/s")
        # robot.move_to(handover_point, speed=speed)
        
        # 等待人接住
        # while not detect_hand_contact():
        #     time.sleep(0.01)
        
        return True
    
    def _precision_insert(self, step):
        """力控精密插入"""
        target_pos = step['target']
        insertion_force_limit = 10.0  # N
        
        # 使用螺旋搜索策略
        search_radius = 0.002  # 2mm搜索半径
        spiral_points = self._generate_spiral(search_radius, n_points=20)
        
        for point in spiral_points:
            attempt_pos = target_pos + np.array([point[0], point[1], 0])
            
            # 力控下降
            for z in np.linspace(0, -step.get('depth', 0.02), 20):
                pos = attempt_pos + np.array([0, 0, z])
                # force = read_force_sensor()
                force = np.zeros(6)
                
                # 检测插入成功(力突变)
                if abs(force[2]) > insertion_force_limit:
                    print("插入完成")
                    return True
        
        print("插入失败")
        return False
    
    def _hold_for_human(self, step, human_state):
        """固定部件供人操作"""
        hold_force = step.get('hold_force', 20.0)
        timeout = step.get('timeout', 30.0)
        
        # 施加恒定力保持位置
        print(f"保持位置,力: {hold_force}N,超时: {timeout}s")
        # robot.apply_force(hold_force)
        
        # 监控人类操作完成
        start_time = time.time()
        while time.time() - start_time < timeout:
            # 检查人类是否完成(通过视觉或按钮信号)
            # if human_done_signal():
            #     return True
            pass
        
        return True
    
    def _wait_for_human(self, step, human_state):
        """等待人完成操作"""
        timeout = step.get('timeout', 60.0)
        expected_action = step.get('expected_action', None)
        
        print(f"等待人类完成操作: {expected_action},超时: {timeout}s")
        
        # 机器人退到安全位置待命
        safe_pos = np.array([0.0, -0.4, 0.5])
        # robot.move_to(safe_pos, speed=0.5)
        
        return True
    
    def _generate_spiral(self, radius, n_points=20):
        """生成螺旋搜索点"""
        points = []
        for i in range(n_points):
            angle = 2 * np.pi * i / n_points
            r = radius * (i / n_points)
            x = r * np.cos(angle)
            y = r * np.sin(angle)
            points.append((x, y))
        return points


# === 应用示例 ===
if __name__ == '__main__':
    # 示例1:正运动学
    dh_params = [
        (0,      -np.pi/2, 0.0892, 0),
        (-0.425, 0,        0,      0),
        (-0.392, 0,        0,      0),
        (0,      -np.pi/2, 0.1093, 0),
        (0,      np.pi/2,  0,      0),
        (0,      0,        0.0823, 0),
    ]
    robot = DHRobot(dh_params)
    joint_angles = [0, -np.pi/4, np.pi/2, 0, np.pi/4, 0]
    T, _ = robot.forward_kinematics(joint_angles)
    print(f"末端位置: {T[:3, 3]}")
    
    # 示例2:轨迹规划
    planner = TrajectoryPlanner()
    t, q, v, a = planner.quintic_polynomial(
        q0=0, qf=1.5, v0=0, vf=0, a0=0, af=0, T=3.0
    )
    print(f"轨迹规划完成,共{len(t)}个点")
    
    # 示例3:安全监控
    safety = SafetyMonitor(
        robot_workspace_bounds=((-0.5, 0.5), (-0.5, 0.5), (0, 0.8))
    )
    safety.update_human_positions([np.array([0.3, 0.2, 0.5])])
    result = safety.evaluate_safety(
        robot_end_effector=np.array([0.25, 0.15, 0.4]),
        robot_joints_vel=np.array([0.1, 0.2, 0.1, 0, 0, 0])
    )
    print(f"安全状态: {result['state']}, 最近距离: {result['min_distance']:.3f}m")
    
    print("\n所有示例运行完成!")

内容声明

本文内容为AI技术学习教程,仅供学习参考。如涉及技术问题,欢迎通过 xurj005@163.com 与我们交流。

目录