跳转到内容
新建笔记

将 z 轴旋转到向量负方向:推导与退化情况

给定非零向量 vv,希望一个主动旋转矩阵 R 使原 z 轴 a=(0,0,1)Ta=(0,0,1)^T 指向 b=−v/∥v∥b=-v/\|v\|,即 Ra=bRa=b。本文保持世界参考系不变,旋转三根基向量;R 的三列就是旋转后的 x、y、z 轴。若要把世界中的坐标改写到旋转后的坐标系,使用的是 RTR^T。

只给定 z 轴方向不能唯一决定完整姿态,还可以绕新 z 轴任意旋转。这里额外选择转角最小的解;恰好反向时最小转角为 π,旋转轴仍不唯一。

对单位向量 a、b:

c=a⋅b=cos⁡θ,w=a×b,s=∥w∥=sin⁡θ.c=a\cdot b=\cos\theta,\quad w=a\times b,\quad s=\|w\|=\sin\theta.

当 s>0s>0,单位旋转轴 k=w/sk=w/s,转角取 θ=atan2⁡(s,c)\theta=\operatorname{atan2}(s,c)。令 K 为叉乘矩阵,满足 Kx=k×xKx=k\times x,则 Rodrigues 公式为:

R=I+sK+(1−c)K2.R=I+sK+(1-c)K^2.

原代码使用与此等价的 [w]×[w]_\times 形式,却除以 s2s^2,未处理平行向量,因而同向、反向时都会出现除零。

import numpy as np
def align_z_to_negative(vector):
v = np.asarray(vector, dtype=float)
if v.shape != (3,) or not np.all(np.isfinite(v)):
raise ValueError("vector must contain three finite numbers")
scale = np.max(np.abs(v))
if scale == 0:
raise ValueError("zero vector has no direction")
# 先缩放,避免对极大或极小输入直接求模时溢出/下溢。
v = v / scale
b = -v / np.linalg.norm(v)
a = np.array([0.0, 0.0, 1.0])
w = np.cross(a, b)
s = np.linalg.norm(w)
c = np.clip(np.dot(a, b), -1.0, 1.0)
if s < 1e-12:
if c > 0:
return np.eye(3)
# 反向时任选与 z 垂直的轴;这里绕 x 轴旋转 pi。
return np.diag([1.0, -1.0, -1.0])
k = w / s
K = np.array([[0.0, -k[2], k[1]],
[k[2], 0.0, -k[0]],
[-k[1], k[0], 0.0]])
return np.eye(3) + s * K + (1.0 - c) * (K @ K)

1e-12 是本例处理浮点近平行情况的容差,不是所有应用的统一要求。验证时检查 RTR≈IR^TR\approx I、det⁡R≈1\det R\approx1、Ra≈bRa\approx b。

若安装 SciPy,可由矩阵转换:

from scipy.spatial.transform import Rotation
R = align_z_to_negative([1, 1, 1])
rotation = Rotation.from_matrix(R)
q_xyzw = rotation.as_quat()
angles_xyz = rotation.as_euler("xyz", degrees=True)

as_quat() 默认返回标量在后的 (x,y,z,w),不应直接当作 (w,x,y,z) 传给别的库。小写 xyz 表示外禀旋转序列;欧拉角有奇异性,万向节锁时不能唯一分解三个角,但旋转矩阵仍可有效。四元数、欧拉角

原记录中旋转前后坐标轴方向的示意

本笔记由 CSDN 原始记录 的文字与图片重新整理;正文中的代码、适用条件与说明已按本次审读补充。