transform-base 0.0.2__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.
@@ -0,0 +1,19 @@
1
+ include README.md
2
+ prune test*
3
+ prune tests*
4
+ prune docs*
5
+ prune examples*
6
+ prune input*
7
+ prune output*
8
+ prune log*
9
+ prune logs*
10
+ prune tmp*
11
+ prune temp*
12
+ prune .git
13
+ prune .pytest_cache
14
+ prune .mypy_cache
15
+ prune __pycache__
16
+ global-exclude *.log
17
+ global-exclude *.pyc
18
+ global-exclude *.pyo
19
+ global-exclude .DS_Store
@@ -0,0 +1,60 @@
1
+ Metadata-Version: 2.4
2
+ Name: transform-base
3
+ Version: 0.0.2
4
+ Summary: 欧拉角、旋转矩阵、四元数、角轴及外参变换基础库,支持多种旋转顺序
5
+ License-Expression: MIT
6
+ Requires-Python: >=3.10
7
+ Description-Content-Type: text/markdown
8
+ Requires-Dist: numpy>=1.20
9
+ Provides-Extra: dev
10
+ Requires-Dist: pytest; extra == "dev"
11
+ Requires-Dist: build; extra == "dev"
12
+ Requires-Dist: twine; extra == "dev"
13
+
14
+ # transform-base
15
+
16
+ 欧拉角、旋转矩阵、四元数、角轴及外参变换基础库,支持多种旋转顺序(XYZ/XZY/YXZ/YZX/ZXY/ZYX)。
17
+
18
+ ## 安装
19
+
20
+ ```bash
21
+ pip install transform-base
22
+ ```
23
+
24
+ ## 功能特性
25
+
26
+ - **角度归一化**:`normalize_angle` 将角度归一化到 [-180, 180]
27
+ - **欧拉角 ↔ 旋转矩阵**:支持 6 种旋转顺序(order),默认 ZYX
28
+ - **旋转矩阵 ↔ 四元数**:`[x, y, z, w]` 格式
29
+ - **旋转矩阵 ↔ 角轴**:Rodrigues 公式
30
+ - **外参类 ExtrinsicParam**:欧拉/旋转矩阵/四元数/角轴多种初始化,齐次矩阵、点变换、复合与逆
31
+
32
+ ## 快速开始
33
+
34
+ ```python
35
+ from transform_base import (
36
+ normalize_angle,
37
+ standard_euler_to_rotation_matrix,
38
+ rotation_matrix_to_standard_euler,
39
+ ExtrinsicParam,
40
+ )
41
+
42
+ # 欧拉角 -> 旋转矩阵 -> 欧拉角
43
+ euler = [10.0, 20.0, 30.0] # roll, pitch, yaw (度)
44
+ R = standard_euler_to_rotation_matrix(*euler, order="ZYX")
45
+ euler_back = rotation_matrix_to_standard_euler(R, order="ZYX")
46
+
47
+ # 外参:欧拉角 + 平移
48
+ ext = ExtrinsicParam(euler=[0, 0, 90], translation=[1, 0, 0])
49
+ p_out = ext.transform_point([1, 0, 0])
50
+ ext_inv = ext.inverse()
51
+ ```
52
+
53
+ ## 依赖
54
+
55
+ - Python >= 3.10
56
+ - numpy >= 1.20
57
+
58
+ ## License
59
+
60
+ MIT
@@ -0,0 +1,47 @@
1
+ # transform-base
2
+
3
+ 欧拉角、旋转矩阵、四元数、角轴及外参变换基础库,支持多种旋转顺序(XYZ/XZY/YXZ/YZX/ZXY/ZYX)。
4
+
5
+ ## 安装
6
+
7
+ ```bash
8
+ pip install transform-base
9
+ ```
10
+
11
+ ## 功能特性
12
+
13
+ - **角度归一化**:`normalize_angle` 将角度归一化到 [-180, 180]
14
+ - **欧拉角 ↔ 旋转矩阵**:支持 6 种旋转顺序(order),默认 ZYX
15
+ - **旋转矩阵 ↔ 四元数**:`[x, y, z, w]` 格式
16
+ - **旋转矩阵 ↔ 角轴**:Rodrigues 公式
17
+ - **外参类 ExtrinsicParam**:欧拉/旋转矩阵/四元数/角轴多种初始化,齐次矩阵、点变换、复合与逆
18
+
19
+ ## 快速开始
20
+
21
+ ```python
22
+ from transform_base import (
23
+ normalize_angle,
24
+ standard_euler_to_rotation_matrix,
25
+ rotation_matrix_to_standard_euler,
26
+ ExtrinsicParam,
27
+ )
28
+
29
+ # 欧拉角 -> 旋转矩阵 -> 欧拉角
30
+ euler = [10.0, 20.0, 30.0] # roll, pitch, yaw (度)
31
+ R = standard_euler_to_rotation_matrix(*euler, order="ZYX")
32
+ euler_back = rotation_matrix_to_standard_euler(R, order="ZYX")
33
+
34
+ # 外参:欧拉角 + 平移
35
+ ext = ExtrinsicParam(euler=[0, 0, 90], translation=[1, 0, 0])
36
+ p_out = ext.transform_point([1, 0, 0])
37
+ ext_inv = ext.inverse()
38
+ ```
39
+
40
+ ## 依赖
41
+
42
+ - Python >= 3.10
43
+ - numpy >= 1.20
44
+
45
+ ## License
46
+
47
+ MIT
@@ -0,0 +1,36 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "transform-base"
7
+ version = "0.0.2"
8
+ description = "欧拉角、旋转矩阵、四元数、角轴及外参变换基础库,支持多种旋转顺序"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ requires-python = ">=3.10"
12
+ dependencies = [
13
+ "numpy>=1.20",
14
+ ]
15
+
16
+ [project.optional-dependencies]
17
+ dev = ["pytest", "build", "twine"]
18
+
19
+ [tool.setuptools.packages.find]
20
+ exclude = [
21
+ "test*",
22
+ "tests*",
23
+ "docs*",
24
+ "examples*",
25
+ "input*",
26
+ "output*",
27
+ "log*",
28
+ "logs*",
29
+ "*.log",
30
+ "tmp*",
31
+ "temp*",
32
+ ".pytest_cache*",
33
+ ".mypy_cache*",
34
+ "__pycache__*",
35
+ "*.pyc",
36
+ ]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,30 @@
1
+ # transform_base: 欧拉角、旋转矩阵、四元数、角轴及外参变换基础库
2
+ from transform_base.transform import (
3
+ normalize_angle,
4
+ standard_euler_to_rotation_matrix,
5
+ rotation_matrix_to_standard_euler,
6
+ rotation_matrix_to_quaternion,
7
+ quaternion_to_rotation_matrix,
8
+ rotation_matrix_to_axis_angle,
9
+ axis_angle_to_rotation_matrix,
10
+ standard_euler_to_quaternion,
11
+ quaternion_to_standard_euler,
12
+ standard_euler_to_axis_angle,
13
+ axis_angle_to_standard_euler,
14
+ ExtrinsicParam,
15
+ )
16
+
17
+ __all__ = [
18
+ "normalize_angle",
19
+ "standard_euler_to_rotation_matrix",
20
+ "rotation_matrix_to_standard_euler",
21
+ "rotation_matrix_to_quaternion",
22
+ "quaternion_to_rotation_matrix",
23
+ "rotation_matrix_to_axis_angle",
24
+ "axis_angle_to_rotation_matrix",
25
+ "standard_euler_to_quaternion",
26
+ "quaternion_to_standard_euler",
27
+ "standard_euler_to_axis_angle",
28
+ "axis_angle_to_standard_euler",
29
+ "ExtrinsicParam",
30
+ ]
@@ -0,0 +1,778 @@
1
+ import numpy as np
2
+ from typing import Tuple, Union, Optional
3
+ import warnings
4
+
5
+
6
+ # ==================== 角度归一化 ====================
7
+
8
+ def normalize_angle(angle: float) -> float:
9
+ """
10
+ 将角度归一化到[-180, 180]区间
11
+
12
+ 参数:
13
+ angle: 输入角度,单位度
14
+
15
+ 返回:
16
+ 归一化后的角度,范围[-180, 180]
17
+ """
18
+ while angle > 180:
19
+ angle -= 360
20
+ while angle < -180:
21
+ angle += 360
22
+ return angle
23
+
24
+
25
+ # ==================== Order参数辅助函数 ====================
26
+
27
+ def _normalize_order(order: str) -> str:
28
+ """
29
+ 规范化order参数(转大写,验证有效性,默认'ZYX')
30
+
31
+ 参数:
32
+ order: 旋转顺序字符串,支持 'XYZ', 'XZY', 'YXZ', 'YZX', 'ZXY', 'ZYX'(不区分大小写)
33
+ order表示从右往左读矩阵乘法的顺序,如ZYX表示 Rx @ Ry @ Rz
34
+
35
+ 返回:
36
+ 规范化后的order字符串(大写),不识别的输入默认返回'ZYX'
37
+ """
38
+ if order is None:
39
+ return 'ZYX'
40
+
41
+ order_upper = order.upper()
42
+ valid_orders = ['XYZ', 'XZY', 'YXZ', 'YZX', 'ZXY', 'ZYX']
43
+
44
+ if order_upper not in valid_orders:
45
+ warnings.warn(f"无效的order值'{order}',默认使用'ZYX'。有效值为: {valid_orders}", UserWarning)
46
+ return 'ZYX'
47
+
48
+ return order_upper
49
+
50
+
51
+ def _get_basic_rotation_matrices(roll_rad: float, pitch_rad: float, yaw_rad: float) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
52
+ """
53
+ 获取基础旋转矩阵 Rx, Ry, Rz
54
+
55
+ 参数:
56
+ roll_rad: 横滚角,单位弧度
57
+ pitch_rad: 俯仰角,单位弧度
58
+ yaw_rad: 航向角,单位弧度
59
+
60
+ 返回:
61
+ (Rx, Ry, Rz): 三个基础旋转矩阵
62
+ """
63
+ # 绕X轴旋转(roll)
64
+ Rx = np.array([
65
+ [1, 0, 0],
66
+ [0, np.cos(roll_rad), -np.sin(roll_rad)],
67
+ [0, np.sin(roll_rad), np.cos(roll_rad)]
68
+ ])
69
+
70
+ # 绕Y轴旋转(pitch)
71
+ Ry = np.array([
72
+ [np.cos(pitch_rad), 0, np.sin(pitch_rad)],
73
+ [0, 1, 0],
74
+ [-np.sin(pitch_rad), 0, np.cos(pitch_rad)]
75
+ ])
76
+
77
+ # 绕Z轴旋转(yaw)
78
+ Rz = np.array([
79
+ [np.cos(yaw_rad), -np.sin(yaw_rad), 0],
80
+ [np.sin(yaw_rad), np.cos(yaw_rad), 0],
81
+ [0, 0, 1]
82
+ ])
83
+
84
+ return Rx, Ry, Rz
85
+
86
+
87
+ def _euler_to_rotation_matrix_by_order(roll: float, pitch: float, yaw: float, order: str) -> np.ndarray:
88
+ """
89
+ 根据order参数生成对应旋转顺序的旋转矩阵
90
+
91
+ order表示从右往左读矩阵乘法的顺序:
92
+ - 'ZYX': Rx @ Ry @ Rz (先绕Z轴,再绕Y轴,最后绕X轴) - 与v0.1.2相同
93
+ - 'XYZ': Rz @ Ry @ Rx (先绕X轴,再绕Y轴,最后绕Z轴)
94
+
95
+ 参数:
96
+ roll: 横滚角,单位度
97
+ pitch: 俯仰角,单位度
98
+ yaw: 航向角,单位度
99
+ order: 旋转顺序,'XYZ', 'XZY', 'YXZ', 'YZX', 'ZXY', 'ZYX'
100
+
101
+ 返回:
102
+ 旋转矩阵,shape (3, 3)
103
+ """
104
+ order = _normalize_order(order)
105
+
106
+ roll_rad = np.deg2rad(roll)
107
+ pitch_rad = np.deg2rad(pitch)
108
+ yaw_rad = np.deg2rad(yaw)
109
+
110
+ Rx, Ry, Rz = _get_basic_rotation_matrices(roll_rad, pitch_rad, yaw_rad)
111
+
112
+ # 根据order组合旋转矩阵
113
+ # order表示从右往左读的顺序,如ZYX表示从右往左是Z-Y-X,即 Rx @ Ry @ Rz
114
+ rotation_map = {
115
+ 'XYZ': Rz @ Ry @ Rx, # 从右往左读: X-Y-Z
116
+ 'XZY': Ry @ Rz @ Rx, # 从右往左读: X-Z-Y
117
+ 'YXZ': Rz @ Rx @ Ry, # 从右往左读: Y-X-Z
118
+ 'YZX': Rx @ Rz @ Ry, # 从右往左读: Y-Z-X
119
+ 'ZXY': Ry @ Rx @ Rz, # 从右往左读: Z-X-Y
120
+ 'ZYX': Rx @ Ry @ Rz, # 从右往左读: Z-Y-X (与v0.1.2相同)
121
+ }
122
+
123
+ return rotation_map[order]
124
+
125
+
126
+ def _rotation_matrix_to_euler_by_order(R: np.ndarray, order: str) -> Tuple[float, float, float]:
127
+ """
128
+ 根据order参数从旋转矩阵提取对应顺序的欧拉角
129
+
130
+ 参数:
131
+ R: 旋转矩阵,shape (3, 3)
132
+ order: 旋转顺序,'XYZ', 'XZY', 'YXZ', 'YZX', 'ZXY', 'ZYX'
133
+
134
+ 返回:
135
+ (roll, pitch, yaw): 欧拉角三元组,单位度,范围[-180, 180]
136
+ """
137
+ order = _normalize_order(order)
138
+
139
+ if order == 'ZYX':
140
+ # R = Rx @ Ry @ Rz (与v0.1.2完全相同的提取公式)
141
+ sy = np.sqrt(R[0, 0]**2 + R[0, 1]**2)
142
+ singular = sy < 1e-6
143
+
144
+ if not singular:
145
+ roll = np.arctan2(-R[1, 2], R[2, 2])
146
+ pitch = np.arctan2(R[0, 2], sy)
147
+ yaw = np.arctan2(-R[0, 1], R[0, 0])
148
+ else:
149
+ roll = np.arctan2(R[2, 1], R[1, 1])
150
+ pitch = np.arctan2(R[0, 2], sy)
151
+ yaw = 0
152
+
153
+ elif order == 'XYZ':
154
+ # R = Rz @ Ry @ Rx
155
+ sin_pitch = -R[2, 0]
156
+ cos_pitch = np.sqrt(R[0, 0]**2 + R[1, 0]**2)
157
+
158
+ if cos_pitch < 1e-6: # 万向锁
159
+ pitch = np.arctan2(sin_pitch, cos_pitch)
160
+ if sin_pitch > 0:
161
+ yaw = np.arctan2(-R[0, 1], R[1, 1])
162
+ roll = 0
163
+ else:
164
+ yaw = np.arctan2(R[0, 1], R[1, 1])
165
+ roll = 0
166
+ else:
167
+ pitch = np.arctan2(sin_pitch, cos_pitch)
168
+ roll = np.arctan2(R[2, 1], R[2, 2])
169
+ yaw = np.arctan2(R[1, 0], R[0, 0])
170
+
171
+ elif order == 'XZY':
172
+ # R = Ry @ Rz @ Rx
173
+ sin_yaw = R[1, 0]
174
+ cos_yaw = np.sqrt(R[0, 0]**2 + R[2, 0]**2)
175
+
176
+ if cos_yaw < 1e-6: # 万向锁
177
+ yaw = np.arctan2(sin_yaw, cos_yaw)
178
+ if sin_yaw > 0:
179
+ roll = np.arctan2(R[0, 2], R[0, 0])
180
+ pitch = 0
181
+ else:
182
+ roll = np.arctan2(-R[0, 2], R[0, 0])
183
+ pitch = 0
184
+ else:
185
+ yaw = np.arctan2(sin_yaw, cos_yaw)
186
+ roll = np.arctan2(-R[1, 2], R[1, 1])
187
+ pitch = np.arctan2(-R[2, 0], R[0, 0])
188
+
189
+ elif order == 'YXZ':
190
+ # R = Rz @ Rx @ Ry
191
+ sin_roll = R[2, 1]
192
+ cos_roll = np.sqrt(R[0, 1]**2 + R[1, 1]**2)
193
+
194
+ if cos_roll < 1e-6: # 万向锁
195
+ roll = np.arctan2(sin_roll, cos_roll)
196
+ if sin_roll > 0:
197
+ yaw = np.arctan2(R[0, 2], R[0, 0])
198
+ pitch = 0
199
+ else:
200
+ yaw = np.arctan2(-R[0, 2], R[0, 0])
201
+ pitch = 0
202
+ else:
203
+ roll = np.arctan2(sin_roll, cos_roll)
204
+ pitch = np.arctan2(-R[2, 0], R[2, 2])
205
+ yaw = np.arctan2(-R[0, 1], R[1, 1])
206
+
207
+ elif order == 'YZX':
208
+ # R = Rx @ Rz @ Ry
209
+ sin_yaw = -R[0, 1]
210
+ cos_yaw = np.sqrt(R[1, 1]**2 + R[2, 1]**2)
211
+
212
+ if cos_yaw < 1e-6: # 万向锁
213
+ yaw = np.arctan2(sin_yaw, cos_yaw)
214
+ if sin_yaw > 0:
215
+ pitch = np.arctan2(-R[2, 0], R[2, 2])
216
+ roll = 0
217
+ else:
218
+ pitch = np.arctan2(R[2, 0], R[2, 2])
219
+ roll = 0
220
+ else:
221
+ yaw = np.arctan2(sin_yaw, cos_yaw)
222
+ roll = np.arctan2(R[2, 1], R[1, 1])
223
+ pitch = np.arctan2(R[0, 2], R[0, 0])
224
+
225
+ elif order == 'ZXY':
226
+ # R = Ry @ Rx @ Rz
227
+ sin_roll = -R[1, 2]
228
+ cos_roll = np.sqrt(R[0, 2]**2 + R[2, 2]**2)
229
+
230
+ if cos_roll < 1e-6: # 万向锁
231
+ roll = np.arctan2(sin_roll, cos_roll)
232
+ if sin_roll > 0:
233
+ pitch = np.arctan2(-R[0, 1], R[0, 0])
234
+ yaw = 0
235
+ else:
236
+ pitch = np.arctan2(R[0, 1], R[0, 0])
237
+ yaw = 0
238
+ else:
239
+ roll = np.arctan2(sin_roll, cos_roll)
240
+ pitch = np.arctan2(R[0, 2], R[2, 2])
241
+ yaw = np.arctan2(R[1, 0], R[1, 1])
242
+
243
+ # 转换为度并归一化
244
+ roll_deg = normalize_angle(np.rad2deg(roll))
245
+ pitch_deg = normalize_angle(np.rad2deg(pitch))
246
+ yaw_deg = normalize_angle(np.rad2deg(yaw))
247
+
248
+ return roll_deg, pitch_deg, yaw_deg
249
+
250
+
251
+ # ==================== 标准欧拉角(支持order参数) ====================
252
+
253
+ def standard_euler_to_rotation_matrix(roll: float, pitch: float, yaw: float, order: str = 'ZYX') -> np.ndarray:
254
+ """
255
+ 标准欧拉角转旋转矩阵(支持多种旋转顺序)
256
+
257
+ 默认order='ZYX':R = Rx @ Ry @ Rz (先绕Z轴,再绕Y轴,最后绕X轴)
258
+
259
+ 参数:
260
+ roll: 横滚角,单位度
261
+ pitch: 俯仰角,单位度
262
+ yaw: 航向角,单位度
263
+ order: 旋转顺序,默认'ZYX',支持 'XYZ', 'XZY', 'YXZ', 'YZX', 'ZXY', 'ZYX'(不区分大小写)
264
+
265
+ 返回:
266
+ 旋转矩阵,shape (3, 3)
267
+ """
268
+ order = _normalize_order(order)
269
+ return _euler_to_rotation_matrix_by_order(roll, pitch, yaw, order)
270
+
271
+
272
+ def rotation_matrix_to_standard_euler(R: np.ndarray, order: str = 'ZYX') -> Tuple[float, float, float]:
273
+ """
274
+ 旋转矩阵转标准欧拉角(支持多种旋转顺序)
275
+
276
+ 参数:
277
+ R: 旋转矩阵,shape (3, 3)
278
+ order: 旋转顺序,默认'ZYX',支持 'XYZ', 'XZY', 'YXZ', 'YZX', 'ZXY', 'ZYX'(不区分大小写)
279
+
280
+ 返回:
281
+ (roll, pitch, yaw): 欧拉角三元组,单位度,范围[-180, 180]
282
+ """
283
+ order = _normalize_order(order)
284
+ return _rotation_matrix_to_euler_by_order(R, order)
285
+
286
+
287
+ # ==================== 旋转矩阵与四元数转换 ====================
288
+
289
+ def rotation_matrix_to_quaternion(R: np.ndarray) -> np.ndarray:
290
+ """
291
+ 将旋转矩阵转换为四元数 [x, y, z, w]
292
+
293
+ 参数:
294
+ R: 旋转矩阵,shape (3, 3)
295
+
296
+ 返回:
297
+ 四元数 [x, y, z, w],shape (4,)
298
+ """
299
+ trace = np.trace(R)
300
+
301
+ if trace > 0:
302
+ s = 0.5 / np.sqrt(trace + 1.0)
303
+ w = 0.25 / s
304
+ x = (R[2, 1] - R[1, 2]) * s
305
+ y = (R[0, 2] - R[2, 0]) * s
306
+ z = (R[1, 0] - R[0, 1]) * s
307
+ elif R[0, 0] > R[1, 1] and R[0, 0] > R[2, 2]:
308
+ s = 2.0 * np.sqrt(1.0 + R[0, 0] - R[1, 1] - R[2, 2])
309
+ w = (R[2, 1] - R[1, 2]) / s
310
+ x = 0.25 * s
311
+ y = (R[0, 1] + R[1, 0]) / s
312
+ z = (R[0, 2] + R[2, 0]) / s
313
+ elif R[1, 1] > R[2, 2]:
314
+ s = 2.0 * np.sqrt(1.0 + R[1, 1] - R[0, 0] - R[2, 2])
315
+ w = (R[0, 2] - R[2, 0]) / s
316
+ x = (R[0, 1] + R[1, 0]) / s
317
+ y = 0.25 * s
318
+ z = (R[1, 2] + R[2, 1]) / s
319
+ else:
320
+ s = 2.0 * np.sqrt(1.0 + R[2, 2] - R[0, 0] - R[1, 1])
321
+ w = (R[1, 0] - R[0, 1]) / s
322
+ x = (R[0, 2] + R[2, 0]) / s
323
+ y = (R[1, 2] + R[2, 1]) / s
324
+ z = 0.25 * s
325
+
326
+ quaternion = np.array([x, y, z, w])
327
+ # 归一化
328
+ return quaternion / np.linalg.norm(quaternion)
329
+
330
+
331
+ def quaternion_to_rotation_matrix(q: Union[list, np.ndarray]) -> np.ndarray:
332
+ """
333
+ 将四元数转换为旋转矩阵
334
+
335
+ 参数:
336
+ q: 四元数 [x, y, z, w],shape (4,)
337
+
338
+ 返回:
339
+ 旋转矩阵,shape (3, 3)
340
+ """
341
+ q = np.array(q, dtype=float)
342
+ # 归一化
343
+ q = q / np.linalg.norm(q)
344
+
345
+ x, y, z, w = q
346
+
347
+ R = np.array([
348
+ [1 - 2*(y**2 + z**2), 2*(x*y - w*z), 2*(x*z + w*y)],
349
+ [2*(x*y + w*z), 1 - 2*(x**2 + z**2), 2*(y*z - w*x)],
350
+ [2*(x*z - w*y), 2*(y*z + w*x), 1 - 2*(x**2 + y**2)]
351
+ ])
352
+
353
+ return R
354
+
355
+
356
+ # ==================== 旋转矩阵与角轴转换 ====================
357
+
358
+ def rotation_matrix_to_axis_angle(R: np.ndarray) -> Tuple[np.ndarray, float]:
359
+ """
360
+ 将旋转矩阵转换为角轴表示
361
+
362
+ 参数:
363
+ R: 旋转矩阵,shape (3, 3)
364
+
365
+ 返回:
366
+ axis: 旋转轴单位向量,shape (3,)
367
+ angle: 旋转角度,单位弧度
368
+ """
369
+ # 计算旋转角度
370
+ trace = np.trace(R)
371
+ angle = np.arccos(np.clip((trace - 1) / 2, -1, 1))
372
+
373
+ # 处理特殊情况
374
+ if np.abs(angle) < 1e-10:
375
+ # 无旋转
376
+ return np.array([1.0, 0.0, 0.0]), 0.0
377
+ elif np.abs(angle - np.pi) < 1e-10:
378
+ # 180度旋转
379
+ # 找到对角线最大元素对应的轴
380
+ diag = np.diag(R)
381
+ k = np.argmax(diag)
382
+ axis = np.zeros(3)
383
+ axis[k] = np.sqrt((R[k, k] + 1) / 2)
384
+ if k == 0:
385
+ axis[1] = R[0, 1] / (2 * axis[0])
386
+ axis[2] = R[0, 2] / (2 * axis[0])
387
+ elif k == 1:
388
+ axis[0] = R[1, 0] / (2 * axis[1])
389
+ axis[2] = R[1, 2] / (2 * axis[1])
390
+ else:
391
+ axis[0] = R[2, 0] / (2 * axis[2])
392
+ axis[1] = R[2, 1] / (2 * axis[2])
393
+ return axis, angle
394
+ else:
395
+ # 一般情况
396
+ axis = np.array([
397
+ R[2, 1] - R[1, 2],
398
+ R[0, 2] - R[2, 0],
399
+ R[1, 0] - R[0, 1]
400
+ ])
401
+ axis = axis / (2 * np.sin(angle))
402
+ return axis, angle
403
+
404
+
405
+ def axis_angle_to_rotation_matrix(axis: np.ndarray, angle: float) -> np.ndarray:
406
+ """
407
+ 将角轴表示转换为旋转矩阵(Rodrigues公式)
408
+
409
+ 参数:
410
+ axis: 旋转轴单位向量,shape (3,)
411
+ angle: 旋转角度,单位弧度
412
+
413
+ 返回:
414
+ 旋转矩阵,shape (3, 3)
415
+ """
416
+ # 归一化旋转轴
417
+ axis = np.array(axis, dtype=float)
418
+ axis = axis / np.linalg.norm(axis)
419
+
420
+ # Rodrigues公式
421
+ K = np.array([
422
+ [0, -axis[2], axis[1]],
423
+ [axis[2], 0, -axis[0]],
424
+ [-axis[1], axis[0], 0]
425
+ ])
426
+
427
+ R = np.eye(3) + np.sin(angle) * K + (1 - np.cos(angle)) * (K @ K)
428
+ return R
429
+
430
+
431
+ # ==================== 标准欧拉角转换函数(支持order) ====================
432
+
433
+ def standard_euler_to_quaternion(roll: float, pitch: float, yaw: float, order: str = 'ZYX') -> np.ndarray:
434
+ """
435
+ 标准欧拉角转四元数
436
+
437
+ 参数:
438
+ roll: 横滚角,单位度
439
+ pitch: 俯仰角,单位度
440
+ yaw: 航向角,单位度
441
+ order: 旋转顺序,默认'ZYX'
442
+
443
+ 返回:
444
+ 四元数 [x, y, z, w],shape (4,)
445
+ """
446
+ R = standard_euler_to_rotation_matrix(roll, pitch, yaw, order)
447
+ return rotation_matrix_to_quaternion(R)
448
+
449
+
450
+ def quaternion_to_standard_euler(q: Union[list, np.ndarray], order: str = 'ZYX') -> Tuple[float, float, float]:
451
+ """
452
+ 四元数转标准欧拉角
453
+
454
+ 参数:
455
+ q: 四元数 [x, y, z, w],shape (4,)
456
+ order: 旋转顺序,默认'ZYX'
457
+
458
+ 返回:
459
+ (roll, pitch, yaw): 欧拉角三元组,单位度,范围[-180, 180]
460
+ """
461
+ R = quaternion_to_rotation_matrix(q)
462
+ return rotation_matrix_to_standard_euler(R, order)
463
+
464
+
465
+ def standard_euler_to_axis_angle(roll: float, pitch: float, yaw: float, order: str = 'ZYX') -> Tuple[np.ndarray, float]:
466
+ """
467
+ 标准欧拉角转角轴表示
468
+
469
+ 参数:
470
+ roll: 横滚角,单位度
471
+ pitch: 俯仰角,单位度
472
+ yaw: 航向角,单位度
473
+ order: 旋转顺序,默认'ZYX'
474
+
475
+ 返回:
476
+ axis: 旋转轴单位向量,shape (3,)
477
+ angle: 旋转角度,单位弧度
478
+ """
479
+ R = standard_euler_to_rotation_matrix(roll, pitch, yaw, order)
480
+ return rotation_matrix_to_axis_angle(R)
481
+
482
+
483
+ def axis_angle_to_standard_euler(axis: np.ndarray, angle: float, order: str = 'ZYX') -> Tuple[float, float, float]:
484
+ """
485
+ 角轴表示转标准欧拉角
486
+
487
+ 参数:
488
+ axis: 旋转轴单位向量,shape (3,)
489
+ angle: 旋转角度,单位弧度
490
+ order: 旋转顺序,默认'ZYX'
491
+
492
+ 返回:
493
+ (roll, pitch, yaw): 欧拉角三元组,单位度,范围[-180, 180]
494
+ """
495
+ R = axis_angle_to_rotation_matrix(axis, angle)
496
+ return rotation_matrix_to_standard_euler(R, order)
497
+
498
+
499
+ # ==================== 外参类(支持order) ====================
500
+
501
+ class ExtrinsicParam:
502
+ """
503
+ 外参类,存储旋转矩阵和平移向量
504
+
505
+ 初始化方式:
506
+ 1. ExtrinsicParam(euler=[roll, pitch, yaw], translation=[x, y, z], euler_order='ZYX')
507
+ 2. ExtrinsicParam(rotation_matrix=R, translation=[x, y, z], euler_order='ZYX')
508
+ 3. ExtrinsicParam(axis_angle=(axis, angle), translation=[x, y, z], euler_order='ZYX')
509
+ 4. ExtrinsicParam(quaternion=[x, y, z, w], translation=[x, y, z], euler_order='ZYX')
510
+ 5. ExtrinsicParam.from_homogeneous_matrix(T, euler_order='ZYX') # 从4x4矩阵创建
511
+
512
+ 属性:
513
+ rotation_matrix: 旋转矩阵,shape (3, 3)
514
+ translation: 平移向量 [x, y, z],单位米
515
+ euler_order: 欧拉角旋转顺序,默认'ZYX'
516
+ standard_euler: 标准欧拉角 [roll, pitch, yaw],单位度,范围[-180, 180]
517
+ axis: 角轴旋转轴,shape (3,)
518
+ angle: 角轴旋转角,单位弧度
519
+ quaternion: 四元数 [x, y, z, w],shape (4,)
520
+ """
521
+
522
+ def __init__(self,
523
+ euler: Optional[Union[list, np.ndarray]] = None,
524
+ translation: Optional[Union[list, np.ndarray]] = None,
525
+ rotation_matrix: Optional[np.ndarray] = None,
526
+ axis_angle: Optional[Tuple[np.ndarray, float]] = None,
527
+ quaternion: Optional[Union[list, np.ndarray]] = None,
528
+ euler_order: str = 'ZYX'):
529
+ """
530
+ 初始化外参
531
+
532
+ 参数:
533
+ euler: 欧拉角 [roll, pitch, yaw],单位度
534
+ translation: 平移向量 [x, y, z],单位米
535
+ rotation_matrix: 旋转矩阵,shape (3, 3)
536
+ axis_angle: 角轴表示 (axis, angle),axis为单位向量,angle单位弧度
537
+ quaternion: 四元数 [x, y, z, w],shape (4,)
538
+ euler_order: 欧拉角旋转顺序,默认'ZYX'(与v0.1.2相同)
539
+ """
540
+ # 存储欧拉角顺序
541
+ self.euler_order = _normalize_order(euler_order)
542
+
543
+ if euler is not None:
544
+ self.rotation_matrix = standard_euler_to_rotation_matrix(*euler, self.euler_order)
545
+ elif rotation_matrix is not None:
546
+ self.rotation_matrix = np.array(rotation_matrix, dtype=float)
547
+ elif axis_angle is not None:
548
+ axis, angle = axis_angle
549
+ self.rotation_matrix = axis_angle_to_rotation_matrix(axis, angle)
550
+ elif quaternion is not None:
551
+ self.rotation_matrix = quaternion_to_rotation_matrix(quaternion)
552
+ else:
553
+ self.rotation_matrix = np.eye(3)
554
+
555
+ # 计算标准欧拉角表示(使用存储的euler_order)
556
+ self.standard_euler = np.array(rotation_matrix_to_standard_euler(self.rotation_matrix, self.euler_order))
557
+
558
+ # 计算角轴表示
559
+ self.axis, self.angle = rotation_matrix_to_axis_angle(self.rotation_matrix)
560
+
561
+ # 计算四元数表示 [x, y, z, w]
562
+ self.quaternion = rotation_matrix_to_quaternion(self.rotation_matrix)
563
+
564
+ self.translation = np.array(translation, dtype=float) if translation is not None else np.zeros(3)
565
+
566
+ @classmethod
567
+ def from_homogeneous_matrix(cls, T: np.ndarray, euler_order: str = 'ZYX') -> 'ExtrinsicParam':
568
+ """
569
+ 从4x4齐次变换矩阵创建外参对象
570
+
571
+ 参数:
572
+ T: 4x4齐次变换矩阵,格式为:
573
+ [[R11, R12, R13, tx],
574
+ [R21, R22, R23, ty],
575
+ [R31, R32, R33, tz],
576
+ [ 0, 0, 0, 1]]
577
+ euler_order: 欧拉角旋转顺序,默认'ZYX'
578
+
579
+ 返回:
580
+ ExtrinsicParam对象
581
+ """
582
+ T = np.array(T, dtype=float)
583
+ if T.shape != (4, 4):
584
+ raise ValueError(f"齐次矩阵必须是4x4矩阵,当前shape: {T.shape}")
585
+
586
+ # 验证最后一行是否为 [0, 0, 0, 1]
587
+ if not np.allclose(T[3, :], [0, 0, 0, 1]):
588
+ raise ValueError(f"齐次矩阵最后一行必须是[0, 0, 0, 1],当前为: {T[3, :]}")
589
+
590
+ rotation_matrix = T[:3, :3]
591
+ translation = T[:3, 3]
592
+
593
+ return cls(rotation_matrix=rotation_matrix, translation=translation, euler_order=euler_order)
594
+
595
+ def __matmul__(self, other: 'ExtrinsicParam') -> 'ExtrinsicParam':
596
+ """
597
+ 外参复合运算(@ 运算符,推荐使用)
598
+
599
+ 齐次变换复合: T_result = T_left @ T_right
600
+ 对应: R_result = R_left @ R_right
601
+ t_result = R_left @ t_right + t_left
602
+
603
+ 参数:
604
+ other: 右侧外参
605
+
606
+ 返回:
607
+ 复合后的外参(使用左侧外参的euler_order)
608
+
609
+ 示例:
610
+ >>> ext1 = ExtrinsicParam(euler=[10, 20, 30], translation=[1, 0, 0])
611
+ >>> ext2 = ExtrinsicParam(euler=[5, 10, 15], translation=[0, 1, 0])
612
+ >>> ext_combined = ext1 @ ext2
613
+ """
614
+ R_combined = self.rotation_matrix @ other.rotation_matrix
615
+ t_combined = self.rotation_matrix @ other.translation + self.translation
616
+ return ExtrinsicParam(rotation_matrix=R_combined, translation=t_combined, euler_order=self.euler_order)
617
+
618
+ def inverse(self) -> 'ExtrinsicParam':
619
+ """
620
+ 计算外参的逆变换
621
+
622
+ 对于齐次变换矩阵:
623
+ T = [ R t ]
624
+ [ 0 1 ]
625
+
626
+ 其逆矩阵为:
627
+ T^-1 = [ R^T -R^T·t ]
628
+ [ 0 1 ]
629
+
630
+ 返回:
631
+ 逆变换的外参对象(保持相同的euler_order)
632
+
633
+ 示例:
634
+ >>> ext = ExtrinsicParam(euler=[30, 45, 60], translation=[1, 2, 3])
635
+ >>> ext_inv = ext.inverse()
636
+ >>> result = ext @ ext_inv # 应该接近单位变换
637
+ """
638
+ inv_rotation = self.rotation_matrix.T # R^T
639
+ inv_translation = -inv_rotation @ self.translation # -R^T·t
640
+ return ExtrinsicParam(rotation_matrix=inv_rotation, translation=inv_translation, euler_order=self.euler_order)
641
+
642
+ def transform_point(self, point: Union[list, np.ndarray]) -> np.ndarray:
643
+ """
644
+ 坐标前向变换: P_out = R * P_in + T
645
+
646
+ 参数:
647
+ point: 输入点坐标 [x, y, z]
648
+
649
+ 返回:
650
+ 变换后的点坐标,shape (3,)
651
+
652
+ 示例:
653
+ >>> ext = ExtrinsicParam(euler=[0, 0, 90], translation=[1, 0, 0])
654
+ >>> point_in = [1, 0, 0]
655
+ >>> point_out = ext.transform_point(point_in)
656
+ """
657
+ point = np.array(point, dtype=float)
658
+ return self.rotation_matrix @ point + self.translation
659
+
660
+ def inverse_transform_point(self, point: Union[list, np.ndarray]) -> np.ndarray:
661
+ """
662
+ 坐标逆变换: P_in = R^T * (P_out - T)
663
+
664
+ 参数:
665
+ point: 输出点坐标 [x', y', z']
666
+
667
+ 返回:
668
+ 逆变换后的点坐标,shape (3,)
669
+
670
+ 示例:
671
+ >>> ext = ExtrinsicParam(euler=[0, 0, 90], translation=[1, 0, 0])
672
+ >>> point_out = [1, 1, 0]
673
+ >>> point_in = ext.inverse_transform_point(point_out)
674
+ """
675
+ point = np.array(point, dtype=float)
676
+ return self.rotation_matrix.T @ (point - self.translation)
677
+
678
+ def get_homogeneous_matrix(self) -> np.ndarray:
679
+ """
680
+ 获取4x4齐次变换矩阵
681
+
682
+ 返回:
683
+ 4x4齐次矩阵:
684
+ [[R11, R12, R13, tx],
685
+ [R21, R22, R23, ty],
686
+ [R31, R32, R33, tz],
687
+ [ 0, 0, 0, 1]]
688
+
689
+ 示例:
690
+ >>> ext = ExtrinsicParam(euler=[30, 45, 60], translation=[1, 2, 3])
691
+ >>> T = ext.get_homogeneous_matrix()
692
+ >>> print(T.shape) # (4, 4)
693
+ """
694
+ T = np.eye(4)
695
+ T[:3, :3] = self.rotation_matrix
696
+ T[:3, 3] = self.translation
697
+ return T
698
+
699
+ def get_axis_angle(self) -> Tuple[np.ndarray, float]:
700
+ """
701
+ 获取角轴表示
702
+
703
+ 返回:
704
+ axis: 旋转轴单位向量,shape (3,)
705
+ angle: 旋转角度,单位弧度
706
+
707
+ 示例:
708
+ >>> ext = ExtrinsicParam(euler=[30, 0, 0], translation=[0, 0, 0])
709
+ >>> axis, angle = ext.get_axis_angle()
710
+ >>> print(f"旋转轴: {axis}, 旋转角: {np.rad2deg(angle)}度")
711
+ """
712
+ return self.axis, self.angle
713
+
714
+ def get_axis_angle_degrees(self) -> Tuple[np.ndarray, float]:
715
+ """
716
+ 获取角轴表示(角度单位为度)
717
+
718
+ 返回:
719
+ axis: 旋转轴单位向量,shape (3,)
720
+ angle: 旋转角度,单位度,范围[-180, 180]
721
+
722
+ 示例:
723
+ >>> ext = ExtrinsicParam(euler=[30, 0, 0], translation=[0, 0, 0])
724
+ >>> axis, angle = ext.get_axis_angle_degrees()
725
+ >>> print(f"旋转轴: {axis}, 旋转角: {angle}度")
726
+ """
727
+ angle_deg = np.rad2deg(self.angle)
728
+ angle_deg = normalize_angle(angle_deg)
729
+ return self.axis, angle_deg
730
+
731
+ def get_quaternion(self) -> np.ndarray:
732
+ """
733
+ 获取四元数表示 [x, y, z, w]
734
+
735
+ 返回:
736
+ 四元数 [x, y, z, w],shape (4,)
737
+
738
+ 示例:
739
+ >>> ext = ExtrinsicParam(euler=[30, 45, 60], translation=[0, 0, 0])
740
+ >>> quat = ext.get_quaternion()
741
+ >>> print(f"四元数: {quat}")
742
+ """
743
+ return self.quaternion
744
+
745
+ def get_standard_euler(self, order: Optional[str] = None) -> np.ndarray:
746
+ """
747
+ 获取标准欧拉角
748
+
749
+ 参数:
750
+ order: 旋转顺序,如果为None则使用self.euler_order,否则使用指定的order
751
+
752
+ 返回:
753
+ 欧拉角 [roll, pitch, yaw],单位度,范围[-180, 180]
754
+
755
+ 示例:
756
+ >>> ext = ExtrinsicParam(quaternion=[0, 0, 0, 1], translation=[0, 0, 0])
757
+ >>> euler = ext.get_standard_euler()
758
+ >>> print(f"标准欧拉角(RPY): {euler}")
759
+ >>> euler_xyz = ext.get_standard_euler(order='XYZ')
760
+ >>> print(f"标准欧拉角(XYZ顺序): {euler_xyz}")
761
+ """
762
+ if order is None:
763
+ return self.standard_euler
764
+ else:
765
+ return np.array(rotation_matrix_to_standard_euler(self.rotation_matrix, order))
766
+
767
+ def __repr__(self) -> str:
768
+ """
769
+ 字符串表示
770
+
771
+ 返回:
772
+ 对象的字符串描述
773
+ """
774
+ return (f"ExtrinsicParam(\n"
775
+ f" euler_order='{self.euler_order}',\n"
776
+ f" standard_euler(RPY)={self.standard_euler},\n"
777
+ f" translation={self.translation}\n"
778
+ f")")
@@ -0,0 +1,60 @@
1
+ Metadata-Version: 2.4
2
+ Name: transform-base
3
+ Version: 0.0.2
4
+ Summary: 欧拉角、旋转矩阵、四元数、角轴及外参变换基础库,支持多种旋转顺序
5
+ License-Expression: MIT
6
+ Requires-Python: >=3.10
7
+ Description-Content-Type: text/markdown
8
+ Requires-Dist: numpy>=1.20
9
+ Provides-Extra: dev
10
+ Requires-Dist: pytest; extra == "dev"
11
+ Requires-Dist: build; extra == "dev"
12
+ Requires-Dist: twine; extra == "dev"
13
+
14
+ # transform-base
15
+
16
+ 欧拉角、旋转矩阵、四元数、角轴及外参变换基础库,支持多种旋转顺序(XYZ/XZY/YXZ/YZX/ZXY/ZYX)。
17
+
18
+ ## 安装
19
+
20
+ ```bash
21
+ pip install transform-base
22
+ ```
23
+
24
+ ## 功能特性
25
+
26
+ - **角度归一化**:`normalize_angle` 将角度归一化到 [-180, 180]
27
+ - **欧拉角 ↔ 旋转矩阵**:支持 6 种旋转顺序(order),默认 ZYX
28
+ - **旋转矩阵 ↔ 四元数**:`[x, y, z, w]` 格式
29
+ - **旋转矩阵 ↔ 角轴**:Rodrigues 公式
30
+ - **外参类 ExtrinsicParam**:欧拉/旋转矩阵/四元数/角轴多种初始化,齐次矩阵、点变换、复合与逆
31
+
32
+ ## 快速开始
33
+
34
+ ```python
35
+ from transform_base import (
36
+ normalize_angle,
37
+ standard_euler_to_rotation_matrix,
38
+ rotation_matrix_to_standard_euler,
39
+ ExtrinsicParam,
40
+ )
41
+
42
+ # 欧拉角 -> 旋转矩阵 -> 欧拉角
43
+ euler = [10.0, 20.0, 30.0] # roll, pitch, yaw (度)
44
+ R = standard_euler_to_rotation_matrix(*euler, order="ZYX")
45
+ euler_back = rotation_matrix_to_standard_euler(R, order="ZYX")
46
+
47
+ # 外参:欧拉角 + 平移
48
+ ext = ExtrinsicParam(euler=[0, 0, 90], translation=[1, 0, 0])
49
+ p_out = ext.transform_point([1, 0, 0])
50
+ ext_inv = ext.inverse()
51
+ ```
52
+
53
+ ## 依赖
54
+
55
+ - Python >= 3.10
56
+ - numpy >= 1.20
57
+
58
+ ## License
59
+
60
+ MIT
@@ -0,0 +1,10 @@
1
+ MANIFEST.in
2
+ README.md
3
+ pyproject.toml
4
+ transform_base/__init__.py
5
+ transform_base/transform.py
6
+ transform_base.egg-info/PKG-INFO
7
+ transform_base.egg-info/SOURCES.txt
8
+ transform_base.egg-info/dependency_links.txt
9
+ transform_base.egg-info/requires.txt
10
+ transform_base.egg-info/top_level.txt
@@ -0,0 +1,6 @@
1
+ numpy>=1.20
2
+
3
+ [dev]
4
+ pytest
5
+ build
6
+ twine
@@ -0,0 +1,2 @@
1
+ dist
2
+ transform_base