这篇文章的目标是基于C++和Python,使用一些常用的库,将旋转矩阵,齐次变换矩阵,欧拉角,四元数等之间进行两两的相互转换,并能够作为长期可复用的库来进行使用
目标:
- 统一约定(坐标系、欧拉顺序、四元数形式等)
- C++:基于 Eigen(机器人/控制/SLAM 领域最常用)
- Python:基于 NumPy + SciPy Rotation(可选 ROS 的 tf,但先给通用版)
- 提供一组简洁、可直接粘贴的函数,带短注释,方便做成你的"姿态库"。
你可以直接把"核心约定 + 代码段"收进自己的项目中。
0. 全局统一约定(极其重要)
下面所有 C++ / Python 代码都遵循同一套约定:
-
右手坐标系:
- x 轴:前
- y 轴:左
- z 轴:上
(典型移动机器人/航空/SLAM 约定)
-
欧拉角定义:
roll:绕 x 轴旋转pitch:绕 y 轴旋转yaw:绕 z 轴旋转- 采用 Z-Y-X 顺序(yaw, pitch, roll) ,即
先绕 x(roll),再绕 y(pitch),再绕 z(yaw)的本体旋转 等价于
旋转矩阵:R = Rz(yaw) * Ry(pitch) * Rx(roll)
-
四元数形式:
- 使用数学常用表示:
q = (w, x, y, z) - 内部库如 Eigen / SciPy 可能用
(x, y, z, w),我们在封装函数里统一成(w, x, y, z)接口。
- 使用数学常用表示:
-
旋转矩阵 R:
- 3×3 正交矩阵,列向量为坐标轴在世界坐标中的方向
- 满足
v_world = R * v_body(列向量右乘的习惯)
-
齐次变换矩阵 T (4×4):
- T 表示从 A 坐标系到 B 坐标系的刚体变换
- 形式:
T=[Rt01] T = \begin{bmatrix} R & t \\ 0 & 1 \end{bmatrix} T=[R0t1] - 向量变换:
pBhom=TBA pAhom p_B^{hom} = T_{BA} \, p_A^{hom} pBhom=TBApAhom
即 TBAT_{BA}TBA 表示"从 A 到 B"的变换(这一点你可以按自己项目统一命名)。
1. C++ 版(基于 Eigen)的统一接口
1.1 头文件示例:pose_utils_eigen.h
cpp
#pragma once
#include <Eigen/Core>
#include <Eigen/Geometry>
// 约定:
// - 欧拉角顺序 Z-Y-X:R = Rz(yaw) * Ry(pitch) * Rx(roll)
// - 欧拉角单位:弧度
// - 四元数接口形式: (w, x, y, z)
// - 旋转矩阵 R: 3x3, v_world = R * v_body
// - 齐次变换 T: 4x4 = [R t; 0 1]
// -----------------------------
// 基本类型别名
// -----------------------------
using Vec3 = Eigen::Vector3d;
using Mat3 = Eigen::Matrix3d;
using Mat4 = Eigen::Matrix4d;
using Quat = Eigen::Quaterniond; // 内部存 (x, y, z, w)
// =============================
// R <-> 欧拉角
// =============================
// 欧拉角 -> 旋转矩阵
// 输入: roll, pitch, yaw (rad)
// 输出: R = Rz(yaw) * Ry(pitch) * Rx(roll)
inline Mat3 euler_to_rot(double roll, double pitch, double yaw)
{
Mat3 R;
R = Eigen::AngleAxisd(yaw, Vec3::UnitZ()) *
Eigen::AngleAxisd(pitch, Vec3::UnitY()) *
Eigen::AngleAxisd(roll, Vec3::UnitX());
return R;
}
// 旋转矩阵 -> 欧拉角
// 返回: (roll, pitch, yaw)
inline Eigen::Vector3d rot_to_euler(const Mat3& R)
{
// eulerAngles(2,1,0) -> [yaw, pitch, roll] for ZYX
Eigen::Vector3d euler = R.eulerAngles(2, 1, 0);
double yaw = euler[0];
double pitch = euler[1];
double roll = euler[2];
return Eigen::Vector3d(roll, pitch, yaw);
}
// =============================
// R <-> 四元数
// =============================
// 四元数(qw,qx,qy,qz) -> 旋转矩阵
inline Mat3 quat_to_rot(double w, double x, double y, double z)
{
Quat q(x, y, z, w); // Eigen 构造: (x, y, z, w)
q.normalize();
return q.toRotationMatrix();
}
// 旋转矩阵 -> 四元数(qw,qx,qy,qz)
inline Eigen::Vector4d rot_to_quat(const Mat3& R)
{
Quat q(R);
q.normalize();
return Eigen::Vector4d(q.w(), q.x(), q.y(), q.z());
}
// =============================
// 欧拉角 <-> 四元数
// =============================
// 欧拉角 -> 四元数(qw,qx,qy,qz)
inline Eigen::Vector4d euler_to_quat(double roll, double pitch, double yaw)
{
Mat3 R = euler_to_rot(roll, pitch, yaw);
return rot_to_quat(R);
}
// 四元数(qw,qx,qy,qz) -> 欧拉角(roll,pitch,yaw)
inline Eigen::Vector3d quat_to_euler(double w, double x, double y, double z)
{
Mat3 R = quat_to_rot(w, x, y, z);
return rot_to_euler(R);
}
// =============================
// 齐次变换 T <-> (R, t)
// =============================
// (R, t) -> 齐次变换矩阵 T
// T = [R t; 0 1]
inline Mat4 rt_to_T(const Mat3& R, const Vec3& t)
{
Mat4 T = Mat4::Identity();
T.topLeftCorner<3,3>() = R;
T.topRightCorner<3,1>() = t;
return T;
}
// 齐次变换矩阵 T -> (R, t)
inline void T_to_rt(const Mat4& T, Mat3& R, Vec3& t)
{
R = T.topLeftCorner<3,3>();
t = T.topRightCorner<3,1>();
}
// =============================
// T <-> (欧拉角, t)
// =============================
// (欧拉角, t) -> T
inline Mat4 euler_t_to_T(double roll, double pitch, double yaw,
double tx, double ty, double tz)
{
Mat3 R = euler_to_rot(roll, pitch, yaw);
Vec3 t(tx, ty, tz);
return rt_to_T(R, t);
}
// T -> (欧拉角, t)
// 输出: roll,pitch,yaw 与 tx,ty,tz
inline void T_to_euler_t(const Mat4& T,
double& roll, double& pitch, double& yaw,
double& tx, double& ty, double& tz)
{
Mat3 R;
Vec3 t;
T_to_rt(T, R, t);
Eigen::Vector3d rpy = rot_to_euler(R);
roll = rpy[0];
pitch = rpy[1];
yaw = rpy[2];
tx = t[0];
ty = t[1];
tz = t[2];
}
// =============================
// T <-> (四元数, t)
// =============================
// (四元数, t) -> T
inline Mat4 quat_t_to_T(double w, double x, double y, double z,
double tx, double ty, double tz)
{
Mat3 R = quat_to_rot(w, x, y, z);
Vec3 t(tx, ty, tz);
return rt_to_T(R, t);
}
// T -> (四元数, t)
inline void T_to_quat_t(const Mat4& T,
double& w, double& x, double& y, double& z,
double& tx, double& ty, double& tz)
{
Mat3 R;
Vec3 t;
T_to_rt(T, R, t);
Eigen::Vector4d q = rot_to_quat(R);
w = q[0];
x = q[1];
y = q[2];
z = q[3];
tx = t[0];
ty = t[1];
tz = t[2];
}
你可以把这整个头文件直接收录到自己的 C++ 工程中作为公共工具。
2. Python 版(NumPy + SciPy Rotation)
假设你可以安装 SciPy。如果暂时没有 SciPy,也可以只用 NumPy + 手写公式(可以再补)。
2.1 依赖安装
bash
pip install numpy scipy
2.2 模块示例:pose_utils.py
python
"""
统一的姿态/位姿转换工具
约定:
- 右手系
- 欧拉角(roll, pitch, yaw), 旋转顺序 Z-Y-X:
R = Rz(yaw) * Ry(pitch) * Rx(roll)
- 四元数接口形式 (w, x, y, z)
- 旋转矩阵 R: shape (3,3), v_world = R @ v_body
- 齐次变换 T: shape (4,4) = [R t; 0 1]
"""
import numpy as np
from scipy.spatial.transform import Rotation as R_ # 避免与变量名 R 冲突
# =============================
# R <-> 欧拉角
# =============================
def euler_to_rot(roll: float, pitch: float, yaw: float) -> np.ndarray:
"""
欧拉角 -> 旋转矩阵
输入单位: 弧度
顺序: Z-Y-X (yaw, pitch, roll)
"""
# SciPy: from_euler('zyx', [z, y, x]) = [yaw, pitch, roll]
r = R_.from_euler('zyx', [yaw, pitch, roll])
return r.as_matrix() # (3,3)
def rot_to_euler(R: np.ndarray) -> np.ndarray:
"""
旋转矩阵 -> 欧拉角
返回: [roll, pitch, yaw] (弧度)
"""
R = np.asarray(R, dtype=float).reshape(3, 3)
r = R_.from_matrix(R)
yaw, pitch, roll = r.as_euler('zyx') # 对应 Z-Y-X
return np.array([roll, pitch, yaw])
# =============================
# R <-> 四元数
# =============================
def quat_to_rot(q: np.ndarray) -> np.ndarray:
"""
四元数 -> 旋转矩阵
q: [w, x, y, z]
返回: R (3,3)
"""
q = np.asarray(q, dtype=float).flatten()
w, x, y, z = q
r = R_.from_quat([x, y, z, w]) # SciPy 使用 [x,y,z,w]
return r.as_matrix()
def rot_to_quat(R: np.ndarray) -> np.ndarray:
"""
旋转矩阵 -> 四元数
返回: [w, x, y, z]
"""
R = np.asarray(R, dtype=float).reshape(3, 3)
r = R_.from_matrix(R)
x, y, z, w = r.as_quat() # SciPy 返回 [x,y,z,w]
return np.array([w, x, y, z])
# =============================
# 欧拉角 <-> 四元数
# =============================
def euler_to_quat(roll: float, pitch: float, yaw: float) -> np.ndarray:
"""
欧拉角 -> 四元数
返回: [w, x, y, z]
"""
R = euler_to_rot(roll, pitch, yaw)
return rot_to_quat(R)
def quat_to_euler(q: np.ndarray) -> np.ndarray:
"""
四元数 -> 欧拉角
q: [w, x, y, z]
返回: [roll, pitch, yaw]
"""
R = quat_to_rot(q)
return rot_to_euler(R)
# =============================
# 齐次变换 T <-> (R, t)
# =============================
def rt_to_T(R: np.ndarray, t: np.ndarray) -> np.ndarray:
"""
(R, t) -> 齐次变换矩阵 T
R: (3,3)
t: (3,)
返回: T (4,4) = [R t; 0 1]
"""
R = np.asarray(R, dtype=float).reshape(3, 3)
t = np.asarray(t, dtype=float).reshape(3,)
T = np.eye(4)
T[:3, :3] = R
T[:3, 3] = t
return T
def T_to_rt(T: np.ndarray):
"""
齐次变换矩阵 T -> (R, t)
返回: R(3,3), t(3,)
"""
T = np.asarray(T, dtype=float).reshape(4, 4)
R = T[:3, :3]
t = T[:3, 3]
return R, t
# =============================
# T <-> (欧拉角, t)
# =============================
def euler_t_to_T(roll: float, pitch: float, yaw: float,
tx: float, ty: float, tz: float) -> np.ndarray:
"""
(欧拉角, t) -> T
"""
R = euler_to_rot(roll, pitch, yaw)
t = np.array([tx, ty, tz], dtype=float)
return rt_to_T(R, t)
def T_to_euler_t(T: np.ndarray):
"""
T -> (欧拉角, t)
返回:
roll, pitch, yaw, tx, ty, tz
"""
R, t = T_to_rt(T)
roll, pitch, yaw = rot_to_euler(R)
tx, ty, tz = t
return roll, pitch, yaw, tx, ty, tz
# =============================
# T <-> (四元数, t)
# =============================
def quat_t_to_T(q: np.ndarray, t: np.ndarray) -> np.ndarray:
"""
(四元数, t) -> T
q: [w, x, y, z]
t: (3,)
"""
R = quat_to_rot(q)
return rt_to_T(R, t)
def T_to_quat_t(T: np.ndarray):
"""
T -> (四元数, t)
返回:
q: [w, x, y, z], t: (3,)
"""
R, t = T_to_rt(T)
q = rot_to_quat(R)
return q, t
3. 转换关系一览(思维导图式)
在这个 C++ / Python 库中,实际上是围绕几条"主干"做封装:
欧拉角 ⇄ 旋转矩阵四元数 ⇄ 旋转矩阵(R, t) ⇄ T- 其他组合,例如:
欧拉角 ⇄ 四元数:通过欧拉角 -> R -> 四元数/四元数 -> R -> 欧拉角(欧拉角, t) ⇄ T:通过欧拉角 -> R+rt ⇄ T(四元数, t) ⇄ T:通过四元数 -> R+rt ⇄ T
这样一方面接口清晰,另一方面将来如果你要替换内部实现(比如用手写三角公式、用别的库),只要保证每条主干的语义不变即可。
4. 建议的项目结构与使用方式
你可以按如下方式组织你的"长期自用库":
C++
目录示例:
text
include/
pose_utils_eigen.h
src/
# 不一定需要单独 cpp,全部 inline 在头文件也可以
tests/
test_pose_utils.cpp
在项目中使用:
cpp
#include "pose_utils_eigen.h"
int main()
{
double roll = 0.1, pitch = 0.2, yaw = 0.3;
Eigen::Matrix3d R = euler_to_rot(roll, pitch, yaw);
Eigen::Vector4d q = rot_to_quat(R);
Eigen::Matrix4d T = euler_t_to_T(roll, pitch, yaw, 1.0, 2.0, 3.0);
return 0;
}
Python
目录示例:
text
pose_utils/
__init__.py
pose_utils.py
tests/
test_pose_utils.py
使用:
python
from pose_utils.pose_utils import euler_to_quat, quat_to_euler, euler_t_to_T
roll, pitch, yaw = 0.1, 0.2, 0.3
q = euler_to_quat(roll, pitch, yaw)
T = euler_t_to_T(roll, pitch, yaw, 1.0, 2.0, 3.0)
5. 后续可以扩展的方向(预留)
以后你可以在这套基础上继续加:
- 不同欧拉顺序 :例如 XYZ、ZXY 等,在函数名中显式写明,比如
euler_xyz_to_rot。 - 度/弧度转换 :提供
*_deg版本;或在函数里增加参数degrees=False/True。 - 与 ROS tf / tf2 的互操作 :在 C++ / Python 分别写
from_tf_quat/to_tf_quat,做(x,y,z,w)与(w,x,y,z)的适配。 - 转移矩阵方向区分 :例如
T_ab、T_ba一套函数,对逆变换进行封装。