Android传感器管理器:奇怪的如何重新映射坐标系统

18

API演示->图形->指南针

只有在不更改设备自然方向时才能正常工作。大多数手机是竖屏,大多数10英寸平板电脑是横屏。如果您更改了方向,则需要将其旋转90度。我希望看到这个系统的三维解决方案。

100%确定需要使用remapCoordinateSystem()方法。

如果我能看到说明如何计算这些轴映射(理论数学)的代码,那就太好了。

我试图理解,但我忘记了所有线性代数知识。

在这里它说为什么我们必须使用,但没有告诉我们如何!

float R[] = new float[9];
// X  (product of Y and Z) and roughly points East
// Y: points to Magnetic NORTH and tangential to ground
// Z: points to SKY and perpendicular to ground
float I[] = new float[9];
boolean success = SensorManager.getRotationMatrix(R, I, mGravity, mGeomagnetic);

看起来这些坐标是指这个位置:- 设备在桌子上放置(x 和 y 轴在桌子上)

设备方向

只有当

getWindowManager().getDefaultDisplay().getRotation() == Surface.ROTATION_0

问题是如何完成这段代码:-那些 case 分支。
switch (mScreenRotation) {
    case Surface.ROTATION_0:
    Log.v("SurfaceRemap", "0 degree");
    axisX = SensorManager.AXIS_X;// is this valid?
    axisY = SensorManager.AXIS_Y;// is this valid?
    break;

    case Surface.ROTATION_90:
    Log.v("SurfaceRemap", "90 degree");
    // examples says remapCoordinateSystem(inR, AXIS_Y, AXIS_MINUS_X, outR);
    axisX = SensorManager.AXIS_Y;
    axisY = SensorManager.AXIS_MINUS_X;
    break;

    case Surface.ROTATION_180:
    Log.v("SurfaceRemap", "180 degree");
    break;

    case Surface.ROTATION_270:
    Log.v("SurfaceRemap", "270 degree");
    break;

    default:
    Log.v("SurfaceRemap", "don't know the mScreenRotation value: "+mScreenRotation+" you should never seen this message!");
    break;
}


boolean remapped = SensorManager.remapCoordinateSystem(R, axisX, axisY, R);

float orientation[] = new float[3];

SensorManager.getOrientation(R, orientation);// All three angles above are in radians and positive in the counter-clockwise direction.
inclination = SensorManager.getInclination(I);

编辑: 我编写了一个小测试应用程序,在屏幕上显示屏幕旋转:0、90、270度(现在无法进行180度的旋转)。

看起来,如果旋转0没有改变(axisX = SensorManager.AXIS_X; axisY = SensorManager.AXIS_Y;),那么90度应该是:

axisX = SensorManager.AXIS_MINUS_Y;
axisY = SensorManager.AXIS_X;

谷歌文档中存在错误数值!问题在哪里?!

getRotationMatrix 返回以下结果:

enter image description here

X被定义为向量积Y.Z(它切向设备当前位置的地面并大致指向东方)。

Y在设备当前位置的地面上切向并指向磁北极。

Z指向天空并垂直于地面。

看上面的手机!我想从左到右,背后相机对准地面。

getOrientation 返回以下结果:

enter image description here

X被定义为向量积Y.Z(它切向设备当前位置的地面并大致指向西方)。

Y在设备当前位置的地面上切向并指向磁北极。

Z指向地球中心并垂直于地面。

values[0]: 方位角,绕Z轴旋转。

values[1]: 俯仰角,绕X轴旋转。

values[2]: 横滚角,绕Y轴旋转。

手机应该怎么摆放?

最后我想要像飞机一样的角度数值。我的手机(我)朝向北方:(偏航角是方位角)

enter image description here

              if ScreenRotation = 0 degree
Pitch axis = -orientationAxisX  =  rotationAxisX
Roll axis  =  orientationAxisY  =  rotationAxisY 
Yaw axis   =  orientationAxisZ  = -rotationAxisZ
5个回答

27

为了完成分支切换,我只需遵循remapCoordinateSystem方法的javadoc:

X 定义设备的X轴映射到哪个世界坐标系和方向。
Y 定义设备的Y轴映射到哪个世界坐标系和方向。

因此,拿起您的设备,将其从自然方向(90、180或270度)旋转,并问自己:原始设备方向中的X正轴对应于当前设备方向中的哪个轴?Y轴同理。

例如,如果您的设备旋转了90度,您会发现原始的X正轴对应于当前正Y轴,而原始正Y轴对应于当前负X轴。

因此,正确的应该是:

switch (mScreenRotation) {
    case Surface.ROTATION_0:
        axisX = SensorManager.AXIS_X;
    axisY = SensorManager.AXIS_Y;
        break;

    case Surface.ROTATION_90:
        axisX = SensorManager.AXIS_Y;
    axisY = SensorManager.AXIS_MINUS_X;
        break;

    case Surface.ROTATION_180:
        axisX = SensorManager.AXIS_MINUS_X;
    axisY = SensorManager.AXIS_MINUS_Y;
        break;

    case Surface.ROTATION_270:
        axisX = SensorManager.AXIS_MINUS_Y;
    axisY = SensorManager.AXIS_X;
        break;

    default:
        break;
}

那对我起作用了,希望能帮到你。


我是独立想出这个的,所以一定是正确的。点个赞吧 ;) - Thomas
当手机平放在地面上时,它的表现非常出色。如果您需要查看相机(AR)时的值,则只需要使用axisX = SensorManager.AXIS_X; axisY = SensorManager.AXIS_Z;,然后将旋转添加到第三个值即可。 - Björn Kechel
@BjörnKechel 您的意思是使用AR,将axisZ = SensorManager.AXIS_Y添加到屏幕旋转中?因此,如果屏幕被旋转,方位角和俯仰角在相机(AR)位置仍然可以正常工作? - unlimited101
@unlimited101,我发布了一个新答案,其中包含更详细的AR描述。 - Björn Kechel

4

感谢keianhzo,你的答案对于手机平放在地面上非常有效。对于AR应用程序,在其中您通过显示屏“查看”时,我发现以下内容可行: 使用正确的轴:

int screenRotation = mActivity.getWindowManager().getDefaultDisplay().getRotation();
//use the correct axis
int axisX = SensorManager.AXIS_X;
int axisY = SensorManager.AXIS_Y;
switch (mMode) {
    case LOOK_THROUGH: {
        // look through always uses x and z
        axisX = SensorManager.AXIS_X;
        axisY = SensorManager.AXIS_Z;
        break;
    }
    case FLAT: {
        // flat changes the x axis depending on rotation state
        switch (screenRotation) {
            case Surface.ROTATION_0:
                axisX = SensorManager.AXIS_X;
                axisY = SensorManager.AXIS_Y;
                break;
            case Surface.ROTATION_90:
                axisX = SensorManager.AXIS_Y;
                axisY = SensorManager.AXIS_MINUS_X;
                break;
            case Surface.ROTATION_180:
                axisX = SensorManager.AXIS_MINUS_X;
                axisY = SensorManager.AXIS_MINUS_Y;
                break;
            case Surface.ROTATION_270:
                axisX = SensorManager.AXIS_MINUS_Y;
                axisY = SensorManager.AXIS_X;
                break;
            default:
                break;
        }
        break;
    }
    default:
        break;
}

获取方向角度:

boolean success = SensorManager.remapCoordinateSystem(getQuaternion().getMatrix4x4().getMatrix(), axisX, axisY, mRotationMatrixTransformed);
if (success) {
    SensorManager.getOrientation(mRotationMatrixTransformed, mOrientationValues);

    for (int i = 0; i < 3; i++) {
        mOrientationDegrees[i] = (float) Math.toDegrees(mOrientationValues[i]);
    }
//And for look through, add the rotation state
    if (mMode == MODE.LOOK_THROUGH) {
    // look through has different angles depending on rotation state
    switch (screenRotation) {
        case Surface.ROTATION_90: {
            mOrientationDegrees[2] += 90;
            break;
        }
        case Surface.ROTATION_180: {
            mOrientationDegrees[2] += 180;
            break;
        }
        case Surface.ROTATION_270: {
            mOrientationDegrees[2] += 270;
            break;
        }
    }
}

这是更完整的答案,@keianhzo写的。如果您正在构建AR应用,则应该使用此答案。然而,这个答案缺少如何检查倾斜度以设置Look Through或平面模式。 - Thracian

3
这是我在应用程序中实现技术的方法:
        float[] rotationMatrixOrig = new float[9];
        SensorManager.getRotationMatrix(rotationMatrixOrig, null, lastAccelerometerValue, lastMagnetometerValue);

        int screenRotation = app.getCurrentActivity().getWindowManager().getDefaultDisplay().getRotation();
        int axisX, axisY;
        boolean isUpSideDown = lastAccelerometerValue[2] < 0;

        switch (screenRotation) {
            case Surface.ROTATION_0:
                axisX = (isUpSideDown ? SensorManager.AXIS_MINUS_X : SensorManager.AXIS_X);
                axisY = (Math.abs(lastAccelerometerValue[1]) > 6.0f ? 
                        (isUpSideDown ? SensorManager.AXIS_MINUS_Z : SensorManager.AXIS_Z) :
                        (isUpSideDown ? SensorManager.AXIS_MINUS_Y : SensorManager.AXIS_Y));
                break;
            case Surface.ROTATION_90:
                axisX = (isUpSideDown ? SensorManager.AXIS_MINUS_Y : SensorManager.AXIS_Y);
                axisY = (Math.abs(lastAccelerometerValue[0]) > 6.0f ? 
                        (isUpSideDown ? SensorManager.AXIS_Z : SensorManager.AXIS_MINUS_Z) :
                        (isUpSideDown ? SensorManager.AXIS_X : SensorManager.AXIS_MINUS_X));
                break;
            case  Surface.ROTATION_180:
                axisX = (isUpSideDown ? SensorManager.AXIS_X : SensorManager.AXIS_MINUS_X);
                axisY = (Math.abs(lastAccelerometerValue[1]) > 6.0f ? 
                        (isUpSideDown ? SensorManager.AXIS_Z : SensorManager.AXIS_MINUS_Z) :
                        (isUpSideDown ? SensorManager.AXIS_Y : SensorManager.AXIS_MINUS_Y));
                break;
            case Surface.ROTATION_270:
                axisX = (isUpSideDown ? SensorManager.AXIS_Y : SensorManager.AXIS_MINUS_Y);
                axisY = (Math.abs(lastAccelerometerValue[0]) > 6.0f ? 
                        (isUpSideDown ? SensorManager.AXIS_MINUS_Z : SensorManager.AXIS_Z) :
                        (isUpSideDown ? SensorManager.AXIS_MINUS_X : SensorManager.AXIS_X));
                break;
            default:
                axisX = (isUpSideDown ? SensorManager.AXIS_MINUS_X : SensorManager.AXIS_X);
                axisY = (isUpSideDown ? SensorManager.AXIS_MINUS_Y : SensorManager.AXIS_Y);
        }

        float[] rotationMatrix = new float[9];
        SensorManager.remapCoordinateSystem(rotationMatrixOrig, axisX, axisY, rotationMatrix);

它能正常工作,但我不知道Y的值是多少 - Y不会起作用。 - ehsan wwe

2
如果手机UI锁定为0度旋转,我将获得以下数值,不需要使用remapCoordinateSystem()
Pitch (phone) = -Pitch   (API)
Roll  (phone) =  Roll     (API)
Yaw   (phone) =  Azimuth  (API)
  • 至少接近0,0,0的值。

如果手机UI被强制旋转90度

偏航角在旧方向上为-90度(-PI/2)!!! => 在现实中我将向东而不是向北走。

如果我把手机放在0,0,0的位置:

Pitch (phone) = -Roll    (API)
Roll  (phone) = -Pitch   (API)
Yaw   (phone) =  Azimuth (API)

如果手机UI被强制旋转180度:
偏航角值在旧方向上为+/-180度(+/- PI)!!! => 在现实中,我将向南而不是向北行进。
如果我将手机放置在0,0,0位置:
Pitch (phone) =  Pitch   (API)
Roll  (phone) = -Roll    (API)
Yaw   (phone) =  Azimuth (API)

如果手机UI被强制旋转为270度:

偏航值在旧方向上增加了90度(+PI/2)!!! => 在现实中,我会朝着西边而不是北边前进。

如果我把手机放在0,0,0的位置:

Pitch (phone) =  Roll    (API)
Roll  (phone) =  Pitch   (API)
Yaw   (phone) =  Azimuth (API)

我写了一个小修正,并使用以下方法进行了测试:android:screenOrientation="fullSensor"

public static final void fixRotation0(float[] orientation) { //azimuth, pitch, roll
    orientation[1] = -orientation[1]; // pitch = -pitch
}

public static final void fixRotation90(float[] orientation) { //azimuth, pitch, roll
    orientation[0] += Math.PI / 2f; // offset
    float tmpOldPitch = orientation[1];
    orientation[1] = -orientation[2]; //pitch = -roll
    orientation[2] = -tmpOldPitch; // roll  = -pitch    
}

public static final void fixRotation180(float[] orientation) { //azimuth, pitch, roll
    orientation[0] = (float)(orientation[0] > 0f ? (orientation[0] - Math.PI) : (orientation[0] + Math.PI)); // offset
    orientation[2] = -orientation[2]; // roll = -roll
}

public static final void fixRotation270(float[] orientation) { //azimuth, pitch, roll
    orientation[0] -= Math.PI / 2; // offset
    float tmpOldPitch = orientation[1];
    orientation[1] = orientation[2]; //pitch = roll
    orientation[2] = tmpOldPitch; // roll  = pitch  
}

大多数情况下都可以正常工作。当您快速围绕一个轴旋转180度时,系统将会出现故障!

完整的代码可在Github上找到。


1
我做的是:
  1. remapping coordinate system like keianhzo suggested in his answer, to remap coordinates according to screen-rotation
  2. then I remap resulting coordinate system again with
    SensorManager.remapCoordinateSystem(rotationMatrixScreenRemapped, SensorManager.AXIS_X, SensorManager.AXIS_Z, rotationMatrixCameraRemapped);
    to remap coordinates according to camera (AR-like), as suggested in documentation
到目前为止,我希望它能够正常工作!

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接