手机当罗盘:从俯仰横滚偏航三个角,算出任意平面的产状

一个看似简单的问题

把手机随手拍在一块倾斜的桌面、岩层面或者墙面上,不管手机是横着放、竖着放、还是斜着扭一个角度贴上去,只要它紧贴着那个平面,手机总能算出这个平面「朝哪个方向倾、倾多少度」。地质上把这两个量叫产状倾向(dip direction)倾角(dip)。手机里的「产状测量」「电子罗盘」App 干的就是这件事。

关键的直觉是:手机贴在平面上时,它自己的朝向是任意的(你可以随便转),但它背面(屏幕法线)永远垂直于那个平面。所以平面的产状只跟法线有关,跟手机怎么摆无关。这篇文章把这套逻辑从头推一遍,给出可复现的计算案例和可视化。

三个姿态角的英文说法

描述一个刚体在空间里的朝向,最常用的是三个欧拉角。中英文对照如下:

  • 俯仰角 —— Pitch,绕横轴(x 轴,机体的左右方向)旋转,抬头/低头。
  • 横滚角 —— Roll,绕纵轴(y 轴,机体的前后方向)旋转,左右翻滚。
  • 偏航角 —— Yaw,绕竖轴(z 轴,垂直机体平面)旋转,也就是航向(Heading)。

在航空航天里这三个词几乎是固定搭配。另外你还会见到几个近义词:航向角常写作 HeadingAzimuth(方位角);整体姿态叫 AttitudeOrientation;三个角合起来常缩写为 RPY(Roll-Pitch-Yaw)。手机传感器文档里则叫 Pitch / Roll / Azimuth

俯仰、横滚、偏航三个姿态角的定义,红绿蓝分别为机体 x/y/z 轴

坐标系与旋转约定

要把话说清楚,先固定两个坐标系。

世界系(导航系)用 ENU:x 轴指向正东(East),y 轴指向正北(North),z 轴指向天顶(Up)。这是本地水平坐标系的标准约定之一。

机体系(手机系):x 轴指向手机右侧,y 轴指向手机顶部,z 轴垂直屏幕向外——这正是手机加速度计/陀螺仪的常见轴向。

姿态就是一个旋转矩阵 $R$,它把机体系的坐标变换到世界系。$R$ 的三个列向量,恰好就是机体三个轴在世界系(ENU)里的方向。我们采用 Z-X-Y 的旋转次序(先偏航、再俯仰、后横滚):

$$R = R_z(\text{yaw})\, R_x(\text{pitch})\, R_y(\text{roll})$$

其中各基本旋转为:

$$R_x(\alpha)=\begin{bmatrix}1&0&0\0&\cos\alpha&-\sin\alpha\0&\sin\alpha&\cos\alpha\end{bmatrix},\quad R_y(\beta)=\begin{bmatrix}\cos\beta&0&\sin\beta\0&1&0\-\sin\beta&0&\cos\beta\end{bmatrix},\quad R_z(\gamma)=\begin{bmatrix}\cos\gamma&-\sin\gamma&0\\sin\gamma&\cos\gamma&0\0&0&1\end{bmatrix}$$

手机的姿态怎么来的?静止时,加速度计测得的是重力反方向(近似天顶方向),由此可解出 pitch 和 roll;磁力计测得地磁水平分量,配合前两个角度可解出 yaw(航向)。这三个角就是我们的输入。

核心一步:屏幕法线的方向

手机贴在平面上,手机的 z 轴(屏幕法线)就等于平面的法线。而机体 z 轴在世界系里的方向,正是旋转矩阵的第三列:

$$\mathbf{n} = R \begin{bmatrix}0\0\1\end{bmatrix} = R_{:,3}$$

把 $R = R_z R_x R_y$ 展开,第三列只跟 pitch、yaw 有关(roll 是绕 y 轴自身,不改变 y 轴、也就不改变第三列的方向):

$$\mathbf{n} = \begin{bmatrix} n_E \ n_N \ n_U \end{bmatrix} = \begin{bmatrix} \sin(\text{yaw})\cos(\text{pitch}) \ \cos(\text{yaw})\cos(\text{pitch}) \ \cos(\text{pitch}) \end{bmatrix}$$

这就是「怎么摆都一样」的数学原因:roll 不出现在法线公式里。你绕着屏幕法线随便自转(改变的是 roll 和一部分 yaw 的组合),只要 z 轴指向不变,法线就不变,产状自然不变。

从法线到产状(倾向与倾角)

有了单位法线 $\mathbf{n}=(n_E, n_N, n_U)$(约定朝上,若 $n_U<0$ 就整体取反),产状就是简单的几何:

倾角 是平面与水平面的夹角,等于法线与竖直方向的夹角:

$$\text{dip} = \arccos\left(n_U\right)$$

倾向 是平面「最陡下坡」方向的水平方位角,从正北起顺时针度量。最陡下坡方向是法线在水平面的投影的反方向,因此:

$$\text{dip direction} = \operatorname{atan2}\left(-n_E,\; -n_N\right) \bmod 360^\circ$$

反过来,已知产状也能重建法线,用于校验:设上倾方位角 $\phi = (\text{dip direction} + 180^\circ)$,则

$$\mathbf{n} = \big(\sin(\text{dip})\sin\phi,\; \sin(\text{dip})\cos\phi,\; \cos(\text{dip})\big)$$

关键演示:同一平面,两种任意摆法

下面这个 GIF 是全文的核心。同一个倾斜平面,手机绕着屏幕法线不停自转(模拟你「随便怎么贴」)。注意看:上面一行的 yaw / pitch / roll 一直在剧烈变化,而下面一行的 倾角、倾向纹丝不动

同一平面上手机任意摆放,姿态角在变,产状恒定不变

我们用一个具体的平面来跑数值——真实产状定为倾角 35°、倾向 120°,对应法线(ENU)为 $(-0.4967,\ 0.2868,\ 0.8192)$。然后用两种完全不同的摆法把手机贴上去:

摆法 yaw pitch roll 反算法线 (E,N,U) 倾角 倾向
摆法一(spin=0°) 60.00° −35.00° 0.00° (−0.4967, 0.2868, 0.8192) 35.00° 120.00°
摆法二(spin=137°) −168.70° 24.80° 25.53° (−0.4967, 0.2868, 0.8192) 35.00° 120.00°

两种摆法的三个欧拉角天差地别,但反算出来的法线一模一样,产状都精确回到 35°/120°。这正是手机「怎么放都能测」的底气。

两种摆法对应同一法线,得到相同产状

完整可复现代码

下面是从三个姿态角到产状的核心函数,以及上面案例的验证。可直接运行。

import numpy as np
r2d, d2r = np.rad2deg, np.deg2rad

def Rx(a): c,s=np.cos(a),np.sin(a); return np.array([[1,0,0],[0,c,-s],[0,s,c]])
def Ry(a): c,s=np.cos(a),np.sin(a); return np.array([[c,0,s],[0,1,0],[-s,0,c]])
def Rz(a): c,s=np.cos(a),np.sin(a); return np.array([[c,-s,0],[s,c,0],[0,0,1]])

def R_from_ypr(yaw, pitch, roll):
    return Rz(yaw) @ Rx(pitch) @ Ry(roll)      # 列 = 机体轴在 ENU 中的方向

def attitude_from_normal(n):
    n = n/np.linalg.norm(n)
    if n[2] < 0: n = -n                          # 法线取朝上
    dip = r2d(np.arccos(np.clip(n[2], -1, 1)))
    dip_dir = r2d(np.arctan2(-n[0], -n[1])) % 360
    return dip, dip_dir

def normal_from_attitude(dip, dip_dir):
    up = d2r((dip_dir + 180) % 360)
    return np.array([np.sin(d2r(dip))*np.sin(up),
                     np.sin(d2r(dip))*np.cos(up),
                     np.cos(d2r(dip))])

# 真实平面:倾角35°,倾向120°
n_true = normal_from_attitude(35, 120)

# 摆法一:yaw=60, pitch=-35, roll=0
R1 = R_from_ypr(d2r(60), d2r(-35), d2r(0))
print(attitude_from_normal(R1 @ [0,0,1.0]))   # -> (35.0, 120.0)

# 摆法二:yaw=-168.7, pitch=24.8, roll=25.53
R2 = R_from_ypr(d2r(-168.70), d2r(24.80), d2r(25.53))
print(attitude_from_normal(R2 @ [0,0,1.0]))   # -> (35.0, 120.0)

别忘了磁偏角与经纬度

上面算出来的倾向是相对手机航向的,而手机航向来自磁力计,指的是磁北。地质记录和地图用的是真北。两者之差就是磁偏角(magnetic declination),它随经纬度和年份变化,可由 WMM/IGRF 地磁模型按位置查得。校正很简单:

$$\text{倾向}{\text{真北}} = \big(\text{倾向}\big) \bmod 360^\circ$$}} + \text{磁偏角

以重庆(纬度 29.563°、经度 106.551°)为例,磁偏角约 −1.9°(西偏),则磁北倾向 120° 对应真北倾向 118.10°。

如果还想把这个法线放到全球统一的地心地固坐标系(ECEF)里——比如做跨区域的构造统计——就要用经纬度做一次 ENU→ECEF 旋转:

$$\begin{bmatrix}X\Y\Z\end{bmatrix}= \begin{bmatrix}-\sin\lambda & -\sin\varphi\cos\lambda & \cos\varphi\cos\lambda\ \cos\lambda & -\sin\varphi\sin\lambda & \cos\varphi\sin\lambda\ 0 & \cos\varphi & \sin\varphi\end{bmatrix} \begin{bmatrix}n_E\n_N\n_U\end{bmatrix}$$

其中 $\varphi$ 是纬度、$\lambda$ 是经度。同一块岩层面在重庆测得的法线,换算到 ECEF 就是 $(0.3135, 0.6889, 0.6536)$。这样,无论你在地球上哪个位置,任意时刻、任意摆法测到的产状,都能落到同一套全局坐标里比较。

小结

整条链路其实非常干净:

  1. 手机静止时用加速度计+磁力计解出 yaw / pitch / roll 三个姿态角;
  2. 组成旋转矩阵 $R$,取第三列得到屏幕法线 $\mathbf{n}$——这一步天然把 roll 消掉,所以怎么摆都行;
  3. 由法线用 $\arccos$ 和 $\operatorname{atan2}$ 直接读出倾角倾向
  4. 磁偏角把磁北换成真北,必要时用经纬度把法线搬进 ECEF 全局坐标。

一部手机、几行三角函数,就复刻了地质罗盘的核心功能。而它「随便怎么放都准」的秘密,不过是一句话:产状只认那根垂直于平面的法线,不认手机自己转了多少。

Category
Tagcloud
Translate Ollama LlamaFactory Data Science Photography GIS Nonlinear Filtering Photo Tool ML Bayesian LLM Education School Network Tools Lens Poem CLI Book OpenWebUI ChromeBook GameTheory Rotation Linux PVE Game University QGIS Parenting opencode Mac Ubuntu Pyenv VibeCoding Kivy OSX-KVM CUDA Attitude Windows Python Scholar Junck Turing Data Memory Imbalance Qwen3 Architecture FckZhiHu Earth Science Math Web Communicate Computability Science Hackintosh Life VirtualMachine Hadoop RTL-SDR Learn Code Generation GPT-OSS Brent History IDE Cognition Algorithm Optimization PHD Thinking TUI Camera GlumPy Golden Section Discuss Microscope OPC Coding Traffic Psychology Geology Fibonacci Visualization Hack Code IMU AI C Story Mathematical Modeling Job Search Hardware Filtering Signal Processing Radio Learning Programming
Apps