人工势场法在轨迹规划中的应用:以三连杆机械臂为例
演示效果图1. 引言
轨迹规划是机器人运动控制中的核心问题之一,常见的方法包括人工势场法(Artificial Potential Field, APF)、动态规划、快速探索随机树(RRT)等。其中,人工势场法因其计算简单、直观易理解而广泛应用于机器人路径规划。
本文将详细介绍人工势场法的基本原理,并以三连杆机械臂为实例,使用 MATLAB 实现轨迹规划的完整代码。
2. 人工势场法原理
人工势场法的核心思想是将机器人视为一个受力物体,环境中的目标点施加吸引力,障碍物施加斥力,使得机器人在势场的引导下朝向目标,同时避开障碍物。
2.1 势场函数
人工势场由两部分组成: 1. 吸引势场:用于引导机器人朝向目标点; 2. 斥力势场:用于使机器人远离障碍物。
2.1.1 吸引势场
吸引势场通常采用二次函数形式:
吸引力计算如下:
其中, $k_{att}$ 是吸引势场的增益系数,$q$ 为机器人当前位置,$q_{goal}$ 为目标位置。
2.1.2 斥力势场
斥力势场通常采用指数形式或双曲线形式,这里采用如下表达式:
对应的斥力计算如下:
其中,$d(q, q_{obs})$ 为机器人到障碍物的欧几里得距离,$d_0$ 为斥力作用的阈值,$k_{rep}$ 为斥力势场的增益系数。
2.2 运动方向
机器人在势场中的受力为吸引力与斥力之和:
机器人沿着总力方向移动,从而实现避障和目标跟踪。
3. 三连杆机械臂轨迹规划示例
3.1 案例数学建模
三连杆机械臂具有三个旋转关节,关节角度变量: $\mathbf{q} = [\theta_1, \theta_2, \theta_3]^T$ ,定义机械臂的运动学方程如下:
其中,$L_1, L_2, L_3$ 分别为三个连杆的长度,案例中给定 $ L_1=1, \quad L_2=1, \quad L_3=0.5 $。 初始关节角度和目标关节角度分别为:
- 初始关节角度: $ \mathbf{q} = [q_1, q_2, q_3]^T = [0, 0, 0]^T$
- 目标关节角度: $ \mathbf{q}_{\text{goal}} = [q_{1,\text{goal}}, q_{2,\text{goal}}, q_{3,\text{goal}}]^T = \left[\frac{\pi}{4}, \frac{\pi}{6}, -\frac{\pi}{6}\right]^T$
计算目标末端执行器在笛卡尔空间中的位置。正向运动学公式如下:
代入具体数值:
因此,目标末端执行器的位置为:
定义吸引力和斥力的参数:
- 吸引力比例系数: $ k_{\text{att}} = 1$
- 斥力比例系数: $ k_{\text{rep}} = 0.1$
- 斥力有效范围: $ d_0 = 0.5$
障碍物在笛卡尔坐标系中的位置为:
定义迭代参数:
- 最大迭代次数: $ \text{max\_iters} = 200$
- 步长因子: $ \alpha = 0.1$
- 阻尼因子: $ \lambda = 0.01$
在每次迭代中,更新关节角度以使末端执行器接近目标并避开障碍物。使用正向运动学计算当前关节角度下的末端执行器位置:
因此,末端执行器的位置为:
吸引力力由目标位置和当前位置之间的差值决定:
斥力(障碍物势场梯度)仅当末端执行器接近障碍物时生效。如果末端执行器与障碍物的距离小于 $d_0$,则计算斥力:
如果 $ d_{\text{obs}} < d_0$:
否则:
总合力为吸引力和斥力之和:
雅可比矩阵的计算
雅可比矩阵 J 表示末端执行器位置对关节角度的偏导数,即:
对于三连杆机械臂,末端执行器的位置坐标 (x, y) 可以表示为:
其中: - $l_1, l_2, l_3$ 是三个连杆的长度 - $\theta_1, \theta_2, \theta_3$ 是三个关节角度
对这些方程求偏导,可以得到雅可比矩阵的各个元素:
- 对于第一个关节 $\theta_1$:
- 对于第二个关节 $\theta_2$:
- 对于第三个关节 $\theta_3$:
这样计算得到的雅可比矩阵可以用于:
- 将末端执行器的速度转换为关节角速度
- 将末端执行器的力转换为关节力矩
- 在路径规划中进行速度和位置控制
这里的雅可比矩阵 $J$ (2x3) 描述了末端执行器速度与关节速度之间的关系:
具体展开为:
为了稳定求解,使用奇异值分解(SVD)来计算雅可比矩阵的伪逆:
其中 $ I$ 是单位矩阵,$ \lambda$ 是阻尼因子。
伪逆矩阵 $ J^\dagger$ 可以表示为:
其中 $ s_i$ 是 $ S$ 的对角元素,$ \epsilon$ 是一个小常数以避免除以零。
最终的关节角度变化 $ \Delta \mathbf{q}$ (阻尼伪逆控制律)为:
参数说明: - $\alpha=0.1$:步长因子 - $\lambda=0.01$:阻尼因子(防止矩阵奇异)
更新关节角度:
3.2 MATLAB 代码实现
- 初始化:设置目标位形 $\mathbf{q}_{goal}$,计算目标位置 $\mathbf{p}_{goal}$
- 迭代过程:
while ||Δp|| > ε and iter < max_iters do
1. 计算当前末端位置 p_e
2. 计算合力 F = F_att + F_rep
3. 计算雅可比矩阵 J(q)
4. 求阻尼伪逆 J⁺ = J^T(JJ^T + λ²I)^-1
5. 更新关节角度:q ← q + αJ⁺F
6. 可视化检测
end while
| 参数 | 作用域 | 调节效果 |
|---|---|---|
| k_att | 吸引力场强度 | 增大加速收敛,但可能引发振荡 |
| k_rep | 斥力场强度 | 增大增强避障,但可能造成局部极小 |
| λ | 阻尼因子 | 增大提高稳定性,降低收敛速度 |
| α | 步长因子 | 影响收敛速度和超调量 |
下面的 MATLAB 代码实现了人工势场法在三连杆机械臂轨迹规划中的应用。
clc; clear; close all;
% 机械臂参数
L1 = 1; L2 = 1; L3 = 0.5;
q = [0; 0; 0]; % 初始关节角度
q_goal = [pi/4; pi/6; -pi/6]; % 目标关节角度
% 计算目标末端位置(笛卡尔空间)
target_x = L1*cos(q_goal(1)) + L2*cos(q_goal(1)+q_goal(2)) + L3*cos(q_goal(1)+q_goal(2)+q_goal(3));
target_y = L1*sin(q_goal(1)) + L2*sin(q_goal(1)+q_goal(2)) + L3*sin(q_goal(1)+q_goal(2)+q_goal(3));
target = [target_x; target_y];
% 势场参数
k_att = 1;
k_rep = 0.1;
d0 = 0.5;
% 障碍物位置(笛卡尔坐标)
obstacle = [1.2, 1.2];
% 迭代参数
max_iters = 200;
alpha = 0.05; % 步长因子
lambda = 0.05; % 阻尼因子(关键修改)
% GIF 文件设置
gif_filename = 'robot_arm_motion.gif';
delay_time = 0.1; % 每帧间隔时间
figure;
for iter = 1:max_iters
% 计算各关节位置
x0 = 0; y0 = 0;
x1 = L1*cos(q(1));
y1 = L1*sin(q(1));
x2 = x1 + L2*cos(q(1)+q(2));
y2 = y1 + L2*sin(q(1)+q(2));
x_end = x2 + L3*cos(q(1)+q(2)+q(3));
y_end = y2 + L3*sin(q(1)+q(2)+q(3));
end_effector = [x_end; y_end];
% 计算吸引力(目标势场梯度)
F_att = -k_att * (end_effector - target);
% 计算斥力(障碍物势场梯度)
d_obs = norm(end_effector - obstacle');
if d_obs < d0
F_rep = k_rep * (1/d_obs - 1/d0) * (1/d_obs^3) * (end_effector - obstacle');
else
F_rep = [0; 0];
end
% 总合力
F = F_att + F_rep;
% 计算雅可比矩阵 J (2x3)(基于当前关节角度 q)
J = [ -L1*sin(q(1)) - L2*sin(q(1)+q_goal(2)) - L3*sin(q(1)+q_goal(2)+q_goal(3)), ...
-L2*sin(q(1)+q_goal(2)) - L3*sin(q(1)+q_goal(2)+q_goal(3)), ...
-L3*sin(q(1)+q_goal(2)+q_goal(3));
L1*cos(q(1)) + L2*cos(q(1)+q_goal(2)) + L3*cos(q(1)+q_goal(2)+q_goal(3)), ...
L2*cos(q(1)+q_goal(2)) + L3*cos(q(1)+q_goal(2)+q_goal(3)), ...
L3*cos(q(1)+q_goal(2)+q_goal(3))];
% 伪逆计算(带SVD稳定性处理)✅
[U,S,V] = svd(J*J' + lambda^2*eye(2));
inv_term = V * diag(1./(diag(S)+eps)) * U'; % 添加eps避免除以零
dq = alpha * J' * inv_term * F;
% 更新关节角度
q = q + dq;
% 可视化机械臂和环境
clf;
hold on;
plot([x0, x1, x2, x_end], [y0, y1, y2, y_end], 'ro-', 'LineWidth',2);
plot(obstacle(1), obstacle(2), 'ks', 'MarkerSize', 10, 'MarkerFaceColor', 'k');
plot(target(1), target(2), 'g*', 'MarkerSize', 10);
axis equal;
axis([-1 3 -1 3]);
grid on;
title(sprintf('Iteration: %d', iter));
drawnow;
% 保存 GIF
frame = getframe(gcf);
img = frame2im(frame);
[A, map] = rgb2ind(img, 256);
if iter == 1
imwrite(A, map, gif_filename, 'gif', 'LoopCount', Inf, 'DelayTime', delay_time);
else
imwrite(A, map, gif_filename, 'gif', 'WriteMode', 'append', 'DelayTime', delay_time);
end
pause(0.1);
% 判断是否已接近目标
if norm(end_effector - target) < 0.01
break;
end
end
4.补充:python 实现
python的实现效果似乎更好,更清晰:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from matplotlib.animation import PillowWriter
import matplotlib.patches as patches
import time
class ThreeLinkArm:
def __init__(self, link_lengths=[1.2, 1.0, 0.8]):
"""初始化三连杆机械臂
Args:
link_lengths: 三个连杆的长度列表,默认为[1.2, 1.0, 0.8]
"""
self.link_lengths = link_lengths
self.num_links = len(link_lengths) # 连杆数量
self.joints = np.zeros((self.num_links + 1, 2)) # 存储所有关节的坐标,包括基座和末端
self.angles = np.zeros(self.num_links) # 存储各关节角度
self.update_joints()
def update_joints(self):
"""根据关节角度更新各关节位置"""
self.joints[0] = [0, 0] # 基座位置
cumulative_angle = 0
for i in range(self.num_links):
cumulative_angle += self.angles[i]
x = self.joints[i][0] + self.link_lengths[i] * np.cos(cumulative_angle)
y = self.joints[i][1] + self.link_lengths[i] * np.sin(cumulative_angle)
self.joints[i+1] = [x, y]
def get_end_effector(self):
"""获取末端执行器位置"""
return self.joints[-1]
def set_angles(self, angles):
"""设置关节角度"""
self.angles = np.array(angles)
self.update_joints()
def forward_kinematics(self, angles):
"""前向运动学:给定角度计算末端位置"""
x = 0
y = 0
cumulative_angle = 0
for i in range(self.num_links):
cumulative_angle += angles[i]
x += self.link_lengths[i] * np.cos(cumulative_angle)
y += self.link_lengths[i] * np.sin(cumulative_angle)
return np.array([x, y])
def jacobian(self):
"""计算雅可比矩阵"""
J = np.zeros((2, self.num_links))
for i in range(self.num_links):
x_partial = 0
y_partial = 0
for j in range(i, self.num_links):
angle_sum = np.sum(self.angles[:j+1])
x_partial -= self.link_lengths[j] * np.sin(angle_sum)
y_partial += self.link_lengths[j] * np.cos(angle_sum)
J[0, i] = x_partial
J[1, i] = y_partial
return J
def artificial_potential_field(arm, target, obstacles, k_att=1.0, k_rep=10.0, d0=0.5):
"""计算基于人工势场的合力
Args:
arm: 机械臂对象
target: 目标位置坐标 [x, y]
obstacles: 障碍物列表,每个元素为 [x, y, radius]
k_att: 引力系数
k_rep: 斥力系数
d0: 障碍物影响阈值距离
Returns:
total_force: 合力向量 [Fx, Fy]
"""
current_pos = arm.get_end_effector()
# 计算引力:与目标点的距离成正比
att_vec = k_att * (np.array(target) - current_pos)
# 计算斥力:当距离障碍物小于阈值d0时产生
rep_vec = np.zeros(2)
for obs in obstacles:
obs_pos = np.array(obs[:2])
obs_radius = obs[2]
# 计算到障碍物边缘的距离向量
dist_vec = current_pos - obs_pos
# `np.linalg.norm(dist_vec)` 计算这个向量的模长(欧几里得距离)
# `- obs_radius` 减去障碍物的半径
dist = np.linalg.norm(dist_vec) - obs_radius # 到障碍物边缘的实际距离
dist = max(dist, 0.01) # 防止距离为0导致除法错误
if dist < d0: # 只有在影响范围内才计算斥力
rep_magnitude = k_rep * (1/dist - 1/d0) * (1/dist**2)
rep_vec += rep_magnitude * (dist_vec/dist)
total_force = att_vec + rep_vec
return total_force
def plan_path(arm, start_angles, target_position, obstacles, max_steps=2000, dt=0.01):
"""使用人工势场法规划路径
Args:
arm: 机械臂对象
start_angles: 初始关节角度
target_position: 目标位置 [x, y]
obstacles: 障碍物列表
max_steps: 最大迭代步数
dt: 时间步长
Returns:
path: 关节角度路径列表
positions: 末端执行器位置列表
"""
arm.set_angles(start_angles)
path = [arm.angles.copy()]
positions = [arm.get_end_effector().copy()]
for step in range(max_steps):
current_pos = arm.get_end_effector()
# 检查是否到达目标(末端位置与目标距离小于阈值)
if np.linalg.norm(np.array(target_position) - current_pos) < 0.05:
print(f"目标达成!在第 {step} 步")
break
# 计算人工势场力
force = artificial_potential_field(arm, target_position, obstacles)
# 通过雅可比矩阵将末端力转换为关节角度变化
J = arm.jacobian()
try:
J_inv = np.linalg.pinv(J) # 计算雅可比矩阵的伪逆(Moore-Penrose伪逆),用于将末端力转换为关节力矩
angle_changes = J_inv @ force # 将末端力转换为关节角度变化量(Δθ = α * J⁺ * F,α为缩放因子)
angle_changes = np.clip(angle_changes, -0.1, 0.1) # 对关节角度变化量进行限幅,防止单步变化过大(数值稳定性)
arm.angles += dt * angle_changes # 根据时间步长 dt 更新关节角度(θ_{k+1} = θ_k + dt * Δθ)
arm.update_joints() # 更新机械臂关节的几何位置(前向运动学)
path.append(arm.angles.copy()) # 记录当前关节角度和末端位置,用于路径可视化
positions.append(arm.get_end_effector().copy())
except np.linalg.LinAlgError:
print("雅可比矩阵求逆出错,可能处于奇异点") # 处理雅可比矩阵奇异的情况(如机械臂处于奇异构型)
break
return path, positions
def visualize_arm_motion(arm, path, positions, obstacles, target, save_animation=False):
"""可视化机械臂运动过程
Args:
arm: 机械臂对象
path: 关节角度路径列表
positions: 末端执行器位置列表
obstacles: 障碍物列表
target: 目标位置
save_animation: 是否保存动画为GIF文件
"""
# 创建画布和坐标轴
fig, ax = plt.subplots(figsize=(10, 8))
# 设置坐标轴范围和网格
ax.set_xlim(-1, 4)
ax.set_ylim(-1, 2)
ax.set_aspect('equal') # 保持横纵比例相等
ax.grid(True)
# 绘制目标点(绿色圆形)
target_point = plt.Circle(target, 0.1, color='green', alpha=0.7)
ax.add_patch(target_point)
ax.text(target[0]+0.1, target[1]+0.1, "目标", fontsize=12)
# 绘制障碍物(红色圆形)
for obs in obstacles:
obstacle = plt.Circle(obs[:2], obs[2], color='red', alpha=0.5)
ax.add_patch(obstacle)
# 绘制末端执行器的运动轨迹
positions_arr = np.array(positions)
ax.plot(positions_arr[:, 0], positions_arr[:, 1], 'b--', alpha=0.3, label='末端轨迹')
# 创建机械臂连杆和关节的绘图对象
arm_links = [plt.Line2D([], [], color='black', lw=3) for _ in range(arm.num_links)]
arm_joints = [plt.Circle((0, 0), 0.05, color='blue', zorder=3) for _ in range(arm.num_links + 1)]
for link in arm_links:
ax.add_line(link)
for joint in arm_joints:
ax.add_patch(joint)
end_effector = plt.Circle((0, 0), 0.08, color='purple', zorder=3)
ax.add_patch(end_effector)
# 添加文本信息
time_text = ax.text(0.02, 0.95, '', transform=ax.transAxes)
angle_text = ax.text(0.02, 0.90, '', transform=ax.transAxes)
ax.legend(loc='upper right')
ax.set_title('三连杆机械臂人工势场法路径规划')
def init():
for link in arm_links:
link.set_data([], [])
return arm_links + arm_joints + [end_effector, time_text, angle_text]
def animate(i):
arm.set_angles(path[i])
# 更新连杆数据
for j in range(arm.num_links):
x_data = [arm.joints[j][0], arm.joints[j+1][0]]
y_data = [arm.joints[j][1], arm.joints[j+1][1]]
arm_links[j].set_data(x_data, y_data)
# 更新关节位置
for j in range(arm.num_links + 1):
arm_joints[j].center = arm.joints[j]
end_effector.center = arm.joints[-1]
time_text.set_text(f'步骤: {i}/{len(path)-1}')
angle_text.set_text(f'角度: [{arm.angles[0]:.2f}, {arm.angles[1]:.2f}, {arm.angles[2]:.2f}]')
return arm_links + arm_joints + [end_effector, time_text, angle_text]
ani = FuncAnimation(fig, animate, frames=len(path), init_func=init, interval=20, blit=True)
if save_animation:
ani.save('arm_motion.gif', writer=PillowWriter(fps=30)) # 使用 PillowWriter 保存为 GIF
plt.tight_layout()
plt.show()
return ani
def main():
"""主函数:设置参数并运行仿真"""
# 设置中文字体
plt.rcParams['font.sans-serif'] = ['SimSun']
# 创建机械臂实例
arm = ThreeLinkArm(link_lengths=[1.2, 1.0, 0.8])
# 设置初始角度(所有关节角度为0)
start_angles = [0.0, 0.0, 0.0]
# 设置目标位置坐标
target = [2, 1.5]
# 设置障碍物位置和大小:[x坐标, y坐标, 半径]
obstacles = [
[0.7, 0.3, 0.1],
[0.8, 1.2, 0.2],
[1.2, 0.8, 0.2],
[2.5, 0.5, 0.1]
]
print("正在使用人工势场法规划路径...")
path, positions = plan_path(arm, start_angles, target, obstacles, max_steps=2000, dt=0.01)
print("正在可视化机械臂运动...")
visualize_arm_motion(arm, path, positions, obstacles, target, save_animation=True)
print("完成!")
if __name__ == "__main__":
main()
5.人工势场法的优缺点
优点:
- 计算简单:不需要复杂的路径搜索算法,仅通过计算势场梯度进行导航,适用于实时应用。
- 直观易理解:物理意义明确,吸引力和斥力的概念直观易懂,易于调试和优化。
- 高效性:计算复杂度较低,适用于低计算资源的嵌入式系统。
- 适用于动态环境:可以根据障碍物或目标点的变化即时调整轨迹,不需要重新规划整个路径。
缺点:
- 局部极小问题:在势场中可能会陷入局部最小值,导致机器人无法继续前进。
- 目标不可达问题:如果目标位于障碍物的凹陷区域,可能导致无法到达目标点。
- 震荡问题:当机器人接近障碍物或目标时,可能会产生来回摆动的现象,特别是在势场设计不合理时。
- 参数敏感性:吸引力和斥力的参数(增益系数、作用范围等)需要仔细调节,否则可能导致路径不稳定或不可行。
实践过程中的关键点
首先是避免局部极小问题。可以引入随机扰动(如模拟退火或随机势场扰动)来帮助机器人跳出局部最小值;采用虚拟势场或路径启发式方法,比如在目标方向添加小的外力来引导机器人前进;还能结合其他路径规划算法(如 A* 、RRT)用于全局优化,再用人工势场法进行局部避障。
其次是改善震荡问题。可以采用梯度下降 + 惯性项,在运动过程中增加一个速度记忆项,使其运动更加平滑;设计更平滑的势场函数,避免在边界附近出现过大的梯度变化;采用变步长方法,当接近目标或障碍物时减小步长,提高稳定性。
再者是合理设置参数。吸引力系数不能过大,否则会导致运动不稳定,过小则可能影响收敛速度;斥力作用范围需要适中,过大会影响目标吸引,过小可能导致碰撞风险;斥力强度需要根据障碍物的形状和密度调整,避免远处障碍物影响过大。
最后是考虑机械臂的运动学约束。对于机器人机械臂,需结合雅可比矩阵进行运动变换,保证轨迹在可行工作空间内;机械臂的奇异位形(Singularity)可能导致雅可比矩阵不可逆,此时应采取正则化处理或调整运动策略。
6. 结论
本文介绍了人工势场法的基本原理,并通过 MATLAB 实现了其在三连杆机械臂轨迹规划中的应用。人工势场法在机器人导航和轨迹规划中是一种高效且直观的方法,但其局部最小值、震荡等问题需要结合其他优化方法进行改进。实际应用中,合理调节参数、结合全局路径规划方法、优化势场函数形态,可以有效提升算法的稳定性和实用性。