【2】数学基础
本文介绍了机器人运动学中的正向运动学(FK)原理及实现方法。首先解释了运动学正解的概念,即通过已知关节角度求解末端执行器的位姿。重点讨论了齐次变换矩阵法,包括2D/3D旋转矩阵和平移矩阵的数学表达。文章还提供了一个Python实现示例,展示了如何通过齐次变换矩阵计算机械臂各关节的变换关系。代码中包含了旋转矩阵、平移矩阵的构建方法,以及基于URDF参数的机械臂正向运动学求解器实现。
专栏目录
[1]URDF初析
文章目录
前言
上一章写到UEDF基本语法,接下来黑马程序员讲了一些基础数学知识。以下仅个人看法,望请批评指正。数学知识板块分为运动学正解、运动学逆解两个部分,涉及知识点包括矩阵运算、三角函数、微积分。个人认为只能说有个大体框架,具体细节还需要经由学习、练习、考试、运用,诸多阶段。故而该部分为浅析框架,以示自明。
一、FK(运动学正解)
解释:简单来说,就是建立从关节空间到任务空间的映射。实际例子即为:给定一组已知的关节角度,求解机器人末端执行器在任务空间中的确切位姿。
解法分为:(1)齐次变换矩阵法 (2)解析几何与三角函数法 以下重点关注齐次变换矩阵法,解析几何与三角函数法在后续有更深入的理解再增加。
1.齐次变换矩阵法
1.1 齐次旋转矩阵:
(1)2D 齐次旋转矩阵
绕原点逆时针旋转 θ,齐次坐标
R 2 D ( θ ) = [ cos θ − sin θ 0 sin θ cos θ 0 0 0 1 ] R_{2D}(\theta) = \begin{bmatrix} \cos\theta & -\sin\theta & 0 \\ \sin\theta & \cos\theta & 0 \\ 0 & 0 & 1 \end{bmatrix} R2D(θ)=
cosθsinθ0−sinθcosθ0001
变换方程为:
[ cos θ − sin θ 0 sin θ cos θ 0 0 0 1 ] [ x y 1 ] \begin{bmatrix} \cos\theta & -\sin\theta & 0 \\ \sin\theta & \cos\theta & 0\\ 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} x \\ y \\ 1 \end{bmatrix}
cosθsinθ0−sinθcosθ0001
xy1
解得:
x ′ = x cos θ − y sin θ y ′ = x sin θ + y cos θ \begin{aligned} x' &= x\cos\theta - y\sin\theta \\ y' &= x\sin\theta + y\cos\theta \end{aligned} x′y′=xcosθ−ysinθ=xsinθ+ycosθ
其中,第一行表示移动后的x坐标,第二行表示移动后的y坐标,第三行为固定。
(1)3D 齐次旋转矩阵
然而,机器人的运动空间为三维,为此,黑马给出的解释是,将坐标系投影到各个面,再运用二维齐次旋转矩阵,即可求解。
我通过询问AI,它给出了3D 齐次旋转矩阵,仅做记录,有更深的理解了再做勘误解释:
- 绕 x 轴旋转 θ
R x ( θ ) = [ 1 0 0 0 0 cos θ − sin θ 0 0 sin θ cos θ 0 0 0 0 1 ] R_x(\theta) = \begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & \cos\theta & -\sin\theta & 0 \\ 0 & \sin\theta & \cos\theta & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} Rx(θ)= 10000cosθsinθ00−sinθcosθ00001 - 绕 y 轴旋转 θ
R y ( θ ) = [ cos θ 0 sin θ 0 0 1 0 0 − sin θ 0 cos θ 0 0 0 0 1 ] R_y(\theta) = \begin{bmatrix} \cos\theta & 0 & \sin\theta & 0 \\ 0 & 1 & 0 & 0 \\ -\sin\theta & 0 & \cos\theta & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} Ry(θ)= cosθ0−sinθ00100sinθ0cosθ00001 - 绕 z 轴旋转 θ
R z ( θ ) = [ cos θ − sin θ 0 0 sin θ cos θ 0 0 0 0 1 0 0 0 0 1 ] R_z(\theta) = \begin{bmatrix} \cos\theta & -\sin\theta & 0 & 0 \\ \sin\theta & \cos\theta & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} Rz(θ)= cosθsinθ00−sinθcosθ0000100001
1.2 齐次平移矩阵:
[ 1 0 t x 0 1 t y 0 0 1 ] \begin{bmatrix} 1& 0 & tx \\ 0& 1 & ty \\ 0& 0 & 1 \\ \end{bmatrix}
100010txty1
则最终,一个2D位移即可表示为:
[ cos θ − sin θ t x sin θ cos θ t y 0 0 1 ] [ x y 1 ] \begin{bmatrix} \cos\theta & -\sin\theta & tx \\ \sin\theta & \cos\theta & ty \\ 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} x \\ y \\ 1 \end{bmatrix}
cosθsinθ0−sinθcosθ0txty1
xy1
3.代码示例
import numpy as np
import math
class GenkiArmForwardKinematics:
def __init__(self):
"""
初始化Genki机械臂的正向运动学求解器
根据URDF文件定义的DH参数和关节配置
"""
# 关节偏移量 (来自URDF文件的origin标签)
self.joint_offsets = [
np.array([-0.013, 0, 0.0265]), # Joint 1 (腰部旋转)
np.array([0.081, 0, 0]), # Joint 2 (大臂控制)
np.array([0, 0, 0.118]), # Joint 3 (小臂控制)
np.array([0, 0, 0.118]), # Joint 4 (腕部控制)
np.array([0, 0, 0.0653]), # Joint 5 (腕部旋转)
np.array([0, -0.0132, 0.021]) # Joint 6 (夹爪控制)
]
# 关节轴向量 (来自URDF文件的axis标签)
self.joint_axes = [
np.array([1, 0, 0]), # Joint 1: x轴旋转
np.array([0, 1, 0]), # Joint 2: y轴旋转
np.array([0, 1, 0]), # Joint 3: y轴旋转
np.array([0, 1, 0]), # Joint 4: y轴旋转
np.array([0, 0, 1]), # Joint 5: z轴旋转
np.array([1, 0, 0]) # Joint 6: x轴旋转
]
# 关节角度偏移 (来自URDF文件的rpy标签)
self.joint_rpy_offsets = [
np.array([0, -1.57, 0]), # Joint 1
np.array([0, 1.57, 0]), # Joint 2
np.array([0, 0, 0]), # Joint 3
np.array([0, 0, 0]), # Joint 4
np.array([0, 0, 0]), # Joint 5
np.array([0, 0, 0]) # Joint 6
]
def rotation_matrix_x(self, theta):
"""绕X轴旋转的变换矩阵"""
cos_theta = math.cos(theta)
sin_theta = math.sin(theta)
return np.array([
[1, 0, 0, 0],
[0, cos_theta, -sin_theta, 0],
[0, sin_theta, cos_theta, 0],
[0, 0, 0, 1]
])
def rotation_matrix_y(self, theta):
"""绕Y轴旋转的变换矩阵"""
cos_theta = math.cos(theta)
sin_theta = math.sin(theta)
return np.array([
[cos_theta, 0, sin_theta, 0],
[0, 1, 0, 0],
[-sin_theta, 0, cos_theta, 0],
[0, 0, 0, 1]
])
def rotation_matrix_z(self, theta):
"""绕Z轴旋转的变换矩阵"""
cos_theta = math.cos(theta)
sin_theta = math.sin(theta)
return np.array([
[cos_theta, -sin_theta, 0, 0],
[sin_theta, cos_theta, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1]
])
def translation_matrix(self, x, y, z):
"""平移变换矩阵"""
return np.array([
[1, 0, 0, x],
[0, 1, 0, y],
[0, 0, 1, z],
[0, 0, 0, 1]
])
def get_joint_transform(self, joint_index, angle):
"""
计算单个关节的齐次变换矩阵
"""
# 获取关节参数
offset = self.joint_offsets[joint_index]
axis = self.joint_axes[joint_index]
rpy_offset = self.joint_rpy_offsets[joint_index]
# 构建变换矩阵
# 1. 先应用RPY偏移
T_rpy = np.eye(4)
if rpy_offset[0] != 0: # roll
T_rpy = T_rpy @ self.rotation_matrix_x(rpy_offset[0])
if rpy_offset[1] != 0: # pitch
T_rpy = T_rpy @ self.rotation_matrix_y(rpy_offset[1])
if rpy_offset[2] != 0: # yaw
T_rpy = T_rpy @ self.rotation_matrix_z(rpy_offset[2])
# 2. 应用关节旋转
T_joint = np.eye(4)
if joint_index == 0: # Joint 1: 绕X轴旋转
T_joint = self.rotation_matrix_x(angle)
elif joint_index in [1, 2, 3]: # Joints 2,3,4: 绕Y轴旋转
T_joint = self.rotation_matrix_y(angle)
elif joint_index == 4: # Joint 5: 绕Z轴旋转
T_joint = self.rotation_matrix_z(angle)
elif joint_index == 5: # Joint 6: 绕X轴旋转
T_joint = self.rotation_matrix_x(angle)
# 3. 应用平移
T_translate = self.translation_matrix(offset[0], offset[1], offset[2])
# 组合变换: RPY偏移 -> 关节旋转 -> 平移
T_total = T_translate @ T_rpy @ T_joint
return T_total
def forward_kinematics(self, joint_angles):
"""
正向运动学求解
参数:
joint_angles: 包含6个关节角度的列表或数组,单位为弧度
[j1, j2, j3, j4, j5, j6]
返回:
末端执行器的XYZ坐标 [x, y, z]
"""
if len(joint_angles) != 6:
raise ValueError("必须提供6个关节角度")
# 初始化总的变换矩阵为单位矩阵
T_total = np.eye(4)
# 依次计算每个关节的变换并累乘
for i in range(6):
T_joint = self.get_joint_transform(i, joint_angles[i])
T_total = T_total @ T_joint
print(f"Joint {i+1} 变换矩阵:")
print(T_joint)
print()
# 提取末端位置坐标
end_effector_position = T_total[:3, 3]
return end_effector_position
def demo(self):
"""演示函数"""
print("=== Genki机械臂正向运动学演示 ===\n")
# 示例关节角度 (弧度)
test_angles = [0, 0, 0, 0, 0, 0] # 所有关节在零位
print(f"输入关节角度: {test_angles}")
try:
position = self.forward_kinematics(test_angles)
print(f"末端位置坐标: x={position[0]:.4f}, y={position[1]:.4f}, z={position[2]:.4f}")
except Exception as e:
print(f"计算出错: {e}")
def main():
# 创建运动学求解器实例
arm = GenkiArmForwardKinematics()
# 运行演示
arm.demo()
# 交互式测试
print("\n=== 交互式测试 ===")
while True:
try:
print("\n请输入6个关节角度(弧度),用空格分隔:")
print("例如: 0 0.5 -0.3 0.2 -0.1 0")
user_input = input("关节角度: ")
if user_input.lower() == 'quit':
break
angles = list(map(float, user_input.split()))
if len(angles) != 6:
print("错误: 必须输入6个角度值")
continue
position = arm.forward_kinematics(angles)
print(f"\n末端位置坐标:")
print(f" X: {position[0]:.6f} 米")
print(f" Y: {position[1]:.6f} 米")
print(f" Z: {position[2]:.6f} 米")
print(f" 距离原点: {np.linalg.norm(position):.6f} 米")
except ValueError:
print("错误: 请输入有效的数字")
except KeyboardInterrupt:
print("\n程序退出")
break
except Exception as e:
print(f"计算错误: {e}")
if __name__ == "__main__":
main()
二、IK(运动学逆解)
运动学逆解即为:从任务空间到关节空间。然而对比与FK的唯一性,确定性,IK可能无解、单解或多解。其特性有:
- 多解性:同一目标,可能存在多种姿态。这个比较好理解,够一个东西,可以有多条线路,机器臂有多种抬法。
- 无解性:这要求目标必须在工作空间内(可以这么理解:机器人的胳膊长度不够,够不到,无解,机器人的肢体跟人一样,有盲区,比如人的舌头舔不到胳膊肘,这也是无解)
- 奇异性:特定位形丢失自由度,这很危险,需要主动规避。这个可以理解为,手臂伸直了,往肩这个方向,向里就收缩不动,这个方向就是奇异点。用数学解释即:雅可比矩阵降秩 → 不可逆 → 逆解算不出 / 无穷大 / 爆炸
"""
Genki机械臂逆向运动学 - 实用示例版本
包含工作空间检查和合理的目标位置示例
"""
import numpy as np
import math
from scipy.optimize import minimize
def inverse_kinematics(target_xyz):
"""
Genki机械臂逆向运动学求解
参数:
target_xyz: [x, y, z] 目标位置坐标(米)
返回:
[j1, j2, j3, j4, j5, j6] 关节角度(度数)
"""
def fk(joint_angles_deg):
"""正向运动学"""
angles = [math.radians(a) for a in joint_angles_deg]
joints = [
{'axis': 'x', 'offset': [-0.013, 0, 0.0265], 'rpy': [0, -1.57, 0]},
{'axis': 'y', 'offset': [0.081, 0, 0], 'rpy': [0, 1.57, 0]},
{'axis': 'y', 'offset': [0, 0, 0.118], 'rpy': [0, 0, 0]},
{'axis': 'y', 'offset': [0, 0, 0.118], 'rpy': [0, 0, 0]},
{'axis': 'z', 'offset': [0, 0, 0.0653], 'rpy': [0, 0, 0]},
{'axis': 'x', 'offset': [0, -0.0132, 0.021], 'rpy': [0, 0, 0]}
]
def rot(axis, angle):
c, s = math.cos(angle), math.sin(angle)
if axis == 'x': return np.array([[1,0,0],[0,c,-s],[0,s,c]])
elif axis == 'y': return np.array([[c,0,s],[0,1,0],[-s,0,c]])
elif axis == 'z': return np.array([[c,-s,0],[s,c,0],[0,0,1]])
T = np.eye(4)
for angle, joint in zip(angles, joints):
# RPY偏移
R_rpy = np.eye(3)
if joint['rpy'][0] != 0: R_rpy = R_rpy @ rot('x', joint['rpy'][0])
if joint['rpy'][1] != 0: R_rpy = R_rpy @ rot('y', joint['rpy'][1])
if joint['rpy'][2] != 0: R_rpy = R_rpy @ rot('z', joint['rpy'][2])
# 关节旋转
R_joint = rot(joint['axis'], angle)
R = R_rpy @ R_joint
# 齐次变换
Ti = np.eye(4)
Ti[:3,:3] = R
Ti[:3,3] = joint['offset']
T = T @ Ti
return T[:3, 3]
def objective(joint_angles, target):
"""目标函数"""
pos = fk(joint_angles)
error = np.array(pos) - np.array(target)
return np.sum(error**2)
# 关节限制 (度数)
bounds = [(-90, 90), (-90, 90), (-90, 90), (-90, 90), (-90, 90), (0, 90)]
# 多次初始猜测以提高成功率
initial_guesses = [
[0, 0, 0, 0, 0, 0],
[30, -30, 30, -30, 30, 0],
[-30, 30, -30, 30, -30, 0],
[45, -45, 45, -45, 45, 0]
]
best_result = None
best_error = float('inf')
for guess in initial_guesses:
result = minimize(
fun=objective,
x0=guess,
args=(target_xyz,),
method='L-BFGS-B',
bounds=bounds,
options={'disp': False}
)
final_pos = fk(result.x)
error = np.linalg.norm(np.array(final_pos) - np.array(target_xyz))
if error < best_error:
best_error = error
best_result = result
if best_result is not None:
final_angles = best_result.x.tolist()
final_position = fk(final_angles)
return {
'joint_angles': final_angles,
'final_position': final_position.tolist(),
'position_error': best_error,
'success': best_error < 0.01 # 1cm以内认为成功
}
else:
return {'success': False, 'message': '无法找到解'}
# 实用示例函数
def solve_position(x, y, z):
"""求解指定位置的关节角度"""
print(f"\n🎯 目标位置: X={x:.3f}m, Y={y:.3f}m, Z={z:.3f}m")
result = inverse_kinematics([x, y, z])
if result['success']:
print("✅ 求解成功!")
print("关节角度:")
joint_names = ['腰部旋转', '大臂控制', '小臂控制', '腕部控制', '腕部旋转', '夹爪控制']
for i, (name, angle) in enumerate(zip(joint_names, result['joint_angles']), 1):
print(f" {i}. {name}: {angle:.2f}°")
final_pos = result['final_position']
print(f"\n📍 实际到达位置:")
print(f" X: {final_pos[0]:.4f}m")
print(f" Y: {final_pos[1]:.4f}m")
print(f" Z: {final_pos[2]:.4f}m")
print(f"📏 位置误差: {result['position_error']:.4f}m")
else:
print("❌ 求解失败 - 目标位置可能超出工作空间")
return result
def demo_common_positions():
"""演示常见工作位置"""
print("=== Genki机械臂逆向运动学演示 ===")
print("展示机械臂可达的典型工作位置\n")
# 测试位置集合
test_positions = [
# 基本位置
(0.0, 0.0, 0.4), # 正前方中等高度
(0.1, 0.0, 0.35), # 右侧中等高度
(-0.1, 0.0, 0.35), # 左侧中等高度
(0.0, 0.1, 0.3), # 前方较高位置
(0.0, -0.1, 0.25), # 后方较低位置
# 工作区域位置
(0.05, 0.05, 0.32), # 右前方
(-0.05, 0.05, 0.32), # 左前方
(0.08, -0.03, 0.28), # 右后方
(-0.08, -0.03, 0.28), # 左后方
# 边界测试
(0.12, 0.0, 0.2), # 右侧边界
(-0.12, 0.0, 0.2), # 左侧边界
(0.0, 0.15, 0.25), # 前方边界
]
successful = 0
total = len(test_positions)
for x, y, z in test_positions:
result = solve_position(x, y, z)
if result['success']:
successful += 1
print(f"\n📊 求解统计:")
print(f" 测试位置总数: {total}")
print(f" 成功求解数量: {successful}")
print(f" 成功率: {successful/total*100:.1f}%")
def interactive_solver():
"""交互式求解器"""
print("\n=== 交互式位置求解器 ===")
print("请输入目标位置坐标 (米)")
print("格式: X Y Z (用空格分隔)")
print("例如: 0.1 0.05 0.3")
print("输入 'demo' 查看演示,'quit' 退出\n")
while True:
try:
user_input = input("目标位置: ").strip()
if user_input.lower() == 'quit':
print("👋 程序退出")
break
elif user_input.lower() == 'demo':
demo_common_positions()
continue
coords = list(map(float, user_input.split()))
if len(coords) != 3:
print("❌ 错误: 请提供3个坐标值")
continue
x, y, z = coords
solve_position(x, y, z)
print("-" * 50)
except ValueError:
print("❌ 输入格式错误,请输入数字")
except KeyboardInterrupt:
print("\n👋 程序被用户中断")
break
except Exception as e:
print(f"❌ 计算错误: {e}")
if __name__ == "__main__":
# 运行演示
demo_common_positions()
# 可选:启动交互式求解器
# interactive_solver()
三、PID粗析
最后我们来简单了解一下PID控制器。首先,为了防止机器人出现“帕金森”,我们不仅要有大脑,还要用控制器。为此,诞生了Bang-Bang控制器和PID控制器,然而,Bang-Bang控制器会导致剧烈波动,所以目前主要用PID控制器。
P(Proportional)比例(永远到不了精确位置,到了,如果只有P控制,会立刻停止工作,然后又算误差,又开始工作)、I(Integral)积分、D(Derivative)微分
标准PID公式为:
u ( t ) = K p e ( t ) + K i ∫ 0 t e ( τ ) d τ + K d d e ( t ) d t u(t) = K_p e(t) + K_i \int_0^t e(\tau)d\tau + K_d \frac{de(t)}{dt} u(t)=Kpe(t)+Ki∫0te(τ)dτ+Kddtde(t)
其中:
e(t):误差 = 目标值 - 当前值
Kp:比例系数
Ki:积分系数
Kd:微分系数
在这个阶段,我暂且用这个例子辅助理解:
eg. 机器人关节从 0° 转到 100°
- 仅P 比例控制时,控制器会根据误差大小出力,离目标越远劲越大、越近劲越小,动作很快,但永远差一点到不了目标,存在稳定静差;
- 加上 I 积分控制后,只要还有误差,积分就会不断累加出力,把最后一点偏差慢慢补到 0,能精准到达目标位置,可是容易冲过头、来回晃动;
- 再加入 D 微分控制后,它能根据误差变化速度提前预判、起到刹车减震作用,既保留了 P 的快速、I 的精准,又避免了冲击与抖动,最终实现又快、又准、又平稳的控制效果,这就是完整 PID 控制器的作用。
个人感觉可以从比例、积分、微分的角度进一步在实际应用中细细品味。
总结
初编2026/2/15。个人对自己的要求是,这一章是一个大致框架,明确了后续深度研究的基础,后续有更多理解了再补充吧。本人才疏学浅,敬请批评指正,欢迎大家交流看法。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐

所有评论(0)