第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 传感器类型
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(同时建图和定位)
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 路径规划
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 避障
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 控制
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 强化学习控制
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 机器人操作系统
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 案例一:简单的移动机器人仿真
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:在已知地图中实现机器人定位。