第三章 运动控制
3.1 运动学模型
3.1.1 差速机器人运动学
正运动学(Forward Kinematics):
已知左右轮速度,求机器人速度:
输入: v_left, v_right (左右轮线速度)
参数: L (轮距), R (轮半径)
输出:
线速度: v = (v_left + v_right) / 2
角速度: ω = (v_right - v_left) / L
位姿更新:
x_dot = v * cos(θ)
y_dot = v * sin(θ)
θ_dot = ω
Python实现:
import numpy as np
class DifferentialRobot:
def __init__(self, wheel_base, wheel_radius):
self.L = wheel_base # 轮距
self.R = wheel_radius # 轮半径
self.x = 0.0
self.y = 0.0
self.theta = 0.0
def forward_kinematics(self, v_left, v_right, dt):
"""正运动学"""
# 计算机器人速度
v = (v_left + v_right) / 2.0
omega = (v_right - v_left) / self.L
# 更新位姿
self.x += v * np.cos(self.theta) * dt
self.y += v * np.sin(self.theta) * dt
self.theta += omega * dt
return v, omega
def inverse_kinematics(self, v, omega):
"""逆运动学"""
v_left = v - omega * self.L / 2.0
v_right = v + omega * self.L / 2.0
return v_left, v_right
def get_pose(self):
return self.x, self.y, self.theta
# 使用示例
robot = DifferentialRobot(wheel_base=0.3, wheel_radius=0.05)
# 已知左右轮速,计算机器人速度
v, omega = robot.forward_kinematics(v_left=1.0, v_right=1.2, dt=0.1)
print(f"线速度: {v:.2f} m/s, 角速度: {omega:.2f} rad/s")
# 已知期望速度,计算左右轮速
v_left, v_right = robot.inverse_kinematics(v=1.0, omega=0.5)
print(f"左轮: {v_left:.2f} m/s, 右轮: {v_right:.2f} m/s")
转弯半径计算:
def turning_radius(v, omega):
"""计算转弯半径"""
if abs(omega) < 1e-6:
return float('inf') # 直线运动
return abs(v / omega)
# 示例
R = turning_radius(v=1.0, omega=0.5)
print(f"转弯半径: {R:.2f} m")
3.1.2 全向轮机器人运动学
三轮全向(120度分布):
class OmniThreeWheel:
def __init__(self, wheel_radius, robot_radius):
self.r = wheel_radius # 轮半径
self.L = robot_radius # 机器人半径
def inverse_kinematics(self, vx, vy, omega):
"""逆运动学: 机器人速度 → 轮速"""
# 120度分布
v1 = -0.5 * vx + 0.866 * vy + self.L * omega
v2 = -0.5 * vx - 0.866 * vy + self.L * omega
v3 = vx + self.L * omega
# 转换为轮速(rad/s)
w1 = v1 / self.r
w2 = v2 / self.r
w3 = v3 / self.r
return w1, w2, w3
def forward_kinematics(self, w1, w2, w3):
"""正运动学: 轮速 → 机器人速度"""
# 轮速转线速度
v1 = w1 * self.r
v2 = w2 * self.r
v3 = w3 * self.r
# 解算机器人速度
vx = (-v1 - v2 + 2 * v3) / 3.0
vy = (v1 - v2) / 1.732
omega = (v1 + v2 + v3) / (3.0 * self.L)
return vx, vy, omega
# 使用示例
robot = OmniThreeWheel(wheel_radius=0.05, robot_radius=0.2)
# 期望向右前方移动
w1, w2, w3 = robot.inverse_kinematics(vx=0.5, vy=0.3, omega=0.0)
print(f"轮速: {w1:.2f}, {w2:.2f}, {w3:.2f} rad/s")
四轮麦克纳姆:
class MecanumWheel:
def __init__(self, wheel_base_x, wheel_base_y, wheel_radius):
self.lx = wheel_base_x / 2 # 半轮距(x方向)
self.ly = wheel_base_y / 2 # 半轮距(y方向)
self.r = wheel_radius
def inverse_kinematics(self, vx, vy, omega):
"""X型配置麦轮逆运动学"""
# 四个轮子: 左前、右前、左后、右后
v_lf = vx - vy - (self.lx + self.ly) * omega
v_rf = vx + vy + (self.lx + self.ly) * omega
v_lr = vx + vy - (self.lx + self.ly) * omega
v_rr = vx - vy + (self.lx + self.ly) * omega
# 转换为轮速
w_lf = v_lf / self.r
w_rf = v_rf / self.r
w_lr = v_lr / self.r
w_rr = v_rr / self.r
return w_lf, w_rf, w_lr, w_rr
def forward_kinematics(self, w_lf, w_rf, w_lr, w_rr):
"""正运动学"""
v_lf = w_lf * self.r
v_rf = w_rf * self.r
v_lr = w_lr * self.r
v_rr = w_rr * self.r
vx = (v_lf + v_rf + v_lr + v_rr) / 4.0
vy = (-v_lf + v_rf + v_lr - v_rr) / 4.0
omega = (-v_lf + v_rf - v_lr + v_rr) / (4.0 * (self.lx + self.ly))
return vx, vy, omega
# 使用示例
robot = MecanumWheel(wheel_base_x=0.4, wheel_base_y=0.3, wheel_radius=0.05)
# 横向移动(麦轮特色)
w_lf, w_rf, w_lr, w_rr = robot.inverse_kinematics(vx=0, vy=0.5, omega=0)
print(f"横向移动轮速: {w_lf:.2f}, {w_rf:.2f}, {w_lr:.2f}, {w_rr:.2f}")
3.1.3 机械臂运动学
D-H参数法:
import numpy as np
def dh_transform(theta, d, a, alpha):
"""D-H变换矩阵"""
ct = np.cos(theta)
st = np.sin(theta)
ca = np.cos(alpha)
sa = np.sin(alpha)
T = np.array([
[ct, -st*ca, st*sa, a*ct],
[st, ct*ca, -ct*sa, a*st],
[0, sa, ca, d ],
[0, 0, 0, 1 ]
])
return T
class RobotArm2DOF:
"""2自由度平面机械臂"""
def __init__(self, L1, L2):
self.L1 = L1 # 第一段长度
self.L2 = L2 # 第二段长度
def forward_kinematics(self, theta1, theta2):
"""正运动学: 关节角 → 末端位置"""
x = self.L1 * np.cos(theta1) + self.L2 * np.cos(theta1 + theta2)
y = self.L1 * np.sin(theta1) + self.L2 * np.sin(theta1 + theta2)
return x, y
def inverse_kinematics(self, x, y):
"""逆运动学: 末端位置 → 关节角"""
# 余弦定理求theta2
c2 = (x**2 + y**2 - self.L1**2 - self.L2**2) / (2 * self.L1 * self.L2)
if abs(c2) > 1:
raise ValueError("目标点超出工作空间")
# 两种解(肘向上/肘向下)
theta2_1 = np.arccos(c2)
theta2_2 = -np.arccos(c2)
# 对应的theta1
k1 = self.L1 + self.L2 * np.cos(theta2_1)
k2 = self.L2 * np.sin(theta2_1)
theta1_1 = np.arctan2(y, x) - np.arctan2(k2, k1)
k1 = self.L1 + self.L2 * np.cos(theta2_2)
k2 = self.L2 * np.sin(theta2_2)
theta1_2 = np.arctan2(y, x) - np.arctan2(k2, k1)
return [(theta1_1, theta2_1), (theta1_2, theta2_2)]
def jacobian(self, theta1, theta2):
"""雅可比矩阵"""
J = np.array([
[-self.L1*np.sin(theta1) - self.L2*np.sin(theta1+theta2),
-self.L2*np.sin(theta1+theta2)],
[self.L1*np.cos(theta1) + self.L2*np.cos(theta1+theta2),
self.L2*np.cos(theta1+theta2)]
])
return J
# 使用示例
arm = RobotArm2DOF(L1=1.0, L2=0.8)
# 正运动学
x, y = arm.forward_kinematics(theta1=np.pi/4, theta2=np.pi/6)
print(f"末端位置: ({x:.2f}, {y:.2f})")
# 逆运动学
solutions = arm.inverse_kinematics(x=1.2, y=0.8)
for i, (t1, t2) in enumerate(solutions):
print(f"解{i+1}: theta1={np.degrees(t1):.2f}°, theta2={np.degrees(t2):.2f}°")
3.2 PID控制
3.2.1 PID原理
PID控制器输出:
u(t) = Kp*e(t) + Ki*∫e(t)dt + Kd*de(t)/dt
离散形式:
u[k] = Kp*e[k] + Ki*Σe[i] + Kd*(e[k]-e[k-1])
其中:
e[k] = target - current (误差)
Kp: 比例系数 (响应速度)
Ki: 积分系数 (消除稳态误差)
Kd: 微分系数 (抑制超调)
3.2.2 PID实现
Python实现:
class PIDController:
def __init__(self, Kp, Ki, Kd, output_limits=None):
self.Kp = Kp
self.Ki = Ki
self.Kd = Kd
self.output_limits = output_limits
self.integral = 0.0
self.last_error = 0.0
def update(self, error, dt):
"""计算PID输出"""
# 比例项
P = self.Kp * error
# 积分项
self.integral += error * dt
I = self.Ki * self.integral
# 微分项
D = self.Kd * (error - self.last_error) / dt if dt > 0 else 0
# 总输出
output = P + I + D
# 限幅
if self.output_limits:
output = np.clip(output, self.output_limits[0], self.output_limits[1])
# 抗积分饱和
if output >= self.output_limits[1] or output <= self.output_limits[0]:
self.integral -= error * dt # 回退积分
self.last_error = error
return output
def reset(self):
"""重置控制器"""
self.integral = 0.0
self.last_error = 0.0
# 使用示例:电机速度控制
pid = PIDController(Kp=1.0, Ki=0.1, Kd=0.05, output_limits=(-255, 255))
target_speed = 1.0 # m/s
current_speed = 0.0
dt = 0.01 # 10ms
for _ in range(100):
error = target_speed - current_speed
pwm = pid.update(error, dt)
# 模拟电机响应
current_speed += pwm * 0.001
print(f"Speed: {current_speed:.3f}, PWM: {pwm:.1f}")
C++实现:
class PIDController {
private:
float Kp, Ki, Kd;
float integral;
float last_error;
float output_min, output_max;
public:
PIDController(float p, float i, float d, float min_out, float max_out)
: Kp(p), Ki(i), Kd(d), integral(0), last_error(0),
output_min(min_out), output_max(max_out) {}
float update(float error, float dt) {
// 比例
float P = Kp * error;
// 积分
integral += error * dt;
float I = Ki * integral;
// 微分
float D = (dt > 0) ? Kd * (error - last_error) / dt : 0;
// 输出
float output = P + I + D;
// 限幅
if (output > output_max) {
output = output_max;
integral -= error * dt; // 抗饱和
} else if (output < output_min) {
output = output_min;
integral -= error * dt;
}
last_error = error;
return output;
}
void reset() {
integral = 0;
last_error = 0;
}
};
// Arduino使用
PIDController motor_pid(1.0, 0.1, 0.05, -255, 255);
void loop() {
float target = 1.0; // 目标速度
float current = getMotorSpeed(); // 当前速度
float error = target - current;
int pwm = (int)motor_pid.update(error, 0.01); // 10ms
setMotorPWM(pwm);
delay(10);
}
3.2.3 参数整定
Ziegler-Nichols方法:
步骤1: 设置Ki=0, Kd=0
步骤2: 逐渐增加Kp直到系统震荡(临界增益Ku)
步骤3: 测量震荡周期Tu
参数计算:
经典PID:
Kp = 0.6 * Ku
Ki = 2 * Kp / Tu
Kd = Kp * Tu / 8
P控制器:
Kp = 0.5 * Ku
PI控制器:
Kp = 0.45 * Ku
Ki = 1.2 * Kp / Tu
手动调参经验:
def tune_pid_manually():
"""
调参步骤:
1. 设置Ki=0, Kd=0, 调节Kp
- Kp过小: 响应慢
- Kp过大: 震荡
- 目标: 快速响应但略有超调
2. 增加Ki消除稳态误差
- Ki过小: 残余误差
- Ki过大: 震荡、积分饱和
- 目标: 稳态误差为0
3. 增加Kd抑制超调
- Kd过小: 超调大
- Kd过大: 对噪声敏感
- 目标: 减小超调,平滑响应
"""
pass
# 典型值参考
# 电机速度控制: Kp=1.0, Ki=0.1, Kd=0.05
# 位置控制: Kp=5.0, Ki=0.5, Kd=0.2
# 温度控制: Kp=2.0, Ki=0.1, Kd=0.0
3.3 路径规划算法
3.3.1 A*算法
import heapq
import numpy as np
class AStarPlanner:
def __init__(self, grid_map):
"""
grid_map: 0=可通行, 1=障碍物
"""
self.grid = np.array(grid_map)
self.rows, self.cols = self.grid.shape
def heuristic(self, node, goal):
"""启发式函数(曼哈顿距离)"""
return abs(node[0] - goal[0]) + abs(node[1] - goal[1])
def get_neighbors(self, node):
"""获取邻居节点(8方向)"""
neighbors = []
directions = [
(-1, 0), (1, 0), (0, -1), (0, 1), # 上下左右
(-1, -1), (-1, 1), (1, -1), (1, 1) # 对角线
]
for dx, dy in directions:
x, y = node[0] + dx, node[1] + dy
if 0 <= x < self.rows and 0 <= y < self.cols:
if self.grid[x, y] == 0: # 可通行
cost = 1.414 if dx != 0 and dy != 0 else 1.0 # 对角线代价
neighbors.append(((x, y), cost))
return neighbors
def plan(self, start, goal):
"""A*路径规划"""
# 初始化
open_set = []
heapq.heappush(open_set, (0, start))
came_from = {}
g_score = {start: 0}
f_score = {start: self.heuristic(start, goal)}
while open_set:
current = heapq.heappop(open_set)[1]
if current == goal:
# 重建路径
path = []
while current in came_from:
path.append(current)
current = came_from[current]
path.append(start)
return path[::-1]
for neighbor, cost in self.get_neighbors(current):
tentative_g = g_score[current] + cost
if neighbor not in g_score or tentative_g < g_score[neighbor]:
came_from[neighbor] = current
g_score[neighbor] = tentative_g
f_score[neighbor] = tentative_g + self.heuristic(neighbor, goal)
heapq.heappush(open_set, (f_score[neighbor], neighbor))
return None # 无路径
# 使用示例
grid_map = [
[0, 0, 0, 1, 0],
[0, 1, 0, 1, 0],
[0, 1, 0, 0, 0],
[0, 0, 0, 1, 0],
[0, 0, 0, 0, 0]
]
planner = AStarPlanner(grid_map)
path = planner.plan(start=(0, 0), goal=(4, 4))
print("Path:", path)
3.3.2 RRT算法
class RRTPlanner:
def __init__(self, start, goal, obstacles, bounds, step_size=0.5, max_iter=1000):
self.start = np.array(start)
self.goal = np.array(goal)
self.obstacles = obstacles # 障碍物列表
self.bounds = bounds # (x_min, x_max, y_min, y_max)
self.step_size = step_size
self.max_iter = max_iter
self.tree = {tuple(self.start): None}
def random_point(self):
"""生成随机点"""
x = np.random.uniform(self.bounds[0], self.bounds[1])
y = np.random.uniform(self.bounds[2], self.bounds[3])
return np.array([x, y])
def nearest_node(self, point):
"""找最近节点"""
nodes = np.array(list(self.tree.keys()))
distances = np.linalg.norm(nodes - point, axis=1)
return tuple(nodes[np.argmin(distances)])
def steer(self, from_node, to_point):
"""从from_node向to_point扩展step_size"""
direction = to_point - np.array(from_node)
distance = np.linalg.norm(direction)
if distance < self.step_size:
return to_point
return np.array(from_node) + direction / distance * self.step_size
def is_collision_free(self, point):
"""检查是否无碰撞"""
for obs_center, obs_radius in self.obstacles:
if np.linalg.norm(point - np.array(obs_center)) < obs_radius:
return False
return True
def plan(self):
"""RRT规划"""
for i in range(self.max_iter):
# 以一定概率采样目标点
if np.random.random() < 0.1:
rand_point = self.goal
else:
rand_point = self.random_point()
# 找最近节点
nearest = self.nearest_node(rand_point)
# 扩展
new_point = self.steer(nearest, rand_point)
# 碰撞检测
if self.is_collision_free(new_point):
self.tree[tuple(new_point)] = nearest
# 检查是否到达目标
if np.linalg.norm(new_point - self.goal) < self.step_size:
self.tree[tuple(self.goal)] = tuple(new_point)
return self.extract_path()
return None
def extract_path(self):
"""提取路径"""
path = [tuple(self.goal)]
current = tuple(self.goal)
while current != tuple(self.start):
current = self.tree[current]
path.append(current)
return path[::-1]
# 使用示例
obstacles = [
([2, 2], 0.5), # 圆心(2,2), 半径0.5
([3, 4], 0.7)
]
planner = RRTPlanner(
start=(0, 0),
goal=(5, 5),
obstacles=obstacles,
bounds=(0, 5, 0, 5),
step_size=0.3
)
path = planner.plan()
print("RRT Path:", path)
3.3.3 DWA(动态窗口法)
class DWA:
def __init__(self, config):
# 机器人参数
self.max_speed = config['max_speed']
self.min_speed = config['min_speed']
self.max_yaw_rate = config['max_yaw_rate']
self.max_accel = config['max_accel']
self.max_dyaw_rate = config['max_dyaw_rate']
# DWA参数
self.v_resolution = config['v_resolution']
self.yaw_rate_resolution = config['yaw_rate_resolution']
self.dt = config['dt']
self.predict_time = config['predict_time']
# 评价函数权重
self.heading_weight = config['heading_weight']
self.clearance_weight = config['clearance_weight']
self.velocity_weight = config['velocity_weight']
def calc_dynamic_window(self, v, yaw_rate):
"""计算动态窗口"""
# 基于速度限制的窗口
Vs = [self.min_speed, self.max_speed,
-self.max_yaw_rate, self.max_yaw_rate]
# 基于加速度限制的窗口
Vd = [v - self.max_accel * self.dt,
v + self.max_accel * self.dt,
yaw_rate - self.max_dyaw_rate * self.dt,
yaw_rate + self.max_dyaw_rate * self.dt]
# 交集
dw = [max(Vs[0], Vd[0]), min(Vs[1], Vd[1]),
max(Vs[2], Vd[2]), min(Vs[3], Vd[3])]
return dw
def predict_trajectory(self, x, y, yaw, v, yaw_rate):
"""预测轨迹"""
trajectory = [[x, y, yaw, v, yaw_rate]]
time = 0
while time <= self.predict_time:
x += v * np.cos(yaw) * self.dt
y += v * np.sin(yaw) * self.dt
yaw += yaw_rate * self.dt
trajectory.append([x, y, yaw, v, yaw_rate])
time += self.dt
return trajectory
def calc_heading_eval(self, trajectory, goal):
"""航向评价"""
dx = goal[0] - trajectory[-1][0]
dy = goal[1] - trajectory[-1][1]
goal_theta = np.arctan2(dy, dx)
cost = abs(goal_theta - trajectory[-1][2])
return np.pi - cost # 转换为评分(越大越好)
def calc_clearance_eval(self, trajectory, obstacles):
"""障碍物距离评价"""
min_dist = float('inf')
for point in trajectory:
for obs in obstacles:
dist = np.hypot(point[0] - obs[0], point[1] - obs[1])
if dist < min_dist:
min_dist = dist
return min_dist
def calc_velocity_eval(self, trajectory):
"""速度评价"""
return trajectory[-1][3] # 期望快速行驶
def plan(self, x, y, yaw, v, yaw_rate, goal, obstacles):
"""DWA规划"""
dw = self.calc_dynamic_window(v, yaw_rate)
best_v, best_yaw_rate = 0, 0
best_score = -float('inf')
best_trajectory = None
# 遍历速度空间
for test_v in np.arange(dw[0], dw[1], self.v_resolution):
for test_yaw_rate in np.arange(dw[2], dw[3], self.yaw_rate_resolution):
# 预测轨迹
trajectory = self.predict_trajectory(x, y, yaw, test_v, test_yaw_rate)
# 评价
heading = self.calc_heading_eval(trajectory, goal)
clearance = self.calc_clearance_eval(trajectory, obstacles)
velocity = self.calc_velocity_eval(trajectory)
# 综合评分
score = (self.heading_weight * heading +
self.clearance_weight * clearance +
self.velocity_weight * velocity)
# 选择最优
if score > best_score:
best_score = score
best_v = test_v
best_yaw_rate = test_yaw_rate
best_trajectory = trajectory
return best_v, best_yaw_rate, best_trajectory
# 使用示例
config = {
'max_speed': 1.0,
'min_speed': 0.0,
'max_yaw_rate': 40.0 * np.pi / 180.0,
'max_accel': 0.2,
'max_dyaw_rate': 40.0 * np.pi / 180.0,
'v_resolution': 0.01,
'yaw_rate_resolution': 0.1 * np.pi / 180.0,
'dt': 0.1,
'predict_time': 3.0,
'heading_weight': 1.0,
'clearance_weight': 2.0,
'velocity_weight': 1.0
}
dwa = DWA(config)
v, yaw_rate, trajectory = dwa.plan(
x=0, y=0, yaw=0, v=0.5, yaw_rate=0,
goal=(10, 10),
obstacles=[(5, 5), (6, 6)]
)
print(f"最优速度: {v:.2f} m/s, 角速度: {yaw_rate:.2f} rad/s")
3.4 轨迹跟踪
3.4.1 Pure Pursuit(纯跟踪)
class PurePursuit:
def __init__(self, lookahead_distance, wheelbase):
self.ld = lookahead_distance # 前瞻距离
self.L = wheelbase # 轮距
def find_target_point(self, path, current_pos):
"""找前瞻点"""
min_dist = float('inf')
target_idx = 0
for i, point in enumerate(path):
dist = np.hypot(point[0] - current_pos[0],
point[1] - current_pos[1])
if abs(dist - self.ld) < min_dist:
min_dist = abs(dist - self.ld)
target_idx = i
return path[target_idx]
def calc_steering(self, current_pos, current_yaw, target_point):
"""计算转向角"""
dx = target_point[0] - current_pos[0]
dy = target_point[1] - current_pos[1]
# 前瞻点在车体坐标系下的位置
alpha = np.arctan2(dy, dx) - current_yaw
# Pure Pursuit公式
steering = np.arctan2(2.0 * self.L * np.sin(alpha), self.ld)
return steering
# 使用示例
pp = PurePursuit(lookahead_distance=2.0, wheelbase=0.3)
path = [(0, 0), (1, 0), (2, 0.5), (3, 1), (4, 1.5)]
current_pos = (0.5, 0.1)
current_yaw = 0.1
target = pp.find_target_point(path, current_pos)
steering = pp.calc_steering(current_pos, current_yaw, target)
print(f"转向角: {np.degrees(steering):.2f}°")
3.5 高频面试题
问题1: 差速机器人如何实现原地旋转和圆弧运动?
答案:
原地旋转:
# 左右轮速相反,大小相等
v_left = -0.5 # m/s
v_right = 0.5 # m/s
# 角速度: ω = (v_right - v_left) / L
# 线速度: v = (v_left + v_right) / 2 = 0
圆弧运动:
# 转弯半径R, 线速度v, 轮距L
R = 2.0 # 转弯半径
v = 1.0 # 线速度
L = 0.3 # 轮距
# 角速度
omega = v / R
# 左右轮速
v_left = v - omega * L / 2
v_right = v + omega * L / 2
# 例: R=2m, v=1m/s, L=0.3m
# omega = 0.5 rad/s
# v_left = 0.925 m/s
# v_right = 1.075 m/s
最小转弯半径:
R_min = L / 2
当一侧轮速为0时达到
问题2: PID控制器中的积分饱和问题如何解决?
答案:
积分饱和导致响应变慢,解决方法:
- 限幅后回退积分
output = Kp*error + Ki*integral + Kd*derivative
if output > limit:
output = limit
integral -= error * dt # 回退
- 积分分离
if abs(error) > threshold:
# 误差大时不积分
I = 0
else:
integral += error * dt
I = Ki * integral
- 积分限幅
integral += error * dt
integral = np.clip(integral, -integral_limit, integral_limit)
- 变积分系数
if abs(error) > threshold:
Ki_effective = 0
else:
Ki_effective = Ki * (1 - abs(error)/threshold)
问题3: A*算法和Dijkstra算法有什么区别?
答案:
| 特性 | Dijkstra | A* |
|---|---|---|
| 搜索策略 | 均匀扩展 | 启发式搜索 |
| 代价函数 | g(n) | f(n)=g(n)+h(n) |
| 效率 | 较低 | 高 |
| 最优性 | 保证最优 | h(n)可容时最优 |
| 适用 | 单源最短路 | 点对点规划 |
核心区别:
# Dijkstra
f = g # 只考虑已走距离
# A*
f = g + h # 已走距离 + 启发式估计
# 启发式函数h(n)要求:
# 1. h(n)必须可容(admissible): h(n) <= 实际代价
# 2. h(n)一致(consistent): h(n) <= cost(n,n') + h(n')
常用启发式函数:
- 曼哈顿: |x1-x2| + |y1-y2| (4方向)
- 欧几里得: √((x1-x2)² + (y1-y2)²) (8方向)
- 对角距离: max(|x1-x2|, |y1-y2|) (8方向快速估计)
问题4: 机械臂逆运动学可能有多个解,如何选择?
答案:
解的类型:
- 多解(冗余)
- 单解
- 无解(超出工作空间)
选择策略:
- 最近解
def select_nearest_solution(solutions, current_joints):
"""选择与当前关节角最接近的解"""
min_dist = float('inf')
best_sol = None
for sol in solutions:
dist = sum((s - c)**2 for s, c in zip(sol, current_joints))
if dist < min_dist:
min_dist = dist
best_sol = sol
return best_sol
- 避免关节限位
def is_valid_solution(joints, limits):
"""检查是否在关节限位内"""
for joint, (min_limit, max_limit) in zip(joints, limits):
if not (min_limit <= joint <= max_limit):
return False
return True
- 避免奇异点
def check_singularity(joints):
"""检查雅可比矩阵行列式"""
J = calculate_jacobian(joints)
det = np.linalg.det(J)
return abs(det) > threshold # 远离奇异
- 最小能量
def energy_cost(joints):
"""关节角度平方和最小"""
return sum(j**2 for j in joints)
问题5: 如何实现机器人的平滑启停?
答案:
方法1: S型加减速曲线
def s_curve_profile(t, t_total, v_max):
"""S型速度曲线"""
if t < t_total / 3:
# 加速阶段
v = v_max * (3 * (t / t_total)**2 - 2 * (t / t_total)**3)
elif t < 2 * t_total / 3:
# 匀速阶段
v = v_max
else:
# 减速阶段
t_decel = t - 2 * t_total / 3
v = v_max * (1 - 3 * (t_decel / (t_total/3))**2
+ 2 * (t_decel / (t_total/3))**3)
return v
方法2: 梯形速度曲线
class TrapezoidalProfile:
def __init__(self, v_max, accel):
self.v_max = v_max
self.accel = accel
def generate(self, distance):
# 加速时间
t_accel = self.v_max / self.accel
# 加速距离
d_accel = 0.5 * self.accel * t_accel**2
if 2 * d_accel > distance:
# 无匀速段
v_peak = np.sqrt(self.accel * distance)
return self.triangular_profile(distance, v_peak)
else:
# 有匀速段
return self.trapezoidal_profile(distance, d_accel, t_accel)
方法3: 低通滤波
class LowPassFilter:
def __init__(self, alpha):
self.alpha = alpha # 0-1, 越小越平滑
self.value = 0
def update(self, new_value):
self.value = self.alpha * new_value + (1 - self.alpha) * self.value
return self.value
# 使用
lpf = LowPassFilter(alpha=0.1)
smoothed_speed = lpf.update(target_speed)