12人工智能:一种现代方法(第4版)

机器人学

智能机器人

机器人感知运动规划控制

🎯 AI 时代的学习建议:不要只读理论。理解这一章的设计动机(为什么需要这个概念?)和边界条件(什么情况下会失效?)。当 AI 生成的代码涉及这些概念时,你能判断它是否用对了场景。AI 能帮你总结内容,但无法替代你亲手阅读、思考、建立直觉的过程。

第12章:机器人学(Robotics)

一、导读

1.1 本章学习目标

第12章介绍机器人学——AI的综合应用领域。机器人集成了感知、决策、控制等多个AI技术,是AI在物理世界中的具体体现。从工业机械臂到自动驾驶汽车,从手术机器人到人形机器人,机器人技术正在改变我们的生活。

本章的核心学习目标包括:

  • 理解机器人的基本架构:感知-决策-控制的完整链条。
  • 掌握机器人感知:传感器类型、SLAM、目标检测。
  • 理解运动规划:路径规划、避障、运动学约束。
  • 掌握控制理论:PID 控制、强化学习控制。
  • 了解机器人操作系统(ROS):机器人软件开发的标准框架。
  • 了解现代机器人技术:人形机器人、协作机器人、自动驾驶。

1.2 为什么第十二章如此重要

机器人学是AI技术的综合应用。一个完整的机器人系统需要计算机视觉(感知)、搜索和规划(决策)、控制理论(执行)等多个AI技术的协同工作。理解机器人学,有助于将前面章节的知识整合起来,解决实际问题。

随着硬件技术的进步和AI算法的突破,机器人正在从工厂走向家庭、从结构化环境走向非结构化环境。掌握机器人学的基本原理,对于理解未来AI的发展方向至关重要。

1.3 核心问题引导

在阅读本章时,请带着以下问题思考:

感知-决策-控制的循环:机器人如何在动态环境中实时做出决策?

SLAM 的挑战:为什么同时建图和定位如此困难?

运动规划的复杂性:高维空间中的路径规划有什么挑战?

控制的精确性:如何让机器人在不确定环境中精确执行任务?

人机协作:如何设计安全的人机协作系统?


二、核心概念详解

2.1 机器人感知

2.1.1 传感器类型

python
class Sensor:
    def __init__(self, name, noise_level=0.0):
        self.name = name
        self.noise_level = noise_level

    def read(self, true_value):
        noise = np.random.normal(0, self.noise_level)
        return true_value + noise

class LaserScanner(Sensor):
    def __init__(self, max_range=10.0, angular_resolution=1.0):
        super().__init__("LaserScanner", noise_level=0.01)
        self.max_range = max_range
        self.angular_resolution = angular_resolution

    def scan(self, obstacles):
        readings = []
        for angle in np.arange(0, 360, self.angular_resolution):
            distance = self._ray_cast(angle, obstacles)
            readings.append((angle, min(distance, self.max_range)))
        return readings

    def _ray_cast(self, angle, obstacles):
        return np.random.uniform(0.5, self.max_range)

class IMU(Sensor):
    def __init__(self):
        super().__init__("IMU", noise_level=0.001)

    def read_acceleration(self, true_accel):
        return self.read(true_accel)

    def read_angular_velocity(self, true_omega):
        return self.read(true_omega)

# 测试传感器
laser = LaserScanner()
readings = laser.scan([])
print(f"激光扫描点数: {len(readings)}")

2.1.2 SLAM(同时建图和定位)

python
class SimpleSLAM:
    def __init__(self, grid_size=100, resolution=0.1):
        self.grid_size = grid_size
        self.resolution = resolution
        self.map = np.zeros((grid_size, grid_size))
        self.robot_pos = np.array([grid_size // 2, grid_size // 2])
        self.robot_angle = 0.0
        self.uncertainty = np.eye(3)

    def predict(self, motion):
        dx, dy, dtheta = motion
        self.robot_pos[0] += dx * np.cos(self.robot_angle) - dy * np.sin(self.robot_angle)
        self.robot_pos[1] += dx * np.sin(self.robot_angle) + dy * np.cos(self.robot_angle)
        self.robot_angle += dtheta
        self.robot_pos = np.clip(self.robot_pos, 0, self.grid_size - 1)

    def update(self, observations):
        for obs in observations:
            angle, distance = obs
            obs_x = self.robot_pos[0] + distance * np.cos(self.robot_angle + np.radians(angle))
            obs_y = self.robot_pos[1] + distance * np.sin(self.robot_angle + np.radians(angle))
            obs_x = int(np.clip(obs_x, 0, self.grid_size - 1))
            obs_y = int(np.clip(obs_y, 0, self.grid_size - 1))
            self.map[obs_y, obs_x] = 1.0

    def get_map(self):
        return self.map.copy()

# 测试 SLAM
slam = SimpleSLAM()
slam.predict((1, 0, 0))
slam.update([(0, 5), (90, 3), (180, 4)])
print(f"地图非零元素: {np.sum(slam.get_map() > 0)}")

2.2 运动规划

2.2.1 路径规划

python
class GridMap:
    def __init__(self, width, height):
        self.width = width
        self.height = height
        self.grid = np.zeros((height, width), dtype=bool)

    def add_obstacle(self, x, y):
        if 0 <= x < self.width and 0 <= y < self.height:
            self.grid[y, x] = True

    def is_free(self, x, y):
        return 0 <= x < self.width and 0 <= y < self.height and not self.grid[y, x]

class AStarPlanner:
    def __init__(self, grid_map):
        self.grid_map = grid_map

    def heuristic(self, a, b):
        return abs(a[0] - b[0]) + abs(a[1] - b[1])

    def plan(self, start, goal):
        import heapq
        open_set = [(0, start)]
        came_from = {}
        g_score = {start: 0}

        while open_set:
            _, current = heapq.heappop(open_set)

            if current == goal:
                path = []
                while current in came_from:
                    path.append(current)
                    current = came_from[current]
                path.append(start)
                return list(reversed(path))

            for dx, dy in [(0,1),(0,-1),(1,0),(-1,0)]:
                neighbor = (current[0] + dx, current[1] + dy)
                if not self.grid_map.is_free(*neighbor):
                    continue
                tentative_g = g_score[current] + 1
                if tentative_g < g_score.get(neighbor, float('inf')):
                    came_from[neighbor] = current
                    g_score[neighbor] = tentative_g
                    f_score = tentative_g + self.heuristic(neighbor, goal)
                    heapq.heappush(open_set, (f_score, neighbor))

        return None

# 测试路径规划
grid = GridMap(20, 20)
for x in range(5, 15):
    grid.add_obstacle(x, 10)
planner = AStarPlanner(grid)
path = planner.plan((0, 0), (19, 19))
print(f"路径长度: {len(path) if path else '无路径'}")

2.2.2 避障

python
class PotentialFieldPlanner:
    def __init__(self, goal, obstacles, k_att=1.0, k_rep=100.0, rho_0=5.0):
        self.goal = np.array(goal)
        self.obstacles = [np.array(o) for o in obstacles]
        self.k_att = k_att
        self.k_rep = k_rep
        self.rho_0 = rho_0

    def attractive_force(self, pos):
        pos = np.array(pos)
        return -self.k_att * (pos - self.goal)

    def repulsive_force(self, pos):
        pos = np.array(pos)
        force = np.zeros(2)
        for obs in self.obstacles:
            rho = np.linalg.norm(pos - obs)
            if rho < self.rho_0:
                magnitude = self.k_rep * (1/rho - 1/self.rho_0) / (rho**2)
                direction = (pos - obs) / rho
                force += magnitude * direction
        return force

    def total_force(self, pos):
        return self.attractive_force(pos) + self.repulsive_force(pos)

    def plan(self, start, max_steps=1000, step_size=0.1):
        pos = np.array(start, dtype=float)
        path = [pos.copy()]

        for _ in range(max_steps):
            if np.linalg.norm(pos - self.goal) < 0.5:
                break
            force = self.total_force(pos)
            direction = force / (np.linalg.norm(force) + 1e-6)
            pos += step_size * direction
            path.append(pos.copy())

        return path

# 测试势场规划
planner = PotentialFieldPlanner(
    goal=[10, 10],
    obstacles=[[5, 5], [5, 6], [6, 5]]
)
path = planner.plan([0, 0])
print(f"势场规划路径点数: {len(path)}")

2.3 控制理论

2.3.1 PID 控制

python
class PIDController:
    def __init__(self, kp, ki, kd):
        self.kp = kp
        self.ki = ki
        self.kd = kd
        self.integral = 0.0
        self.prev_error = 0.0

    def compute(self, setpoint, measurement, dt):
        error = setpoint - measurement
        self.integral += error * dt
        derivative = (error - self.prev_error) / dt if dt > 0 else 0.0
        output = self.kp * error + self.ki * self.integral + self.kd * derivative
        self.prev_error = error
        return output

    def reset(self):
        self.integral = 0.0
        self.prev_error = 0.0

# 测试 PID 控制
pid = PIDController(kp=1.0, ki=0.1, kd=0.05)
setpoint = 10.0
measurement = 0.0
dt = 0.1
history = []

for step in range(100):
    control = pid.compute(setpoint, measurement, dt)
    measurement += control * dt
    history.append(measurement)

print(f"最终值: {history[-1]:.2f}, 目标: {setpoint}")

2.3.2 强化学习控制

python
class RLController:
    def __init__(self, state_size, action_size):
        self.q_table = {}
        self.state_size = state_size
        self.action_size = action_size
        self.lr = 0.1
        self.gamma = 0.9
        self.epsilon = 0.1

    def discretize_state(self, state):
        return tuple(int(s * 10) for s in state)

    def choose_action(self, state):
        state = self.discretize_state(state)
        if state not in self.q_table or np.random.random() < self.epsilon:
            return np.random.randint(self.action_size)
        return np.argmax(self.q_table[state])

    def update(self, state, action, reward, next_state):
        state = self.discretize_state(state)
        next_state = self.discretize_state(next_state)
        if state not in self.q_table:
            self.q_table[state] = [0.0] * self.action_size
        if next_state not in self.q_table:
            self.q_table[next_state] = [0.0] * self.action_size
        max_next = max(self.q_table[next_state])
        self.q_table[state][action] += self.lr * (reward + self.gamma * max_next - self.q_table[state][action])

# 简单的平衡控制
controller = RLController(state_size=2, action_size=3)
state = [0.5, 0.0]
for step in range(100):
    action = controller.choose_action(state)
    reward = -abs(state[0])
    next_state = [state[0] + (action - 1) * 0.1, state[1]]
    controller.update(state, action, reward, next_state)
    state = next_state
print(f"RL 控制训练完成")

2.4 机器人操作系统

python
class SimpleROS:
    def __init__(self):
        self.topics = {}
        self.subscribers = {}

    def publish(self, topic, message):
        if topic not in self.topics:
            self.topics[topic] = []
        self.topics[topic].append(message)
        if topic in self.subscribers:
            for callback in self.subscribers[topic]:
                callback(message)

    def subscribe(self, topic, callback):
        if topic not in self.subscribers:
            self.subscribers[topic] = []
        self.subscribers[topic].append(callback)

    def get_latest(self, topic):
        if topic in self.topics and self.topics[topic]:
            return self.topics[topic][-1]
        return None

# 测试 ROS
ros = SimpleROS()
received = []
ros.subscribe('sensor_data', lambda msg: received.append(msg))
ros.publish('sensor_data', {'distance': 1.5})
ros.publish('sensor_data', {'distance': 2.0})
print(f"接收到的消息数: {len(received)}")

三、实战案例

3.1 案例一:简单的移动机器人仿真

python
class MobileRobot:
    def __init__(self, x=0, y=0, theta=0):
        self.x = x
        self.y = y
        self.theta = theta
        self.path = [(x, y)]

    def move(self, v, omega, dt):
        self.x += v * np.cos(self.theta) * dt
        self.y += v * np.sin(self.theta) * dt
        self.theta += omega * dt
        self.path.append((self.x, self.y))

    def go_to(self, target_x, target_y, speed=1.0, dt=0.1):
        while True:
            dx = target_x - self.x
            dy = target_y - self.y
            distance = np.sqrt(dx**2 + dy**2)
            if distance < 0.1:
                break
            target_theta = np.arctan2(dy, dx)
            omega = (target_theta - self.theta)
            self.move(speed, omega, dt)
        return self.path

# 测试机器人
robot = MobileRobot()
path = robot.go_to(5, 5)
print(f"路径点数: {len(path)}")
print(f"最终位置: ({robot.x:.2f}, {robot.y:.2f})")

四、本章小结

本章介绍了机器人学的核心概念和方法。机器人通过传感器感知环境,通过规划做出决策,通过控制器执行动作。SLAM 让机器人同时建图和定位,运动规划让机器人找到无碰撞路径,控制理论让机器人精确执行任务。

核心知识点回顾:

  • 机器人的感知-决策-控制架构
  • SLAM 同时解决建图和定位问题
  • 路径规划在配置空间中搜索无碰撞路径
  • PID 控制是最经典的反馈控制方法
  • 强化学习可以学习复杂的控制策略

五、思考与练习

思考题

感知不确定性:传感器噪声如何影响机器人的决策?如何处理?

实时性挑战:机器人在动态环境中需要实时决策,这对算法有什么要求?

编程练习

实现 A* 路径规划:在网格地图上实现 A* 算法。

实现 PID 控制:用 PID 控制让机器人跟踪目标轨迹。

实现简单的 SLAM:在已知地图中实现机器人定位。

12/13机器人学
0%