引言:transform3d 是 Python 中处理三维空间变换的专用库。其核心是提供多种三维几何变换(旋转、平移、缩放)的表示和转换方法,类似于 [[numpy]] 在数值计算领域的地位,但在三维几何变换领域提供了专门的数据结构和函数。
摘要:本笔记系统介绍 transform3d 的核心功能、旋转表示、坐标变换、坐标系管理及实际应用。
关键词:三维变换, 旋转矩阵, 四元数, 欧拉角, 齐次变换
关联笔记:[[numpy|NumPy数值计算库]]
1 回忆
transform3d 完全基于 [[numpy]] 数组作为数据容器,所有函数都返回 [[numpy]] 数组,与 [[numpy]] 的线性代数函数无缝衔接。
2 重点
理解不同旋转表示法(四元数、旋转矩阵、欧拉角、轴角)之间的转换关系,以及齐次变换矩阵的构建方法。
3 犯错
混淆不同旋转表示法的坐标系约定(内旋 vs 外旋,旋转顺序);错误使用齐次变换矩阵的乘法顺序(从右向左 vs 从左向右)。
4 思考
transform3d 的“坐标系链”机制是如何管理多个坐标系之间的变换关系的?与 [[numpy]] 的广播机制有何相似之处?
1. 认识 transform3d
transform3d 是 Python 中专门处理三维空间几何变换的库,提供旋转、平移、缩放等变换的多种表示和转换方法。
核心价值:
- 专业化:专注于三维几何变换这一特定领域,提供标准化的数学工具
- 完整性:支持所有常见的旋转表示法及其相互转换
- 工程友好:遵循机器人学、计算机图形学领域的工程惯例
- 轻量高效:纯 Python 实现,依赖少,与 [[numpy]] 深度集成
2. 安装 transform3d
# 使用 pip 安装
pip install transforms3d
# 验证安装
python -c "import transforms3d as tf; print(tf.__version__)"
3. 核心模块结构
transform3d 按功能分为多个子模块,每个模块处理一种特定的变换表示或操作。
<center><b>表:transform3d 主要模块</b></center>
| 模块 | 核心功能 | 关键数据类型 |
transforms3d.axangles | 轴角表示与转换 | 轴向量 + 角度 |
transforms3d.euler | 欧拉角表示与转换 | 三个旋转角度 |
transforms3d.quaternions | 四元数表示与转换 | 四元数 (w,x,y,z) |
transforms3d.affines | 仿射变换(齐次变换矩阵) | 4×4 齐次矩阵 |
transforms3d.derivations | 数学推导相关函数 | |
transforms3d.utils | 工具函数 | |
4. 旋转表示与转换
三维旋转有多种数学表示方法,各有优缺点。transform3d 提供了它们之间的完整转换。
4.1 旋转矩阵 (Rotation Matrix)
旋转矩阵是 3×3 的正交矩阵,表示绕坐标轴的旋转。
axangle2mat(axis, angle)
- 功能:将轴角表示转换为旋转矩阵
- 参数:
axis (array-like):旋转轴单位向量 [x, y, z]
angle (float):旋转角度(弧度)
- 返回:3×3 旋转矩阵([[numpy]] 数组)
- 数学原理:罗德里格斯旋转公式
- 示例:
import numpy as np
import transforms3d.axangles as ax
# 绕Z轴旋转90度
axis = [0, 0, 1]
angle = np.pi / 2
R = ax.axangle2mat(axis, angle)
print("旋转矩阵:\n", R)
# [[ 0. -1. 0.]
# [ 1. 0. 0.]
# [ 0. 0. 1.]]
mat2axangle(R)
4.2 四元数 (Quaternions)
四元数用四个数表示三维旋转,避免万向节锁问题,适合插值。
mat2quat(R)
- 功能:旋转矩阵转四元数
- 参数:
- 返回:四元数 [w, x, y, z]
- 注意:返回单位四元数(范数为1)
- 示例:
import transforms3d.quaternions as q
quat = q.mat2quat(R)
print("四元数:", quat) # [0.707, 0, 0, 0.707]
quat2mat(quat)
quat_multiply(q1, q2)
- 功能:四元数乘法(表示旋转的组合)
- 参数:
- 返回:组合后的四元数
- 注意:四元数乘法不可交换
- 示例:
q1 = [0.707, 0, 0, 0.707] # 绕Z轴90度
q2 = [0.707, 0.707, 0, 0] # 绕X轴90度
q_combined = q.quat_multiply(q2, q1) # 先q1后q2
print("组合四元数:", q_combined)
4.3 欧拉角 (Euler Angles)
欧拉角用三个绕坐标轴的旋转角度表示方向,直观但存在万向节锁问题。
euler2mat(ai, aj, ak, axes='sxyz')
- 功能:欧拉角转旋转矩阵
- 参数:
ai, aj, ak:三个旋转角度(弧度)
axes:旋转轴顺序和旋转类型
'sxyz':静态坐标系,X→Y→Z 顺序
'rxyz':旋转坐标系,X→Y→Z 顺序
- 返回:3×3 旋转矩阵
- 示例:
import transforms3d.euler as euler
# 绕X轴30度,Y轴45度,Z轴60度
angles = [np.pi/6, np.pi/4, np.pi/3]
R_euler = euler.euler2mat(*angles, axes='sxyz')
print("欧拉角转换的旋转矩阵:\n", R_euler)
mat2euler(R, axes='sxyz')
<center><b>表:旋转表示转换函数总结</b></center>
| 转换方向 | 函数 | 所属模块 | 关键特性 |
| 轴角 → 矩阵 | axangle2mat(axis, angle) | axangles | 使用罗德里格斯公式 |
| 矩阵 → 轴角 | mat2axangle(R) | axangles | 提取旋转轴和角度 |
| 矩阵 → 四元数 | mat2quat(R) | quaternions | 返回单位四元数 |
| 四元数 → 矩阵 | quat2mat(quat) | quaternions | 四元数需归一化 |
| 欧拉角 → 矩阵 | euler2mat(ai, aj, ak, axes) | euler | 支持多种旋转顺序 |
| 矩阵 → 欧拉角 | mat2euler(R, axes) | euler | 注意万向节锁 |
| 四元数乘法 | quat_multiply(q1, q2) | quaternions | 旋转组合 |
5. 仿射变换 (Affine Transformations)
仿射变换包括旋转、平移、缩放和剪切,用 4×4 齐次变换矩阵表示。
compose(T, R, Z, S=None)
- 功能:构建齐次变换矩阵
- 参数:
T (array-like):平移向量 [tx, ty, tz]
R (array-like):3×3 旋转矩阵
Z (array-like):缩放向量 [sx, sy, sz]
S (array-like, 可选):剪切向量,默认为0
- 返回:4×4 齐次变换矩阵
- 数学形式:
M = \begin{bmatrix}
s_x R_{00} & s_y R_{01} & s_z R_{02} & t_x \\
s_x R_{10} & s_y R_{11} & s_z R_{12} & t_y \\
s_x R_{20} & s_y R_{21} & s_z R_{22} & t_z \\
0 & 0 & 0 & 1
\end{bmatrix}
- 示例:
import transforms3d.affines as aff
# 平移
translation = [1.0, 2.0, 3.0]
# 旋转(绕Z轴90度)
rotation = ax.axangle2mat([0, 0, 1], np.pi/2)
# 缩放
scale = [2.0, 1.0, 1.0] # X方向放大2倍
# 构建齐次变换矩阵
T_matrix = aff.compose(translation, rotation, scale)
print("齐次变换矩阵:\n", T_matrix)
decompose44(A44)
- 功能:分解齐次变换矩阵
- 参数:
- 返回:
(T, R, Z, S) 元组
T:平移向量
R:旋转矩阵
Z:缩放向量
S:剪切向量
- 示例:
T, R, Z, S = aff.decompose44(T_matrix)
print("平移:", T)
print("旋转矩阵:\n", R)
print("缩放:", Z)
compose_from_vectors(t, r, z)
- 功能:从向量构建变换矩阵的便捷函数
- 参数:
t:平移向量
r:旋转向量(轴角表示)
z:缩放向量
- 返回:4×4 齐次变换矩阵
- 示例:
# 使用轴角直接构建
t = [1, 2, 3]
r = [0, 0, np.pi/2] # 轴角表示:[axis_x, axis_y, axis_z, angle]?
z = [2, 1, 1]
T_simple = aff.compose_from_vectors(t, r, z)
<center><b>表:仿射变换核心函数</b></center>
| 函数 | 功能 | 输入 | 输出 | 应用场景 |
compose(T, R, Z, S) | 构建变换矩阵 | 平移、旋转、缩放、剪切 | 4×4 矩阵 | 完整的空间变换 |
decompose44(A44) | 分解变换矩阵 | 4×4 矩阵 | (T, R, Z, S) | 分析变换参数 |
compose_from_vectors(t, r, z) | 从向量构建 | 平移、旋转、缩放向量 | 4×4 矩阵 | 快速构建 |
6. 坐标系变换
6.1 点坐标变换
apply_transform(points, transform)
- 功能:将变换应用到点集上
- 参数:
points:N×3 的点坐标数组
transform:4×4 变换矩阵
- 返回:变换后的 N×3 点坐标
- 示例:
# 创建点集:立方体的8个顶点
points = np.array([
[0, 0, 0], [1, 0, 0], [1, 1, 0], [0, 1, 0],
[0, 0, 1], [1, 0, 1], [1, 1, 1], [0, 1, 1]
])
# 应用变换:平移(1,2,3),绕Z轴旋转90度
transformed_points = aff.apply_transform(points, T_matrix)
print("原始点:\n", points)
print("变换后点:\n", transformed_points)
6.2 坐标系链管理
transform3d 支持构建坐标系变换链,管理多个坐标系之间的关系。
# 示例:机器人坐标系链
# 世界坐标系 → 基座坐标系 → 工具坐标系
# 世界到基座的变换
T_world_to_base = aff.compose(
[0, 0, 0.5], # 基座高度0.5米
ax.axangle2mat([0, 0, 1], 0), # 无旋转
[1, 1, 1]
)
# 基座到工具的变换(机械臂末端)
T_base_to_tool = aff.compose(
[0.3, 0, 0.2], # 工具位置
ax.axangle2mat([0, 1, 0], np.pi/4), # 绕Y轴旋转45度
[1, 1, 1]
)
# 计算世界到工具的变换
T_world_to_tool = T_base_to_tool @ T_world_to_base # 矩阵乘法
# 或使用 numpy 的 dot 函数
# T_world_to_tool = np.dot(T_base_to_tool, T_world_to_base)
print("世界→工具变换矩阵:\n", T_world_to_tool)
7. 插值功能
transform3d 提供旋转和平移的插值方法,用于动画和轨迹规划。
7.1 旋转插值
quat_slerp(q0, q1, t)
- 功能:四元数球面线性插值
- 参数:
q0, q1:起始和结束四元数
t:插值参数 [0, 1]
- 返回:插值后的四元数
- 数学原理:沿四维球面的最短路径插值
- 示例:
# 定义起始和结束姿态
q_start = [1, 0, 0, 0] # 无旋转
q_end = [0.707, 0, 0, 0.707] # 绕Z轴90度
# 在中间时刻的插值
t = 0.5
q_mid = q.quat_slerp(q_start, q_end, t)
print(f"t={t}时的四元数:", q_mid)
# 生成平滑轨迹
times = np.linspace(0, 1, 10)
trajectory = [q.quat_slerp(q_start, q_end, t) for t in times]
7.2 平移插值
平移插值通常使用线性插值,可直接用 [[numpy]] 实现:
# 线性插值示例
start_pos = np.array([0, 0, 0])
end_pos = np.array([1, 2, 3])
t = 0.5
mid_pos = start_pos + t * (end_pos - start_pos)
print(f"中间位置: {mid_pos}")
# 生成轨迹
positions = np.linspace(start_pos, end_pos, 10)
print("轨迹点:\n", positions)
8. 实际应用示例
8.1 机器人运动学
import numpy as np
import transforms3d as tf
def forward_kinematics(joint_angles, link_lengths):
"""
计算2D平面机械臂的正运动学
joint_angles: 关节角度列表 [θ1, θ2, ...]
link_lengths: 连杆长度列表 [l1, l2, ...]
返回末端执行器位置
"""
T = np.eye(4) # 初始变换矩阵
for angle, length in zip(joint_angles, link_lengths):
# 每个关节的变换:旋转 + 平移
R = tf.axangles.axangle2mat([0, 0, 1], angle) # 绕Z轴旋转
t = [length, 0, 0] # 沿X轴平移
# 构建连杆变换矩阵
T_link = tf.affines.compose(t, R, [1, 1, 1])
# 累积变换
T = T @ T_link
# 提取末端位置
end_position = T[:3, 3]
return end_position
# 示例:2连杆机械臂
angles = [np.pi/4, np.pi/6] # 45度和30度
lengths = [1.0, 0.8]
position = forward_kinematics(angles, lengths)
print(f"末端位置: {position}")
8.2 相机标定
def project_3d_to_2d(points_3d, camera_matrix, pose_matrix):
"""
将3D点投影到2D图像平面
points_3d: N×3 世界坐标点
camera_matrix: 3×3 相机内参矩阵
pose_matrix: 4×4 相机外参矩阵(世界到相机)
返回: N×2 图像坐标
"""
# 将点转换为齐次坐标
points_homo = np.hstack([points_3d, np.ones((len(points_3d), 1))])
# 世界坐标 → 相机坐标
points_camera = (pose_matrix @ points_homo.T).T
# 相机坐标 → 图像坐标
points_image_homo = (camera_matrix @ points_camera[:, :3].T).T
# 齐次坐标归一化
points_image = points_image_homo[:, :2] / points_image_homo[:, 2:3]
return points_image
# 示例:简单的投影
points_3d = np.array([[1, 0, 2], [0, 1, 3], [-1, 0, 4]])
camera_matrix = np.array([[500, 0, 320], [0, 500, 240], [0, 0, 1]])
pose_matrix = tf.affines.compose([0, 0, 0], np.eye(3), [1, 1, 1])
points_2d = project_3d_to_2d(points_3d, camera_matrix, pose_matrix)
print("投影后的2D点:\n", points_2d)
8.3 三维模型变换
def transform_mesh(vertices, transform_matrix):
"""
变换三维网格模型
vertices: N×3 顶点坐标
transform_matrix: 4×4 变换矩阵
返回: 变换后的顶点
"""
# 转换为齐次坐标
vertices_homo = np.hstack([vertices, np.ones((len(vertices), 1))])
# 应用变换
transformed_homo = (transform_matrix @ vertices_homo.T).T
# 转换回3D坐标
transformed_vertices = transformed_homo[:, :3]
return transformed_vertices
# 示例:模型的旋转和平移
# 假设 vertices 是从文件读取的网格顶点
# transform = 绕Y轴旋转90度 + 平移(1,0,0)
R = tf.axangles.axangle2mat([0, 1, 0], np.pi/2)
t = [1, 0, 0]
transform = tf.affines.compose(t, R, [1, 1, 1])
# transformed_vertices = transform_mesh(vertices, transform)
9. 与 [[numpy]] 的集成
transform3d 深度集成 [[numpy]],所有函数都遵循 [[numpy]] 的约定:
- 数据容器:所有函数都接受并返回 [[numpy]] 数组
- 广播支持:支持 [[numpy]] 的广播机制
- 线性代数:与
np.linalg 函数无缝协作
- 性能优化:利用 [[numpy]] 的向量化操作
# 示例:批量处理多个变换
import numpy as np
import transforms3d.axangles as ax
# 批量轴角数据:N×4,每行是 [axis_x, axis_y, axis_z, angle]
batch_axangles = np.array([
[0, 0, 1, np.pi/4],
[0, 1, 0, np.pi/3],
[1, 0, 0, np.pi/6]
])
# 批量转换为旋转矩阵
batch_rotations = np.array([ax.axangle2mat(row[:3], row[3])
for row in batch_axangles])
print("批量旋转矩阵形状:", batch_rotations.shape) # (3, 3, 3)
# 批量点变换
batch_points = np.random.randn(10, 3) # 10个点
batch_transforms = np.array([tf.affines.compose([i, 0, 0], np.eye(3), [1,1,1])
for i in range(5)]) # 5个变换
# 应用每个变换到所有点
transformed_all = np.array([tf.affines.apply_transform(batch_points, T)
for T in batch_transforms])
print("变换后点集形状:", transformed_all.shape) # (5, 10, 3)
10. 常见问题与解决方案
<center><b>表:transform3d 常见问题</b></center>
| 问题 | 原因 | 解决方案 |
| 旋转方向错误 | 坐标系约定不一致(左手系 vs 右手系) | 统一使用右手坐标系,检查旋转方向 |
| 万向节锁 | 欧拉角表示在特定角度失去自由度 | 使用四元数或旋转矩阵代替欧拉角 |
| 变换顺序错误 | 矩阵乘法顺序错误(从右向左) | 明确变换顺序:T_final = T3 @ T2 @ T1 |
| 缩放影响旋转 | 缩放不均匀导致旋转轴变化 | 先旋转后缩放,或使用均匀缩放 |
| 数值误差累积 | 多次变换后矩阵不再正交 | 定期正交化旋转矩阵 |
11. 性能优化建议
- 批量处理:利用 [[numpy]] 的向量化操作,避免循环
- 选择合适表示:根据操作选择最合适的旋转表示
- 预计算:对于固定变换,预计算变换矩阵
- 避免重复转换:在一种表示下完成所有可能操作
- 使用视图:对于大数组,使用 [[numpy]] 的视图而非拷贝
# 性能对比示例
import time
import numpy as np
import transforms3d.quaternions as q
# 方法1:循环转换
quats = np.random.randn(10000, 4)
quats = quats / np.linalg.norm(quats, axis=1, keepdims=True) # 归一化
start = time.time()
mats_loop = np.zeros((10000, 3, 3))
for i in range(10000):
mats_loop[i] =q.quat2mat(quats[i])
loop_time = time.time() - start
# 方法2:向量化转换
start = time.time()
mats_vectorized = np.array([q.quat2mat(q) for q in quats])
vectorized_time = time.time() - start
print(f"循环转换时间: {loop_time:.4f}秒")
print(f"向量化转换时间: {vectorized_time:.4f}秒")
print(f"加速比: {loop_time/vectorized_time:.1f}倍")
12. 习题
<center><b><h1>简答题</h1></b></center>
axangle2mat 和 euler2mat 函数的主要区别是什么?在什么场景下应优先使用轴角表示?
- 四元数乘法
quat_multiply(q1, q2) 的顺序意义是什么?如果先执行旋转A再执行旋转B,应该如何组合四元数?
- 解释齐次变换矩阵
compose(T, R, Z, S) 中各个参数的含义。为什么需要4×4矩阵而不是3×3矩阵?
- 什么是万向节锁(Gimbal Lock)?为什么四元数可以避免这个问题?
- 在机器人坐标系链中,从世界坐标系到末端执行器的变换矩阵应该如何计算?为什么矩阵乘法顺序很重要?
<center><b><h1>代码题</h1></b></center>
创建一个函数,将欧拉角(绕X、Y、Z轴的旋转角度)转换为四元数,再转换为旋转矩阵,最后验证转换的正确性。
def euler_to_quat_to_matrix(roll, pitch, yaw):
"""
将欧拉角转换为四元数,再转换为旋转矩阵
roll: 绕X轴旋转角度(弧度)
pitch: 绕Y轴旋转角度(弧度)
yaw: 绕Z轴旋转角度(弧度)
返回:旋转矩阵,并验证转换一致性
"""
pass
实现一个简单的机械臂逆运动学求解器(2D平面):
def inverse_kinematics_2d(target_position, link_lengths):
"""
计算2连杆机械臂的逆运动学
target_position: 目标位置 [x, y]
link_lengths: 连杆长度 [l1, l2]
返回:关节角度 [θ1, θ2](两个解)
"""
# 提示:使用余弦定理
pass
创建一个相机姿态估计函数:
def estimate_camera_pose(image_points, world_points, camera_matrix):
"""
通过2D-3D点对应估计相机姿态
image_points: N×2 图像坐标
world_points: N×3 世界坐标
camera_matrix: 3×3 相机内参矩阵
返回:4×4 相机姿态矩阵(世界到相机)
"""
# 提示:使用PnP问题求解,可简化使用最小二乘法
pass
实现三维模型的批量变换:
def batch_transform_meshes(vertices_list, transforms):
"""
批量变换多个三维模型
vertices_list: 顶点列表,每个元素是N×3的顶点数组
transforms: 变换矩阵列表,每个元素是4×4变换矩阵
返回:变换后的顶点列表
"""
# 要求:使用向量化操作,避免循环
pass
13. 扩展学习
13.1 相关库推荐
<center><b>表:与transform3d相关的Python库</b></center>
| 库名 | 主要功能 | 与transform3d的关系 |
| [[numpy]] | 数值计算基础 | transform3d的底层依赖 |
| SciPy | 科学计算 | 提供更高级的优化和插值函数 |
| Open3D | 三维数据处理 | 包含点云、网格的变换操作 |
| PyBullet | 物理仿真 | 用于机器人动力学仿真 |
| ROS (Robot Operating System) | 机器人框架 | 使用类似的变换表示 |
13.2 进阶主题
- 李群与李代数:旋转矩阵属于SO(3)李群,四元数属于SU(2)李群
- 姿态估计优化:使用最小二乘法或优化算法求解最优变换
- 卡尔曼滤波:在动态系统中估计和预测姿态
- SLAM(同时定位与地图构建):结合transform3d进行位姿估计
- 深度学习中的三维变换:在神经网络中集成几何变换层
13.3 最佳实践
- 统一坐标系:在整个项目中保持一致的坐标系约定
- 文档化变换:记录每个变换矩阵的含义和坐标系关系
- 验证变换:定期检查变换矩阵的正交性和行列式
- 性能监控:对于实时应用,监控变换计算的时间开销
- 错误处理:添加适当的异常处理,如奇异值分解失败的情况
总结:transform3d 是一个专门处理三维空间变换的Python库,它提供了完整的旋转表示转换、坐标变换和插值功能。与 [[numpy]] 深度集成,适合机器人学、计算机视觉和计算机图形学等领域。通过掌握其核心函数和最佳实践,可以高效地处理各种三维几何变换问题。