akai-transform 0.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- akai_transform-0.0.1/CLAUDE.md +301 -0
- akai_transform-0.0.1/MANIFEST.in +5 -0
- akai_transform-0.0.1/PKG-INFO +127 -0
- akai_transform-0.0.1/README.md +100 -0
- akai_transform-0.0.1/akai_transform/__init__.py +31 -0
- akai_transform-0.0.1/akai_transform/const.py +51 -0
- akai_transform-0.0.1/akai_transform/log_quat.py +84 -0
- akai_transform-0.0.1/akai_transform/quat.py +447 -0
- akai_transform-0.0.1/akai_transform/se3.py +260 -0
- akai_transform-0.0.1/akai_transform/tf2d.py +170 -0
- akai_transform-0.0.1/akai_transform/tf3d.py +742 -0
- akai_transform-0.0.1/akai_transform.egg-info/PKG-INFO +127 -0
- akai_transform-0.0.1/akai_transform.egg-info/SOURCES.txt +42 -0
- akai_transform-0.0.1/akai_transform.egg-info/dependency_links.txt +1 -0
- akai_transform-0.0.1/akai_transform.egg-info/requires.txt +3 -0
- akai_transform-0.0.1/akai_transform.egg-info/top_level.txt +3 -0
- akai_transform-0.0.1/example//347/251/272/351/227/264/345/217/230/346/215/242/345/237/272/347/241/200/346/241/210/344/276/213(akai_transform)/01.2D/347/251/272/351/227/264/345/217/230/346/215/242/01_tf2d_/345/237/272/347/241/200/346/265/213/350/257/225.py +112 -0
- akai_transform-0.0.1/example//347/251/272/351/227/264/345/217/230/346/215/242/345/237/272/347/241/200/346/241/210/344/276/213(akai_transform)/01.2D/347/251/272/351/227/264/345/217/230/346/215/242/02_tf2d_/345/260/217/344/271/214/351/276/237/345/217/257/350/247/206/345/214/226.py +170 -0
- akai_transform-0.0.1/example//347/251/272/351/227/264/345/217/230/346/215/242/345/237/272/347/241/200/346/241/210/344/276/213(akai_transform)/02.3D/347/251/272/351/227/264/345/217/230/346/215/242(/345/237/272/347/241/200)/01_tf3d_/346/227/213/350/275/254/347/237/251/351/230/265/344/270/216/345/271/263/347/247/273/347/237/251/351/230/265.py +175 -0
- akai_transform-0.0.1/example//347/251/272/351/227/264/345/217/230/346/215/242/345/237/272/347/241/200/346/241/210/344/276/213(akai_transform)/02.3D/347/251/272/351/227/264/345/217/230/346/215/242(/345/237/272/347/241/200)/02_tf3d_/346/227/213/350/275/254/345/220/221/351/207/217.py +86 -0
- akai_transform-0.0.1/example//347/251/272/351/227/264/345/217/230/346/215/242/345/237/272/347/241/200/346/241/210/344/276/213(akai_transform)/02.3D/347/251/272/351/227/264/345/217/230/346/215/242(/345/237/272/347/241/200)/03_tf3d_/345/233/233/345/205/203/346/225/260/350/275/254/346/215/242.py +83 -0
- akai_transform-0.0.1/example//347/251/272/351/227/264/345/217/230/346/215/242/345/237/272/347/241/200/346/241/210/344/276/213(akai_transform)/02.3D/347/251/272/351/227/264/345/217/230/346/215/242(/345/237/272/347/241/200)/04_tf3d_XYZRPY.py +73 -0
- akai_transform-0.0.1/example//347/251/272/351/227/264/345/217/230/346/215/242/345/237/272/347/241/200/346/241/210/344/276/213(akai_transform)/02.3D/347/251/272/351/227/264/345/217/230/346/215/242(/345/237/272/347/241/200)/05_tf3d_Open3D/345/217/257/350/247/206/345/214/226.py +154 -0
- akai_transform-0.0.1/example//347/251/272/351/227/264/345/217/230/346/215/242/345/237/272/347/241/200/346/241/210/344/276/213(akai_transform)/03.3D/347/251/272/351/227/264/345/217/230/346/215/242(/350/277/233/351/230/266)/01_/344/277/256/346/255/243/347/275/227/345/276/267/351/207/214/346/240/274/346/226/257/345/217/202/346/225/260(MRP).py +64 -0
- akai_transform-0.0.1/example//347/251/272/351/227/264/345/217/230/346/215/242/345/237/272/347/241/200/346/241/210/344/276/213(akai_transform)/03.3D/347/251/272/351/227/264/345/217/230/346/215/242(/350/277/233/351/230/266)/02_/345/233/233/345/205/203/346/225/260/345/237/272/347/241/200/350/277/220/347/256/227.py +161 -0
- akai_transform-0.0.1/example//347/251/272/351/227/264/345/217/230/346/215/242/345/237/272/347/241/200/346/241/210/344/276/213(akai_transform)/03.3D/347/251/272/351/227/264/345/217/230/346/215/242(/350/277/233/351/230/266)/03_/345/257/271/346/225/260/345/233/233/345/205/203/346/225/260/350/275/254/346/215/242.py +418 -0
- akai_transform-0.0.1/example//347/251/272/351/227/264/345/217/230/346/215/242/345/237/272/347/241/200/346/241/210/344/276/213(akai_transform)/03.3D/347/251/272/351/227/264/345/217/230/346/215/242(/350/277/233/351/230/266)/04_/345/244/232/351/200/224/345/276/204/347/202/271/344/275/215/347/275/256/345/247/277/346/200/201/346/217/222/345/200/274_Slerp_vs_Squad.py +304 -0
- akai_transform-0.0.1/example//347/251/272/351/227/264/345/217/230/346/215/242/345/237/272/347/241/200/346/241/210/344/276/213(akai_transform)/04./346/235/216/347/276/244/344/270/216/346/235/216/344/273/243/346/225/260/01_SE2/345/205/254/345/274/217/346/216/250/345/257/274(sympy).py +41 -0
- akai_transform-0.0.1/example//347/251/272/351/227/264/345/217/230/346/215/242/345/237/272/347/241/200/346/241/210/344/276/213(akai_transform)/04./346/235/216/347/276/244/344/270/216/346/235/216/344/273/243/346/225/260/01_SE2/346/214/207/346/225/260/346/230/240/345/260/204/344/270/216/345/257/271/346/225/260/346/230/240/345/260/204.py +270 -0
- akai_transform-0.0.1/example//347/251/272/351/227/264/345/217/230/346/215/242/345/237/272/347/241/200/346/241/210/344/276/213(akai_transform)/04./346/235/216/347/276/244/344/270/216/346/235/216/344/273/243/346/225/260/02_SE3/346/214/207/346/225/260/346/230/240/345/260/204/344/270/216/345/257/271/346/225/260/346/230/240/345/260/204.py +339 -0
- akai_transform-0.0.1/example//347/251/272/351/227/264/345/217/230/346/215/242/345/237/272/347/241/200/346/241/210/344/276/213(akai_transform)/04./346/235/216/347/276/244/344/270/216/346/235/216/344/273/243/346/225/260//345/205/266/344/273/226/345/267/245/345/205/267/350/204/232/346/234/254/se2_visual_cross_product.py +88 -0
- akai_transform-0.0.1/example//347/251/272/351/227/264/345/217/230/346/215/242/345/237/272/347/241/200/346/241/210/344/276/213(akai_transform)/04./346/235/216/347/276/244/344/270/216/346/235/216/344/273/243/346/225/260//345/205/266/344/273/226/345/267/245/345/205/267/350/204/232/346/234/254/visual_one_minus_cosw_over_w.py +269 -0
- akai_transform-0.0.1/example//347/251/272/351/227/264/345/217/230/346/215/242/345/237/272/347/241/200/346/241/210/344/276/213(akai_transform)/04./346/235/216/347/276/244/344/270/216/346/235/216/344/273/243/346/225/260//345/205/266/344/273/226/345/267/245/345/205/267/350/204/232/346/234/254/visual_se2.py +327 -0
- akai_transform-0.0.1/example//347/251/272/351/227/264/345/217/230/346/215/242/345/237/272/347/241/200/346/241/210/344/276/213(akai_transform)/04./346/235/216/347/276/244/344/270/216/346/235/216/344/273/243/346/225/260//345/205/266/344/273/226/345/267/245/345/205/267/350/204/232/346/234/254/visual_sinw_div_w.py +73 -0
- akai_transform-0.0.1/example//347/251/272/351/227/264/345/217/230/346/215/242/345/237/272/347/241/200/346/241/210/344/276/213(akai_transform)/04./346/235/216/347/276/244/344/270/216/346/235/216/344/273/243/346/225/260//345/205/266/344/273/226/345/267/245/345/205/267/350/204/232/346/234/254/visual_sinw_geometric_meaning.py +269 -0
- akai_transform-0.0.1/example//347/251/272/351/227/264/345/217/230/346/215/242/345/237/272/347/241/200/346/241/210/344/276/213(akai_transform)/04./346/235/216/347/276/244/344/270/216/346/235/216/344/273/243/346/225/260//345/205/266/344/273/226/345/267/245/345/205/267/350/204/232/346/234/254/visual_sinw_over_w_meaning.py +247 -0
- akai_transform-0.0.1/example//347/251/272/351/227/264/345/217/230/346/215/242/345/237/272/347/241/200/346/241/210/344/276/213(akai_transform)/04./346/235/216/347/276/244/344/270/216/346/235/216/344/273/243/346/225/260//345/205/266/344/273/226/345/267/245/345/205/267/350/204/232/346/234/254/visual_t_v_angle_relation.py +330 -0
- akai_transform-0.0.1/example//347/251/272/351/227/264/345/217/230/346/215/242/345/237/272/347/241/200/346/241/210/344/276/213(akai_transform)/04./346/235/216/347/276/244/344/270/216/346/235/216/344/273/243/346/225/260//345/205/266/344/273/226/345/267/245/345/205/267/350/204/232/346/234/254/visual_trigonometric_function.py +73 -0
- akai_transform-0.0.1/image/akai_3d_big.jpg +0 -0
- akai_transform-0.0.1/image/akai_wechat_qrcode.png +0 -0
- akai_transform-0.0.1/image//346/226/207/346/241/243/346/210/252/345/233/276.png +0 -0
- akai_transform-0.0.1/image//351/230/277/345/207/257/347/251/272/351/227/264/345/217/230/346/215/242/345/272/223ICON-V2.png +0 -0
- akai_transform-0.0.1/pyproject.toml +46 -0
- akai_transform-0.0.1/setup.cfg +4 -0
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
# akai_transform 编程指南
|
|
2
|
+
|
|
3
|
+
纯 Python 空间变换工具库,提供 2D/3D 空间变换、四元数运算、SE(3) 李群/李代数、对数四元数等功能。仅依赖 `numpy`。
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 模块总览
|
|
8
|
+
|
|
9
|
+
| 模块 | 导入方式 | 功能 |
|
|
10
|
+
|---|---|---|
|
|
11
|
+
| `tf2d` | `from akai_transform import tf2d` | 2D 空间变换(3×3 齐次矩阵) |
|
|
12
|
+
| `tf3d` | `from akai_transform import tf3d` | 3D 空间变换(4×4 齐次矩阵) |
|
|
13
|
+
| `quat` | `from akai_transform import quat` | 四元数运算 |
|
|
14
|
+
| `se3` | `from akai_transform import se3` | SE(3) 李群/李代数 |
|
|
15
|
+
| `log_quat` | `from akai_transform import log_quat` | 对数四元数 |
|
|
16
|
+
| `const` | `from akai_transform import const` | 常量和枚举 |
|
|
17
|
+
|
|
18
|
+
### 常量便捷导入
|
|
19
|
+
|
|
20
|
+
```python
|
|
21
|
+
from akai_transform import MM, M, DEG, RAD, PI, DEG2RAD, RAD2DEG
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## 约定
|
|
27
|
+
|
|
28
|
+
- **欧拉角**:ZYX 内旋(RPY),`[roll, pitch, yaw]` = `[绕X, 绕Y, 绕Z]`
|
|
29
|
+
- **角度单位**:默认弧度(rad),需要度时通过参数 `rpy_unit=DEG` 指定
|
|
30
|
+
- **四元数**:`[w, x, y, z]` 标量在前
|
|
31
|
+
- **2D 变换矩阵**:3×3 齐次矩阵
|
|
32
|
+
- **3D 变换矩阵**:4×4 齐次矩阵
|
|
33
|
+
- **平移单位**:通过 `MM`(毫米)或 `M`(米)枚举指定
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## tf2d — 2D 空间变换
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
from akai_transform import tf2d
|
|
41
|
+
|
|
42
|
+
# 创建矩阵
|
|
43
|
+
R = tf2d.rmat(np.pi / 4) # 旋转 45°
|
|
44
|
+
D = tf2d.dmat(100, 200) # 平移 (100, 200)
|
|
45
|
+
Dx = tf2d.dx(50) # 仅沿 X 轴平移
|
|
46
|
+
Dy = tf2d.dy(30) # 仅沿 Y 轴平移
|
|
47
|
+
T = tf2d.tmat(100, 200, np.pi / 4) # 旋转 + 平移
|
|
48
|
+
|
|
49
|
+
# 操作
|
|
50
|
+
T_inv = tf2d.inv(T) # 利用正交性加速求逆
|
|
51
|
+
v_a = tf2d.vec_transform(T, [10, 20]) # 坐标变换
|
|
52
|
+
theta = tf2d.rmat2theta(R) # 提取角度
|
|
53
|
+
|
|
54
|
+
# 由两向量求旋转
|
|
55
|
+
theta = tf2d.theta_between_vecs([1,0], [0,1]) # 求旋转角度
|
|
56
|
+
R = tf2d.rmat_between_vecs([1,0], [0,1]) # 求旋转矩阵
|
|
57
|
+
|
|
58
|
+
# 近似相等判断
|
|
59
|
+
tf2d.approx_equal_mat(T @ tf2d.inv(T), np.eye(3)) # True
|
|
60
|
+
tf2d.approx_equal_vec([1.0, 2.0], [1.0, 2.0]) # True
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## tf3d — 3D 空间变换
|
|
66
|
+
|
|
67
|
+
### 创建矩阵
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
from akai_transform import tf3d
|
|
71
|
+
|
|
72
|
+
R = tf3d.rmat(roll=0.1, pitch=0.2, yaw=0.3) # ZYX 内旋
|
|
73
|
+
Rx = tf3d.rx(0.1); Ry = tf3d.ry(0.2); Rz = tf3d.rz(0.3)
|
|
74
|
+
D = tf3d.dmat(100, 200, 300)
|
|
75
|
+
Dx = tf3d.dx(100); Dy = tf3d.dy(200); Dz = tf3d.dz(300) # 单轴平移
|
|
76
|
+
T = tf3d.tmat(100, 200, 300, 0.1, 0.2, 0.3)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### 变换操作
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
T_inv = tf3d.inv(T) # 利用正交性加速求逆
|
|
83
|
+
v_a = tf3d.vec_transform(T, [10, 20, 30]) # 坐标变换
|
|
84
|
+
T_mm = tf3d.tmat_m2mm(T) # 米 → 毫米
|
|
85
|
+
T_m = tf3d.tmat_mm2m(T_mm) # 毫米 → 米
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### 旋转表示转换
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
# 欧拉角 ↔ 旋转矩阵
|
|
92
|
+
R = tf3d.euler2rmat(euler)
|
|
93
|
+
euler = tf3d.rmat2euler(R)
|
|
94
|
+
|
|
95
|
+
# 旋转向量 ↔ 旋转矩阵
|
|
96
|
+
R = tf3d.rvec2rmat(n, theta)
|
|
97
|
+
n, theta = tf3d.rmat2rvec(R)
|
|
98
|
+
|
|
99
|
+
# 四元数 ↔ 旋转矩阵/欧拉角/旋转向量
|
|
100
|
+
q = tf3d.rmat2quat(R); R = tf3d.quat2rmat(q)
|
|
101
|
+
q = tf3d.euler2quat(euler); euler = tf3d.quat2euler(q)
|
|
102
|
+
q = tf3d.rvec2quat(n, theta); n, theta = tf3d.quat2rvec(q)
|
|
103
|
+
|
|
104
|
+
# 修正罗德里格斯参数(MRP)
|
|
105
|
+
mrp = tf3d.rmat2mrp(R); R = tf3d.mrp2rmat(mrp)
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### 由两向量求旋转
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
v = [1, 0, 0]; v_dot = [0, 1, 0]
|
|
112
|
+
n, theta = tf3d.rvec_between_vecs(v, v_dot) # 旋转向量
|
|
113
|
+
R = tf3d.rmat_between_vecs(v, v_dot) # 旋转矩阵
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### XYZRPY 便捷接口(机械臂常用)
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
from akai_transform import tf3d, MM, M, DEG, RAD
|
|
120
|
+
|
|
121
|
+
# 机械臂位姿 → 变换矩阵
|
|
122
|
+
xyzrpy = [400, 0, 300, 180, 0, 0] # mm + deg
|
|
123
|
+
T = tf3d.xyzrpy2tmat(xyzrpy, xyz_unit=MM, rpy_unit=DEG, t_unit=MM)
|
|
124
|
+
|
|
125
|
+
# 变换矩阵 → 机械臂位姿
|
|
126
|
+
xyzrpy = tf3d.tmat2xyzrpy(T, t_unit=MM, xyz_unit=MM, rpy_unit=DEG)
|
|
127
|
+
|
|
128
|
+
# 姿态增量变换(右乘增量)
|
|
129
|
+
new_xyzrpy = tf3d.xyzrpy_transform(xyzrpy, x=10, y=0, z=0,
|
|
130
|
+
roll=0, pitch=0, yaw=5, rpy_unit=DEG)
|
|
131
|
+
|
|
132
|
+
# XYZRPY 求逆
|
|
133
|
+
inv_xyzrpy = tf3d.xyzrpy_inv(xyzrpy, rpy_unit=DEG)
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### 高层工具
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
T_mean = tf3d.tmat_mean([T1, T2, T3]) # 多个变换矩阵求均值
|
|
140
|
+
R_clean = tf3d.orthogonalize_rmat(noisy_R) # SVD 正交化
|
|
141
|
+
dist, theta = tf3d.pose_error(T_real, T_est, is_debug=True)
|
|
142
|
+
# 输出: 平移误差(mm) + 角度误差(deg)
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## quat — 四元数运算
|
|
148
|
+
|
|
149
|
+
四元数格式统一为 `[w, x, y, z]`(标量在前)。
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
from akai_transform import quat
|
|
153
|
+
|
|
154
|
+
# 基础操作
|
|
155
|
+
q_id = quat.identity() # [1, 0, 0, 0]
|
|
156
|
+
n = quat.norm(q) # 模长
|
|
157
|
+
ok = quat.is_unit(q) # 是否为单位四元数
|
|
158
|
+
q_n = quat.normalize(q) # 归一化
|
|
159
|
+
q_conj = quat.conjugate(q) # 共轭 [w, -x, -y, -z]
|
|
160
|
+
q_neg = quat.reverse(q) # 取反 [-w, -x, -y, -z]
|
|
161
|
+
q_inv = quat.inv(q) # 求逆
|
|
162
|
+
|
|
163
|
+
# 运算
|
|
164
|
+
d = quat.dot(q1, q2) # 点乘
|
|
165
|
+
q_prod = quat.multiply(q1, q2) # Hamilton 积
|
|
166
|
+
|
|
167
|
+
# 向量旋转
|
|
168
|
+
v_rot = quat.rotate_vec(q, [1, 0, 0]) # 单个四元数旋转单个向量
|
|
169
|
+
v_list = quat.rotate_vec_nq(q_list, [1, 0, 0]) # 多个四元数旋转同一向量
|
|
170
|
+
v_list = quat.rotate_vec_nv(q, v_list) # 同一四元数旋转多个向量
|
|
171
|
+
|
|
172
|
+
# 度量
|
|
173
|
+
theta, is_reversed = quat.angle_diff(q1, q2, select_min=True)
|
|
174
|
+
q_delta = quat.relative_rotation(q1, q2) # q_delta * q1 = q2
|
|
175
|
+
|
|
176
|
+
# 插值
|
|
177
|
+
q_lerp = quat.lerp(q1, q2, 0.5) # 线性插值(结果非单位四元数)
|
|
178
|
+
q_nlerp = quat.nlerp(q1, q2, 0.5) # 正规化线性插值
|
|
179
|
+
q_slerp = quat.slerp(q1, q2, 0.5) # 球面线性插值(推荐)
|
|
180
|
+
q_list = quat.interp_p2p(q1, q2, t_list, short_path=True) # 批量插值
|
|
181
|
+
|
|
182
|
+
# Squad 插值(多途径点平滑插值)
|
|
183
|
+
q_interp = quat.squad(q0, q1, s0, s1, t) # 单次 Squad 插值
|
|
184
|
+
s = quat.compute_control_quat(q_prev, q_curr, q_next) # 计算控制四元数
|
|
185
|
+
|
|
186
|
+
# 多途径点插值(自动处理控制点)
|
|
187
|
+
q_list_slerp = quat.interp_multi_waypoints(q_waypoints, t_list, method='slerp') # 分段线性
|
|
188
|
+
q_list_squad = quat.interp_multi_waypoints(q_waypoints, t_list, method='squad') # 平滑过渡
|
|
189
|
+
|
|
190
|
+
# 统计
|
|
191
|
+
q_mean = quat.mean([q1, q2, q3, q4]) # 最大特征值法
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## se3 — SE(3) 李群/李代数
|
|
197
|
+
|
|
198
|
+
```python
|
|
199
|
+
from akai_transform import se3
|
|
200
|
+
|
|
201
|
+
# hat/vee 算子
|
|
202
|
+
omega_hat = se3.hat([1, 2, 3]) # R³ → so(3)
|
|
203
|
+
omega = se3.vee(omega_hat) # so(3) → R³
|
|
204
|
+
|
|
205
|
+
# SO(3) 指数/对数映射
|
|
206
|
+
R = se3.exp_so3([0.1, 0.2, 0.3])
|
|
207
|
+
omega = se3.log_so3(R)
|
|
208
|
+
|
|
209
|
+
# SE(3) 指数/对数映射
|
|
210
|
+
T = se3.exp_se3(omega, v)
|
|
211
|
+
omega, v = se3.log_se3(T)
|
|
212
|
+
|
|
213
|
+
# 6维李代数 ↔ 变换矩阵
|
|
214
|
+
xi = np.array([1, 2, 3, 0.1, 0.2, 0.3]) # [vx,vy,vz,wx,wy,wz]
|
|
215
|
+
T = se3.xi2tmat(xi)
|
|
216
|
+
xi = se3.tmat2xi(T)
|
|
217
|
+
|
|
218
|
+
# SE(3) 螺旋插值
|
|
219
|
+
T_mid = se3.interp_se3(T_start, T_end, t=0.5)
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
## log_quat — 对数四元数
|
|
225
|
+
|
|
226
|
+
```python
|
|
227
|
+
from akai_transform import log_quat
|
|
228
|
+
|
|
229
|
+
# 四元数 ↔ 对数空间
|
|
230
|
+
v = log_quat.quat2log(q) # 单位四元数 → R³
|
|
231
|
+
q = log_quat.log2quat(v) # R³ → 单位四元数
|
|
232
|
+
|
|
233
|
+
# 支持批量处理 (N,4) → (N,3)
|
|
234
|
+
v_batch = log_quat.quat2log(q_batch)
|
|
235
|
+
q_batch = log_quat.log2quat(v_batch)
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
## 常用代码模板
|
|
241
|
+
|
|
242
|
+
### 机械臂位姿处理
|
|
243
|
+
|
|
244
|
+
```python
|
|
245
|
+
from akai_transform import tf3d, MM, DEG
|
|
246
|
+
|
|
247
|
+
# 法奥机械臂 XYZRPY → 变换矩阵
|
|
248
|
+
T = tf3d.xyzrpy2tmat([400, 0, 300, 180, 0, 0], xyz_unit=MM, rpy_unit=DEG, t_unit=MM)
|
|
249
|
+
|
|
250
|
+
# 工具偏移
|
|
251
|
+
T_tool = T @ tf3d.tmat(0, 0, 50, 0, 0, 0)
|
|
252
|
+
|
|
253
|
+
# 逆变换
|
|
254
|
+
T_inv = tf3d.inv(T_tool)
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### 坐标系链式变换
|
|
258
|
+
|
|
259
|
+
```python
|
|
260
|
+
T_a2c = T_a2b @ T_b2c # A→B→C
|
|
261
|
+
T_a2b = T_a2c @ tf3d.inv(T_b2c) # 已知 A→C 和 B→C,求 A→B
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### 姿态插值
|
|
265
|
+
|
|
266
|
+
```python
|
|
267
|
+
from akai_transform import tf3d, quat, se3
|
|
268
|
+
|
|
269
|
+
# 四元数 Slerp 插值(两点间)
|
|
270
|
+
q1 = tf3d.euler2quat([0, 0, 0])
|
|
271
|
+
q2 = tf3d.euler2quat([0, 0, np.pi / 2])
|
|
272
|
+
q_list = quat.interp_p2p(q1, q2, np.linspace(0, 1, 50))
|
|
273
|
+
|
|
274
|
+
# 多途径点姿态插值
|
|
275
|
+
q_waypoints = [tf3d.euler2quat(rpy) for rpy in rpy_list]
|
|
276
|
+
t_interp = np.linspace(0, len(q_waypoints) - 1, 500)
|
|
277
|
+
|
|
278
|
+
# 方法 1: Slerp(分段线性,类似位置空间的折线)
|
|
279
|
+
q_list_slerp = quat.interp_multi_waypoints(q_waypoints, t_interp, method='slerp')
|
|
280
|
+
|
|
281
|
+
# 方法 2: Squad(平滑过渡,类似位置空间的圆弧过渡)
|
|
282
|
+
q_list_squad = quat.interp_multi_waypoints(q_waypoints, t_interp, method='squad')
|
|
283
|
+
|
|
284
|
+
# SE(3) 螺旋插值(同时插值位置和姿态)
|
|
285
|
+
T_list = [se3.interp_se3(T_start, T_end, t) for t in np.linspace(0, 1, 50)]
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
**Slerp vs Squad 对比**:
|
|
289
|
+
|
|
290
|
+
| 特性 | Slerp | Squad |
|
|
291
|
+
|------|-------|-------|
|
|
292
|
+
| 连续性 | C0(位置连续) | C1(速度连续) |
|
|
293
|
+
| 角速度 | 途径点处有突变 | 平滑连续 |
|
|
294
|
+
| 类比 | 位置空间的折线 | 位置空间的圆弧过渡 |
|
|
295
|
+
| 适用场景 | 简单路径、快速计算 | 高质量轨迹、减少机械冲击 |
|
|
296
|
+
| 计算复杂度 | 低 | 中等 |
|
|
297
|
+
|
|
298
|
+
**使用建议**:
|
|
299
|
+
- 机械臂轨迹规划:优先使用 Squad,减少机械应力
|
|
300
|
+
- 实时控制:Slerp 计算更快
|
|
301
|
+
- 可视化动画:Squad 更平滑自然
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: akai-transform
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: AKAI Spatial Transform Library
|
|
5
|
+
Author-email: Shunkai Xing <xingshunkai@qq.com>
|
|
6
|
+
Project-URL: Homepage, https://anyi.deepsenserobot.com/
|
|
7
|
+
Keywords: spatial-transform,robotics,quaternion,euler-angles,se3,so3
|
|
8
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
18
|
+
Classifier: Operating System :: OS Independent
|
|
19
|
+
Classifier: Intended Audience :: Science/Research
|
|
20
|
+
Classifier: Intended Audience :: Developers
|
|
21
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
22
|
+
Classifier: Topic :: Scientific/Engineering
|
|
23
|
+
Requires-Python: >=3.8
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
Requires-Dist: numpy
|
|
26
|
+
Provides-Extra: examples
|
|
27
|
+
|
|
28
|
+
# 阿凯空间变换库(akai-transform)
|
|
29
|
+
|
|
30
|
+
纯 Python 空间变换工具库,提供 2D/3D 空间变换、四元数运算、SE(3) 李群/李代数、对数四元数等功能。仅依赖 `numpy`。
|
|
31
|
+
|
|
32
|
+

|
|
33
|
+
|
|
34
|
+
## 文档
|
|
35
|
+
|
|
36
|
+
- [阿凯爱玩机器人 课程官网](https://anyi.deepsenserobot.com/)
|
|
37
|
+
- [理论基础-空间变换基础入门](https://anyi.deepsenserobot.com/course/akai-transform-course)
|
|
38
|
+
- [阿凯空间变换库(akai-transform)使用手册](https://anyi.deepsenserobot.com/course/akai-transform-manual)
|
|
39
|
+
|
|
40
|
+

|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
## 安装
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pip install akai-transform
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
## 模块总览
|
|
53
|
+
|
|
54
|
+
| 模块 | 导入方式 | 功能 |
|
|
55
|
+
|---|---|---|
|
|
56
|
+
| `tf2d` | `from akai_transform import tf2d` | 2D 空间变换(3×3 齐次矩阵) |
|
|
57
|
+
| `tf3d` | `from akai_transform import tf3d` | 3D 空间变换(4×4 齐次矩阵) |
|
|
58
|
+
| `quat` | `from akai_transform import quat` | 四元数运算 |
|
|
59
|
+
| `se3` | `from akai_transform import se3` | SE(3) 李群/李代数 |
|
|
60
|
+
| `log_quat` | `from akai_transform import log_quat` | 对数四元数 |
|
|
61
|
+
| `const` | `from akai_transform import const` | 常量和枚举 |
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
## 约定
|
|
66
|
+
|
|
67
|
+
- **欧拉角**:ZYX 内旋(RPY),`[roll, pitch, yaw]` = `[绕X, 绕Y, 绕Z]`
|
|
68
|
+
- **角度单位**:默认弧度(rad),需要度时通过参数 `rpy_unit=DEG` 指定
|
|
69
|
+
- **四元数**:`[w, x, y, z]` 标量在前
|
|
70
|
+
- **2D 变换矩阵**:3×3 齐次矩阵
|
|
71
|
+
- **3D 变换矩阵**:4×4 齐次矩阵
|
|
72
|
+
- **平移单位**:通过 `MM`(毫米)或 `M`(米)枚举指定
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
## 案例列表
|
|
77
|
+
|
|
78
|
+
完整案例代码位于 `example/空间变换基础案例(akai_transform)/` 目录下。
|
|
79
|
+
|
|
80
|
+
### 2D空间变换
|
|
81
|
+
|
|
82
|
+
| 文件 | 说明 |
|
|
83
|
+
|---|---|
|
|
84
|
+
| `01_tf2d_基础测试.py` | tf2d 基础测试 |
|
|
85
|
+
| `02_tf2d_小乌龟可视化.py` | 小乌龟可视化 |
|
|
86
|
+
|
|
87
|
+
### 3D空间变换(基础)
|
|
88
|
+
|
|
89
|
+
| 文件 | 说明 |
|
|
90
|
+
|---|---|
|
|
91
|
+
| `01_tf3d_旋转矩阵与平移矩阵.py` | 旋转矩阵与平移矩阵 |
|
|
92
|
+
| `02_tf3d_旋转向量.py` | 旋转向量 |
|
|
93
|
+
| `03_tf3d_四元数转换.py` | 四元数转换 |
|
|
94
|
+
| `04_tf3d_XYZRPY.py` | XYZRPY 便捷接口 |
|
|
95
|
+
| `05_tf3d_Open3D可视化.py` | Open3D 可视化 |
|
|
96
|
+
|
|
97
|
+
### 3D空间变换(进阶)
|
|
98
|
+
|
|
99
|
+
| 文件 | 说明 |
|
|
100
|
+
|---|---|
|
|
101
|
+
| `01_修正罗德里格斯参数(MRP).py` | 修正罗德里格斯参数 |
|
|
102
|
+
| `02_四元数基础运算.py` | 四元数基础运算 |
|
|
103
|
+
| `03_对数四元数转换.py` | 对数四元数转换 |
|
|
104
|
+
| `04_多途径点位置姿态插值_Slerp_vs_Squad.py` | Slerp vs Squad 插值对比 |
|
|
105
|
+
|
|
106
|
+
### 李群与李代数
|
|
107
|
+
|
|
108
|
+
| 文件 | 说明 |
|
|
109
|
+
|---|---|
|
|
110
|
+
| `01_SE2公式推导(sympy).py` | SE2 公式推导(SymPy) |
|
|
111
|
+
| `01_SE2指数映射与对数映射.py` | SE2 指数/对数映射 |
|
|
112
|
+
| `02_SE3指数映射与对数映射.py` | SE3 指数/对数映射 |
|
|
113
|
+
| `其他工具脚本/` | 三角函数可视化等辅助脚本 |
|
|
114
|
+
|
|
115
|
+
## 作者
|
|
116
|
+
|
|
117
|
+
**阿凯爱玩机器人**
|
|
118
|
+
|
|
119
|
+
<img src="image/akai_3d_big.jpg" width="200" />
|
|
120
|
+
|
|
121
|
+
扫码添加微信,一起交流机器人技术:
|
|
122
|
+
|
|
123
|
+
<img src="image/akai_wechat_qrcode.png" width="200" />
|
|
124
|
+
|
|
125
|
+
## 许可证
|
|
126
|
+
|
|
127
|
+
MIT License
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# 阿凯空间变换库(akai-transform)
|
|
2
|
+
|
|
3
|
+
纯 Python 空间变换工具库,提供 2D/3D 空间变换、四元数运算、SE(3) 李群/李代数、对数四元数等功能。仅依赖 `numpy`。
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
## 文档
|
|
8
|
+
|
|
9
|
+
- [阿凯爱玩机器人 课程官网](https://anyi.deepsenserobot.com/)
|
|
10
|
+
- [理论基础-空间变换基础入门](https://anyi.deepsenserobot.com/course/akai-transform-course)
|
|
11
|
+
- [阿凯空间变换库(akai-transform)使用手册](https://anyi.deepsenserobot.com/course/akai-transform-manual)
|
|
12
|
+
|
|
13
|
+

|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
## 安装
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pip install akai-transform
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
## 模块总览
|
|
26
|
+
|
|
27
|
+
| 模块 | 导入方式 | 功能 |
|
|
28
|
+
|---|---|---|
|
|
29
|
+
| `tf2d` | `from akai_transform import tf2d` | 2D 空间变换(3×3 齐次矩阵) |
|
|
30
|
+
| `tf3d` | `from akai_transform import tf3d` | 3D 空间变换(4×4 齐次矩阵) |
|
|
31
|
+
| `quat` | `from akai_transform import quat` | 四元数运算 |
|
|
32
|
+
| `se3` | `from akai_transform import se3` | SE(3) 李群/李代数 |
|
|
33
|
+
| `log_quat` | `from akai_transform import log_quat` | 对数四元数 |
|
|
34
|
+
| `const` | `from akai_transform import const` | 常量和枚举 |
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
## 约定
|
|
39
|
+
|
|
40
|
+
- **欧拉角**:ZYX 内旋(RPY),`[roll, pitch, yaw]` = `[绕X, 绕Y, 绕Z]`
|
|
41
|
+
- **角度单位**:默认弧度(rad),需要度时通过参数 `rpy_unit=DEG` 指定
|
|
42
|
+
- **四元数**:`[w, x, y, z]` 标量在前
|
|
43
|
+
- **2D 变换矩阵**:3×3 齐次矩阵
|
|
44
|
+
- **3D 变换矩阵**:4×4 齐次矩阵
|
|
45
|
+
- **平移单位**:通过 `MM`(毫米)或 `M`(米)枚举指定
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
## 案例列表
|
|
50
|
+
|
|
51
|
+
完整案例代码位于 `example/空间变换基础案例(akai_transform)/` 目录下。
|
|
52
|
+
|
|
53
|
+
### 2D空间变换
|
|
54
|
+
|
|
55
|
+
| 文件 | 说明 |
|
|
56
|
+
|---|---|
|
|
57
|
+
| `01_tf2d_基础测试.py` | tf2d 基础测试 |
|
|
58
|
+
| `02_tf2d_小乌龟可视化.py` | 小乌龟可视化 |
|
|
59
|
+
|
|
60
|
+
### 3D空间变换(基础)
|
|
61
|
+
|
|
62
|
+
| 文件 | 说明 |
|
|
63
|
+
|---|---|
|
|
64
|
+
| `01_tf3d_旋转矩阵与平移矩阵.py` | 旋转矩阵与平移矩阵 |
|
|
65
|
+
| `02_tf3d_旋转向量.py` | 旋转向量 |
|
|
66
|
+
| `03_tf3d_四元数转换.py` | 四元数转换 |
|
|
67
|
+
| `04_tf3d_XYZRPY.py` | XYZRPY 便捷接口 |
|
|
68
|
+
| `05_tf3d_Open3D可视化.py` | Open3D 可视化 |
|
|
69
|
+
|
|
70
|
+
### 3D空间变换(进阶)
|
|
71
|
+
|
|
72
|
+
| 文件 | 说明 |
|
|
73
|
+
|---|---|
|
|
74
|
+
| `01_修正罗德里格斯参数(MRP).py` | 修正罗德里格斯参数 |
|
|
75
|
+
| `02_四元数基础运算.py` | 四元数基础运算 |
|
|
76
|
+
| `03_对数四元数转换.py` | 对数四元数转换 |
|
|
77
|
+
| `04_多途径点位置姿态插值_Slerp_vs_Squad.py` | Slerp vs Squad 插值对比 |
|
|
78
|
+
|
|
79
|
+
### 李群与李代数
|
|
80
|
+
|
|
81
|
+
| 文件 | 说明 |
|
|
82
|
+
|---|---|
|
|
83
|
+
| `01_SE2公式推导(sympy).py` | SE2 公式推导(SymPy) |
|
|
84
|
+
| `01_SE2指数映射与对数映射.py` | SE2 指数/对数映射 |
|
|
85
|
+
| `02_SE3指数映射与对数映射.py` | SE3 指数/对数映射 |
|
|
86
|
+
| `其他工具脚本/` | 三角函数可视化等辅助脚本 |
|
|
87
|
+
|
|
88
|
+
## 作者
|
|
89
|
+
|
|
90
|
+
**阿凯爱玩机器人**
|
|
91
|
+
|
|
92
|
+
<img src="image/akai_3d_big.jpg" width="200" />
|
|
93
|
+
|
|
94
|
+
扫码添加微信,一起交流机器人技术:
|
|
95
|
+
|
|
96
|
+
<img src="image/akai_wechat_qrcode.png" width="200" />
|
|
97
|
+
|
|
98
|
+
## 许可证
|
|
99
|
+
|
|
100
|
+
MIT License
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
'''
|
|
2
|
+
akai_transform — 空间变换工具库
|
|
3
|
+
----------------------------------------------------------------
|
|
4
|
+
提供 2D/3D 空间变换、四元数运算、SE(3) 李群/李代数、对数四元数等功能。
|
|
5
|
+
|
|
6
|
+
子模块:
|
|
7
|
+
- tf2d: 2D 空间变换(3×3 齐次变换矩阵)
|
|
8
|
+
- tf3d: 3D 空间变换(4×4 齐次变换矩阵)
|
|
9
|
+
- quat: 四元数运算([w, x, y, z] 标量在前)
|
|
10
|
+
- se3: SE(3) 李群/李代数(指数/对数映射)
|
|
11
|
+
- log_quat: 对数四元数(四元数 ↔ R³ 旋转向量)
|
|
12
|
+
- const: 常量和枚举定义
|
|
13
|
+
|
|
14
|
+
作者: 阿凯爱玩机器人 | 微信: xingshunkai | QQ: 244561792
|
|
15
|
+
官网: deepsenserobot.com
|
|
16
|
+
B站: https://space.bilibili.com/40344504
|
|
17
|
+
'''
|
|
18
|
+
|
|
19
|
+
from . import const
|
|
20
|
+
from . import tf2d
|
|
21
|
+
from . import tf3d
|
|
22
|
+
from . import quat
|
|
23
|
+
from . import se3
|
|
24
|
+
from . import log_quat
|
|
25
|
+
|
|
26
|
+
# 常量便捷导出
|
|
27
|
+
from .const import (
|
|
28
|
+
PI, DEG2RAD, RAD2DEG, PI_2, PI_D_2,
|
|
29
|
+
TranslationUnit, AngleUnit,
|
|
30
|
+
MM, M, DEG, RAD,
|
|
31
|
+
)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
'''
|
|
2
|
+
常量与枚举定义
|
|
3
|
+
----------------------------------------------------------------
|
|
4
|
+
提供空间变换库所需的数学常量和单位枚举。
|
|
5
|
+
|
|
6
|
+
作者: 阿凯爱玩机器人 | 微信: xingshunkai | QQ: 244561792
|
|
7
|
+
官网: deepsenserobot.com
|
|
8
|
+
B站: https://space.bilibili.com/40344504
|
|
9
|
+
'''
|
|
10
|
+
|
|
11
|
+
import math
|
|
12
|
+
from enum import IntEnum
|
|
13
|
+
|
|
14
|
+
# ============================================================
|
|
15
|
+
# 数学常量
|
|
16
|
+
# ============================================================
|
|
17
|
+
|
|
18
|
+
PI = math.pi
|
|
19
|
+
'''圆周率'''
|
|
20
|
+
|
|
21
|
+
DEG2RAD = math.pi / 180.0
|
|
22
|
+
'''角度转弧度系数'''
|
|
23
|
+
|
|
24
|
+
RAD2DEG = 180.0 / math.pi
|
|
25
|
+
'''弧度转角度系数'''
|
|
26
|
+
|
|
27
|
+
PI_2 = math.pi * 2.0
|
|
28
|
+
'''两倍圆周率 (2π)'''
|
|
29
|
+
|
|
30
|
+
PI_D_2 = math.pi / 2.0
|
|
31
|
+
'''圆周率的一半 (π/2)'''
|
|
32
|
+
|
|
33
|
+
# ============================================================
|
|
34
|
+
# 单位枚举
|
|
35
|
+
# ============================================================
|
|
36
|
+
|
|
37
|
+
class TranslationUnit(IntEnum):
|
|
38
|
+
'''平移量单位枚举'''
|
|
39
|
+
MM = 0 # 毫米
|
|
40
|
+
M = 1 # 米
|
|
41
|
+
|
|
42
|
+
class AngleUnit(IntEnum):
|
|
43
|
+
'''角度单位枚举'''
|
|
44
|
+
DEG = 0 # 度
|
|
45
|
+
RAD = 1 # 弧度
|
|
46
|
+
|
|
47
|
+
# 便捷别名
|
|
48
|
+
MM = TranslationUnit.MM
|
|
49
|
+
M = TranslationUnit.M
|
|
50
|
+
DEG = AngleUnit.DEG
|
|
51
|
+
RAD = AngleUnit.RAD
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
'''
|
|
2
|
+
对数四元数模块
|
|
3
|
+
----------------------------------------------------------------
|
|
4
|
+
提供四元数与对数四元数(R³ 旋转向量)之间的映射,
|
|
5
|
+
用于姿态插值和优化。
|
|
6
|
+
|
|
7
|
+
理论基础:
|
|
8
|
+
- 对数映射: SO(3) → so(3), q → log(q)
|
|
9
|
+
- 指数映射: so(3) → SO(3), v → exp(v)
|
|
10
|
+
- 应用场景: 姿态插值、轨迹规划、姿态优化等
|
|
11
|
+
|
|
12
|
+
作者: 阿凯爱玩机器人 | 微信: xingshunkai | QQ: 244561792
|
|
13
|
+
官网: deepsenserobot.com
|
|
14
|
+
B站: https://space.bilibili.com/40344504
|
|
15
|
+
'''
|
|
16
|
+
|
|
17
|
+
import numpy as np
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def quat2log(q):
|
|
21
|
+
'''单位四元数 → 对数四元数(R³ 旋转向量)
|
|
22
|
+
|
|
23
|
+
使用对数映射将 SO(3) 群映射到其李代数 so(3) ≅ R³。
|
|
24
|
+
支持单个或批量四元数输入。
|
|
25
|
+
|
|
26
|
+
:param q: 单位四元数 [w, x, y, z],形状为 (4,) 或 (..., 4)
|
|
27
|
+
:return: 对数四元数(旋转向量) [vx, vy, vz],形状为 (3,) 或 (..., 3)
|
|
28
|
+
'''
|
|
29
|
+
q = np.asarray(q, dtype=np.float64)
|
|
30
|
+
original_shape = q.shape
|
|
31
|
+
|
|
32
|
+
if q.ndim == 1:
|
|
33
|
+
q = q.reshape(1, -1)
|
|
34
|
+
|
|
35
|
+
# 归一化
|
|
36
|
+
q = q / np.linalg.norm(q, axis=-1, keepdims=True)
|
|
37
|
+
|
|
38
|
+
w = q[..., 0]
|
|
39
|
+
vec = q[..., 1:]
|
|
40
|
+
norm_vec = np.linalg.norm(vec, axis=-1)
|
|
41
|
+
|
|
42
|
+
log_q = np.zeros((*q.shape[:-1], 3), dtype=np.float64)
|
|
43
|
+
|
|
44
|
+
mask = norm_vec >= 1e-6
|
|
45
|
+
if np.any(mask):
|
|
46
|
+
theta = 2.0 * np.arctan2(norm_vec[mask], w[mask])
|
|
47
|
+
axis = vec[mask] / norm_vec[mask, np.newaxis]
|
|
48
|
+
log_q[mask] = axis * theta[:, np.newaxis]
|
|
49
|
+
|
|
50
|
+
if len(original_shape) == 1:
|
|
51
|
+
return log_q[0]
|
|
52
|
+
return log_q
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def log2quat(v):
|
|
56
|
+
'''对数四元数(R³ 旋转向量) → 单位四元数
|
|
57
|
+
|
|
58
|
+
使用指数映射将李代数 so(3) 映射回 SO(3) 群。
|
|
59
|
+
支持单个或批量输入。
|
|
60
|
+
|
|
61
|
+
:param v: 对数四元数(旋转向量) [vx, vy, vz],形状为 (3,) 或 (..., 3)
|
|
62
|
+
:return: 单位四元数 [w, x, y, z],形状为 (4,) 或 (..., 4)
|
|
63
|
+
'''
|
|
64
|
+
v = np.asarray(v, dtype=np.float64)
|
|
65
|
+
original_shape = v.shape
|
|
66
|
+
|
|
67
|
+
if v.ndim == 1:
|
|
68
|
+
v = v.reshape(1, -1)
|
|
69
|
+
|
|
70
|
+
theta = np.linalg.norm(v, axis=-1)
|
|
71
|
+
|
|
72
|
+
q = np.zeros((*v.shape[:-1], 4), dtype=np.float64)
|
|
73
|
+
q[..., 0] = 1.0
|
|
74
|
+
|
|
75
|
+
mask = theta >= 1e-6
|
|
76
|
+
if np.any(mask):
|
|
77
|
+
axis = v[mask] / theta[mask, np.newaxis]
|
|
78
|
+
half_theta = 0.5 * theta[mask]
|
|
79
|
+
q[mask, 0] = np.cos(half_theta)
|
|
80
|
+
q[mask, 1:] = axis * np.sin(half_theta)[:, np.newaxis]
|
|
81
|
+
|
|
82
|
+
if len(original_shape) == 1:
|
|
83
|
+
return q[0]
|
|
84
|
+
return q
|