Android:SensorManager.getRotationMatrix 和 SensorManager.getOrientation() 的算法

2024-01-11

要在 Android 中获取欧拉角(例如俯仰角、横滚角、方位角)的方向,需要执行以下操作:

  1. SensorManager.getRotationMatrix(float[] R, float[] I, float[] 重力, float[] 地磁);
  2. SensorManager.getOrientation(float[] R, float[] 方向);

在第一个中,我意识到它使用了一种TRIAD算法;旋转矩阵(R[])由重力、地磁X重力、重力X(地磁X重力)---X为叉积组成。
请参阅下面的代码:

    float Ax = gravity[0];
    float Ay = gravity[1];
    float Az = gravity[2];
    final float Ex = geomagnetic[0];
    final float Ey = geomagnetic[1];
    final float Ez = geomagnetic[2];
    float Hx = Ey*Az - Ez*Ay;
    float Hy = Ez*Ax - Ex*Az;
    float Hz = Ex*Ay - Ey*Ax;
    final float normH = (float)Math.sqrt(Hx*Hx + Hy*Hy + Hz*Hz);
    if (normH < 0.1f) {
        // device is close to free fall (or in space?), or close to
        // magnetic north pole. Typical values are  > 100.
        return false;
    }
    final float invH = 1.0f / normH;
    Hx *= invH;
    Hy *= invH;
    Hz *= invH;
    final float invA = 1.0f / (float)Math.sqrt(Ax*Ax + Ay*Ay + Az*Az);
    Ax *= invA;
    Ay *= invA;
    Az *= invA;
    final float Mx = Ay*Hz - Az*Hy;
    final float My = Az*Hx - Ax*Hz;
    final float Mz = Ax*Hy - Ay*Hx;
    if (R != null) {
        if (R.length == 9) {
            R[0] = Hx;     R[1] = Hy;     R[2] = Hz;
            R[3] = Mx;     R[4] = My;     R[5] = Mz;
            R[6] = Ax;     R[7] = Ay;     R[8] = Az;
        } else if (R.length == 16) {
            R[0]  = Hx;    R[1]  = Hy;    R[2]  = Hz;   R[3]  = 0;
            R[4]  = Mx;    R[5]  = My;    R[6]  = Mz;   R[7]  = 0;
            R[8]  = Ax;    R[9]  = Ay;    R[10] = Az;   R[11] = 0;
            R[12] = 0;     R[13] = 0;     R[14] = 0;    R[15] = 1;
        }
    }

但是,我无法理解 SensorManager.getOrientation()。

 azimuth = (float)Math.atan2(R[1], R[4]);
 pitch = (float)Math.asin(-R[7]);
 roll = (float)Math.atan2(-R[6], R[8]);

获得欧拉角的确切算法是什么?


我将给出几何解释getOrientation并解释如何仅使用计算所有类型的旋转RotationMatrix。如果你明白什么getOrientation那么就没有必要使用它,如果你不这样做,那么使用它会给你带来各种麻烦。

具体来说,它将回答 Stack Exchange 上发布的许多有关方向的问题,例如

  • 在纵向模式下为平板电脑编写指南针。为什么打电话后getRotation需要加上90度才能得到正确答案。
  • 为什么RemapCoordinateSystem必须在调用之前调用getOrientation获取后置摄像头的方向(设备 z 轴的负方向)。
  • 为什么打电话后RemapCoordinateSystem进而getOrientation为了正确获取设备 z 轴的方向,当设备平坦时,结果不再有意义。
  • 如何计算绕任何设备轴的旋转,与设备位置无关,即平坦或不平坦。

首先我需要解释一下RotationMatrix在解释之前更详细地说明它可以做什么getOrientation

这里需要考虑 2 个坐标系。
一种是世界坐标系,x轴指向东方,y轴指向北方,z轴指向天空。
另一种是设备坐标系,其中 x 轴是手机上的短边(平板电脑上的长边),y 轴是手机上的长边(平板电脑上的短边),z 轴是正交向量到屏幕上指着你。

从数学上讲,我们的对象是 3 维实向量空间,我们对该向量空间使用以下基数。

世界基础W = {E, N, SKY} where
E是位于东方向的单位向量
N是位于北方向的单位向量
SKY是位于天空方向的单位向量

设备基础D = {X, Y, Z} where
X是位于手机短边方向的单位向量(平板电脑为长边)
Y是位于手机长边方向(平板电脑为短边)的单位向量
Z是一个单位向量,位于与指向您的屏幕正交的方向上

对于那些忘记什么是基的人来说,它意味着每个向量v在3个空间中可以写成

以世界为基础
v = a_1 E + a_2 N + a_3 SKY其中 a_1、a_2、a_3 是实数
通常写为 (a_1, a_2, a_3)_W 或只是 (a_1, a_2, a_3)。这 3 个元组称为坐标v相对于世界基础或坐标v隐含的基础被理解为世界基础

在设备基础上
v = b_1 X + b_2 Y + b_3 Z其中 b_1、b_2、b_3 是实数
通常写为 (b_1, b_2, b_3)_D 或只是 (b_1, b_2, b_3)。这 3 个元组称为坐标v相对于设备基础或只是坐标v基础隐含地理解为设备基础。传感器返回的值以设备为基础,例如以设备为基础编写的加速度计返回为值[0]、值[1]和值[2]
acc= 值[0]X+ 价值观[1]Y+ 价值观[2]Z

尤其
X = 1 X + 0 Y + 0 Z
Y = 0 X + 1 Y + 0 Z
Z = 0 X + 0 Y + 1 Z

这是坐标X, Y and Z相对于设备基础分别为 (1, 0, 0)、(0, 1, 0) 和 (0, 0, 1)。您将看到如何使用这些向量来解释计算getOrientation later.

请注意,世界基准是固定的,但设备基准会随着手机位置的变化而变化。这就是单位向量X, Y, Z随着设备位置的变化而变化。它们的定义方式相同,但当位置改变时它们是不同的向量。你仍然把它们写成X, Y, Z但他们不同X, Y, Z.
因此,如果手机静止不动,作用在手机上的唯一力就是重力,因此理论上加速度计矢量是位于世界天空轴上的矢量,即在世界基础上的坐标为 (0, 0, g)。在设备基础上,对于某些 a_1、a_2 和 a_3 来说,它是 (a_1, a_2, a_3)。如果设备以不同的方向静止,则加速度计仍然相同,即世界基准中的 (0, 0, g),但现在设备基准中的 (b_1, b_2, b_3),其中至少有一个 a 与b 的。

其中参数为getRotationMatrixgravity and geomagnetic. gravity被假定为真实的gravity也就是说,它的坐标是世界基准中的 (0, 0, k),并且假设地磁位于世界 N-Sky 平面上,也就是说,它的坐标是世界基准中的 (0, a, b)。

有了这些假设,让我们看看如何Rotation Matrix计算为getRotationMatrix。这个方法尝试的是获得一个矩阵M这样,给定设备基础上坐标为 (a_1, a_2, a_3) 的任何向量的乘积M(a_1, a_2, a_3)_T(转置)给出世界基础上的坐标(b_1, b_2, b_3)。从数学上来说getRotationMatrix计算基矩阵的变化。

传入的参数为gravity g应该是从获得的值onSensorChanged for TYPE_GRAVITY或低通滤波器TYPE_ACCELEROMETER并为geomagnetic m应该是来自的值TYPE_MAGNETIC_FIELD

g = a_1 X + a_2 Y + a_3 Z
m = m_1 X + m_2 Y + m_3 Z

不失一般性假设g and m已经正常化了,这是常态g and m等于 1。 我们现在要做的是编写World基础{E, N, SKY}在设备基础上。

Since g假设为重力,即世界基础中的 (0, 0, 1) 这正是向量SKY.

SKY = g = a_1 X + a_2 Y + a_3 Z
这就是坐标SKY在设备基础中是(a_1,a_2,a_3)

现在自从m假设位于世界 N-SKY 平面上的叉积m and g是与世界 N-SKY 平面正交并指向右侧的单位向量,因此这是 E。

E = m x g = b_1 X + b_2 Y + b_3 Z

这就是坐标E相对于设备基础是 (b_1, b_2, b_3) b 是由叉积产生的值m and g

最后得到叉积SKY and E是与 E-SKY 平面正交的向量,并且是N

N = SKY x E = c_1 X + c_2 Y + c_3 Z

放在一起我们有

E = b_1 X + b_2 Y + b_3 Z
N = c_1 X + c_2 Y + c_3 Z
SKY = a_1 X + a_2 Y + a_3 Z

因此,给定世界基础上的任何坐标,我们都可以找到设备基础上的坐标。例如 (1, 2, 3) 是一个向量v

v = E + 2 N + 3 SKY

写入设备坐标将通过替换并相乘

v = b_1 X + b_2 Y + b_3 Z+ 2 (c_1X + c_2 Y + c_3 Z) + 3 (a_1X + a_2 Y + a_3 Z)
v= (b_1 + 2 c_1 + 3 a_1)X+ (b_2 + 2 c_2 + 3 a_2)Y+ (b_3 + 2 c_3 + 3 a_3)Z

但我们真正感兴趣的是相反的情况,即给定设备基础中的任何坐标,找到世界基础中的坐标。对于那些不记得线性代数的人来说,在上面我们有 3 个未知数的 3 个方程,因此我们应该能够解出这个方程X, Y and Z在...方面E, N and SKY.

从数学上讲,将上面的a、b、c放在列中得到的矩阵是基矩阵从World基到Device基的变化,因此我们需要找到它的逆矩阵。但这个矩阵是一个正交矩阵,因此它的逆矩阵只是它的转置。

用代码写的

a[0]  a[1]  a[2]
a[3]  a[4]  a[5]
a[6]  a[7]  a[8]

给定设备基础中的任何坐标 (a_1, a_2, a_3),我们可以通过上面矩阵与 (a_1, a_2, a_3) 转置的乘积找到世界基础中的坐标 (b_1, b_2, b_3)。

尤其

a[0]  a[1]  a[2]      1       a[0]
a[3]  a[4]  a[5]  x   0   =   a[3]
a[6]  a[7]  a[8]      0       a[6]

因此坐标为X在世界基础上是 (a[0], a[3], a[6])

a[0]  a[1]  a[2]      0       a[1]
a[3]  a[4]  a[5]  x   1   =   a[4]
a[6]  a[7]  a[8]      0       a[7]

因此坐标为Y在世界基础上是 (a[1], a[4], a[7])

a[0]  a[1]  a[2]      0       a[2]
a[3]  a[4]  a[5]  x   0   =   a[5]
a[6]  a[7]  a[8]      1       a[8]

因此坐标为Z在世界基础上是 (a[2], a[5], a[8])

现在让我们看看是什么getOrientation does.

它的代码是

azimuth = (float)Math.atan2(R[1], R[4]);
pitch = (float)Math.asin(-R[7]);
roll = (float)Math.atan2(-R[6], R[8]);

文件说

value[0]:方位角,绕-Z轴旋转,即相反 Z轴方向。
值[1]:俯仰角,绕-X轴的旋转, 即X轴的相反方向。
value[2]: 滚动、旋转 Y 轴。

从字面意思看上面的文档,不考虑设备的位置是错误的。该文件的真实性是azimuth仅当设备平坦时。

在我们继续之前,请注意,所有计算都必须使用相对于同一基础的坐标来完成。例如,如果您想找到两个向量之间的角度,这两个向量的坐标必须相对于相同的基础。

现在,当您计算旋转时,您会隐含地理解旋转是与固定位置的偏差。也就是说,如果答案是 90 度,那么它与什么成 90 度?在这种情况下azimuth它与磁北呈90度,也就是说你计算出与磁北的偏差N向量。如果你不能阐明这个隐含的固定位置,你就会遇到很多麻烦。例如,音高的固定矢量是什么?卷?设备方向(纵向-横向)?

让我们看一下每个计算,看看它到底计算了什么。

For the azimuth官方计算是

azimuth = (float)Math.atan2(R[1], R[4]); 

让我们回到原路Y写在设备坐标中。如前所述,坐标Y设备基础中的坐标为 (0, 1, 0)Y在世界基础上是 (a[1], a[4], a[7])

投影在世界基础上的坐标Y进入世界 XY 平面的是 (a[1], a[4])。该投影向量与N向量是(绘制投影向量的图片并N如果你不明白)

Math.atan2(R[1], R[4]);

这正是计算azimuth.

Thus getOrientation计算设备 y 轴到世界 XY 平面的投影与世界北轴之间的角度。因此,如果垂直握住设备,则此计算没有意义,因为投影的 y 坐标始终为 0。从几何角度来看,如果您指向天空,则计算正北方向是没有意义的。此外,在这种情况下,绕 -Z 轴旋转意味着远离纵向旋转,因此文档不正确。

对于音高,官方计算是

pitch = (float)Math.asin(-R[7]);

投影在世界基础上的坐标Y进入世界 N-SKY 平面的向量为 (a[4], a[7]) 并且该投影向量与 的投影之间的角度Y向量进入世界 E-N 平面,该平面与 E-N 平面投影之间的角度相同Z向量进入Y-Z平面,重力向量为(再次自己画图)

Math.asin(-R[7]) 

因此,节距是设备 y 轴在世界 N-SKY 平面中的投影与Y进入E-N平面世界

同样地Roll是设备 x 轴在世界 X-SKY 平面中的投影与X进入世界E-N平面。

例如我之前指出的。

  • 在纵向模式下为平板电脑编写指南针。为什么打电话后getRotation需要加上90度才能得到正确答案。

在这种情况下,我们想要计算 x 轴到世界 XY 平面的投影之间的角度,因此需要添加 90 度,因为 x 轴和 y 轴之间的角度始终为 90 度。人们可以取而代之的是在世界基础上获得坐标X向量为 (a[0], a[3], a[6]) ,然后投影到世界 XY 平面以获得 (a[0], a[3]) 并进行计算 Math.atan2( R[0]、R[3])。

  • 为什么RemapCoordinateSystem必须在调用之前调用getOrientation获取后置摄像头的方向(设备 z 轴的负方向)。

在这种情况下,您想要计算 z 轴的方向,而不是 y 轴的方向。getOrientation计算。因此你必须打电话remapCoordinateSystem它将 Z 轴映射到 Y 轴。从几何角度来说,您所做的是将 z 轴旋转到 y 轴,现在 y 轴变为 -z 轴。因此,您需要对旋转矩阵中的 y 列取反以返回到原始定义的坐标系。您只需获取世界基础上的坐标即可-Z即(-a[2], -a[5], -a[8]),然后投影到世界XY平面得到(-a[2], -a[5])并进行计算。

Note:我第一次使用 Sensor 的课程是在 2011 年,在了解了getRotationMatrix and getOrientation为此,我创建了一个库来仅使用旋转矩阵进行计算。我没那么想remapCoordinateSystem and getOrientation是需要的,但我没有意识到它们不好用,直到我写下这个答案,因为它会给出正确的azymuth但随后给出了错误的值pitch. What remapCoordinateSystem所做的就是计算azymuth in getOrientation根据你想要计算的方向进行修正。但随后pitch值不正确,您必须向其添加 -90 度才能获得后置摄像头方向的正确值以及其他情况下的正确值。

如果我要写这个类,我会创建

float getPitch(float[] rotMatrix)   
float getRoll(float[] rotMatrix) 
float getOrientation(float[] rotMatrix, int axis_you_want_to_calculate) 

这样就不会有混乱,所有的答案都是正确的。

  • 为什么打电话后RemapCoordinateSystem进而getOrientation为了正确获取设备 z 轴的方向,当设备平坦时,结果不再有意义。

您正在尝试计算 -z 轴的正北方向,但现在它指向天空,因此计算不再有意义。

  • 如何计算绕任何设备轴的旋转,与设备位置无关,即平坦或不平坦。

明天我将在以下位置发布关于绕 z 轴旋转情况的答案获取 Android 设备的欧拉偏航角 https://stackoverflow.com/questions/35303111/get-euler-yaw-angle-of-an-android-device.

注意:如果您只想编写一个指南针应用程序并且设备始终是平坦的,那么 TYPE_ORIENTATION 仍然不错。

注2:我的绘画能力很差,因此无法发布任何图片来说明。我什至无法使用 Gimp 画一条直线(我按照说明按住 Shift 键,但我的线是参差不齐的)。如果有人擅长绘画并且愿意提供帮助,请发表评论,我会指导我想到的图片插图以及将它们放在哪里。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Android:SensorManager.getRotationMatrix 和 SensorManager.getOrientation() 的算法 的相关文章

随机推荐

  • 函数的返回值存储在哪里

    我读过一些关于堆栈 堆以及它们在程序执行中如何使用的文章 Here http www c sharpcorner com UploadFile rmcochran csharp memory01122006130034PM csharp m
  • 文件权限;我的 www-folder 内容应该归 www-data 所有吗?

    这可能是一个菜鸟问题 但在任何地方都找不到答案 我有一个问题 另一个文件权限问题 https stackoverflow com questions 3483832 another file permissions problem已经帮助我
  • 用 Python 解析化学公式

    我正在尝试解决这个问题 https leetcode com articles number of atoms approach 1 recursion accepted https leetcode com articles number
  • python str.index 时间复杂度

    为了找到字符串中子字符串的位置 需要一个简单的算法O n 2 时间 然而 使用一些有效的算法 例如KMP算法 https en wikipedia org wiki Knuth E2 80 93Morris E2 80 93Pratt al
  • 在Python中组合多个音频文件(带延迟)

    我希望在 Python 中组合一系列不同的音频文件 mp3 要求之一是我需要能够在每个文件的末尾指定延迟 为了说明这一点 例如 文件1 mp3 3秒 延迟 2秒 文件2 mp3 mp3 3秒 延迟 2秒 mp3 4秒 延迟 2秒 file3
  • 窃取焦点,因为 SetForegroundWindow 无法做到这一点

    我知道这听起来很邪恶 但我的意图根本不是这个 用户单击 延迟拍摄 并开始倒计时 在此期间他们会聚焦另一个应用程序 然后倒计时后用户希望我的应用程序重新获得焦点 SetForegroundWindow当它从 PID X 的应用程序运行而 PI
  • 未找到类 ZMQContext

    我在 Ubuntu 14 04 的虚拟机内的 nginx 1 4 6 和 php 5 5 上运行 Web 服务器 并且需要安装 ZeroMQ 扩展 我已按照以下说明进行操作ZMQ http zeromq org area download
  • RIP寄存器不改变

    为什么当我继续使用c和内联汇编打印堆栈和指令指针寄存器时它们不会改变 因为逻辑上其他程序同时运行 所以它们应该在打印时不断改变 操作系统和 CPU 一起工作 为进程 同时运行 提供 CPU 切片 实际上 他们通过分配时间片来虚拟化 CPU
  • 如何在多行中编写 f 字符串而不引入意外的空格? [复制]

    这个问题在这里已经有答案了 考虑以下代码片段 name1 Nadya name2 Jim def print string string f name1 n name2 print string print string 产生 Nadya
  • Monodevelop - 仅使用 sudo 运行

    我已经在我的 Debian amd64 jessie 构建上安装了 Mono 和 Monodevelop 并且我只能使用提升的权限运行 monodevelop 从 UI startesque 菜单启动 monodevelop 似乎什么也没发
  • 带有位置参数的 Git 别名

    基本上我正在尝试别名 git files 9fa3 执行命令 git diff name status 9fa3 9fa3 但 git 似乎没有将位置参数传递给别名命令 我努力了 alias files git diff name stat
  • 为什么 Apache 没有在 XAMPP 上启动 [关闭]

    Closed 这个问题是无关 help closed questions 目前不接受答案 直到昨天 我的本地主机一切都很好 但从昨天开始 本地主机无法打开 它说 无法连接 我尝试了很多次来启动Apache on XAMPP 但它说消息忙 我
  • .NET .config 文件中 ConnectionString 元素的用途

    在中存储和读取应用程序的连接字符串有什么区别
  • 转移 PyPI 包的所有权

    As per PEP 541 https www python org dev peps pep 0541 现在可以认领废弃的 PyPI 项目 有人这样做过吗 联系谁 我尝试过dist utils 邮件列表 https mail pytho
  • Get-EventLog - 某些事件日志源缺少有效消息

    我正在使用 get eventlog 提取和过滤系统事件日志数据 我发现 get event log 无法正确返回与某些条目关联的消息 这些条目通常显示在事件日志查看器中 例如 get eventlog logname system sou
  • Python 模拟多个具有不同结果的调用

    我希望能够对特定属性函数进行多次调用 为每次连续调用返回不同的结果 在下面的示例中 我希望增量在第一次调用时返回 5 然后在第二次调用时返回 10 Ex import mock class A def init self self size
  • OpenCV 和 VS2010:致命错误 LNK1104:致命错误 LNK1104:无法打开文件“tbb_debug.lib”

    我尝试按照本指南使用 Visual Studio C 2010 安装 OpenCV 使用 Windows 7 64 位 在 Visual C 2010 Express 中安装 OpenCV 2 4 3 https stackoverflow
  • Django 独立脚本

    我正在尝试从另一个 python 脚本访问我的 Django v1 10 应用程序数据库 但遇到了一些问题 这是我的文件和文件夹结构 store store init py settings py urls py wsgi py store
  • 面向对象的设计建议

    这是我的代码 class Soldier public Soldier const string name const Gun gun string getName private Gun gun string name class Gun
  • Android:SensorManager.getRotationMatrix 和 SensorManager.getOrientation() 的算法

    要在 Android 中获取欧拉角 例如俯仰角 横滚角 方位角 的方向 需要执行以下操作 SensorManager getRotationMatrix float R float I float 重力 float 地磁 SensorMan