安卓设备无需使用地磁传感器的方向感知技术

13

我需要获取设备方向。据我所知,通常使用TYPE_ACCELEROMETERTYPE_MAGNETIC_FIELD传感器。我的问题是SensorManager.getDefaultSensor返回地磁传感器的值为null。对于TYPE_ORIENTATION传感器也一样。

manager = (SensorManager) getSystemService(SENSOR_SERVICE);
Sensor sensorAcc = manager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER), //normal object
        sensorMagn = manager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD); //null
orientationListener = new OrientationSensorListener();
manager.registerListener(orientationListener, sensorAcc, 10);
manager.registerListener(orientationListener, sensorMagn, 10);

我需要其他获取设备方向的方法。


@Jas添加到问题中 - Ircover
你可以利用屏幕的宽度和高度来确定方向,在横屏模式下通常宽度大于高度。 - MrDumb
1
@ashutiwari4 知道是不是横屏还不够,我需要获取方向角度。 - Ircover
您所说的设备方向是指相对于竖屏的方向角度吗? - Hoan Nguyen
@HoanNguyen 像那样的东西。是的。 - Ircover
7个回答

10

方向可以分解为三个欧拉角:俯仰角、横滚角和方位角。

只有加速度计数据,您无法计算出您的方位角,也无法确定您的俯仰角的符号。

您可以尝试以下方法来了解您的俯仰角和横滚角:

private final float[] mMagnet = new float[3];               // magnetic field vector
private final float[] mAcceleration = new float[3];         // accelerometer vector
private final float[] mAccMagOrientation = new float[3];    // orientation angles from mAcceleration and mMagnet
private float[] mRotationMatrix = new float[9];             // accelerometer and magnetometer based rotation matrix

public void onSensorChanged(SensorEvent event) {
    switch (event.sensor.getType()) {
        case Sensor.TYPE_ACCELEROMETER:
            System.arraycopy(event.values, 0, mAcceleration, 0, 3);   // save datas
            calculateAccMagOrientation();                       // then calculate new orientation
            break;
        case Sensor.TYPE_MAGNETIC_FIELD:
            System.arraycopy(event.values, 0, mMagnet, 0, 3);         // save datas
            break;
        default:
            break;
    }
}
public void calculateAccMagOrientation() {
    if (SensorManager.getRotationMatrix(mRotationMatrix, null, mAcceleration, mMagnet)) {
        SensorManager.getOrientation(mRotationMatrix, mAccMagOrientation);
    }
    else { // Most chances are that there are no magnet datas
        double gx, gy, gz;
        gx = mAcceleration[0] / 9.81f;
        gy = mAcceleration[1] / 9.81f;
        gz = mAcceleration[2] / 9.81f;
        // http://theccontinuum.com/2012/09/24/arduino-imu-pitch-roll-from-accelerometer/
        float pitch = (float) -Math.atan(gy / Math.sqrt(gx * gx + gz * gz));
        float roll = (float) -Math.atan(gx / Math.sqrt(gy * gy + gz * gz));
        float azimuth = 0; // Impossible to guess

        mAccMagOrientation[0] = azimuth;
        mAccMagOrientation[1] = pitch;
        mAccMagOrientation[2] = roll;
        mRotationMatrix = getRotationMatrixFromOrientation(mAccMagOrientation);
    }
}
public static float[] getRotationMatrixFromOrientation(float[] o) {
    float[] xM = new float[9];
    float[] yM = new float[9];
    float[] zM = new float[9];

    float sinX = (float) Math.sin(o[1]);
    float cosX = (float) Math.cos(o[1]);
    float sinY = (float) Math.sin(o[2]);
    float cosY = (float) Math.cos(o[2]);
    float sinZ = (float) Math.sin(o[0]);
    float cosZ = (float) Math.cos(o[0]);

    // rotation about x-axis (pitch)
    xM[0] = 1.0f;xM[1] = 0.0f;xM[2] = 0.0f;
    xM[3] = 0.0f;xM[4] = cosX;xM[5] = sinX;
    xM[6] = 0.0f;xM[7] =-sinX;xM[8] = cosX;

    // rotation about y-axis (roll)
    yM[0] = cosY;yM[1] = 0.0f;yM[2] = sinY;
    yM[3] = 0.0f;yM[4] = 1.0f;yM[5] = 0.0f;
    yM[6] =-sinY;yM[7] = 0.0f;yM[8] = cosY;

    // rotation about z-axis (azimuth)
    zM[0] = cosZ;zM[1] = sinZ;zM[2] = 0.0f;
    zM[3] =-sinZ;zM[4] = cosZ;zM[5] = 0.0f;
    zM[6] = 0.0f;zM[7] = 0.0f;zM[8] = 1.0f;

    // rotation order is y, x, z (roll, pitch, azimuth)
    float[] resultMatrix = matrixMultiplication(xM, yM);
    resultMatrix = matrixMultiplication(zM, resultMatrix);
    return resultMatrix;
}
public static float[] matrixMultiplication(float[] A, float[] B) {
    float[] result = new float[9];

    result[0] = A[0] * B[0] + A[1] * B[3] + A[2] * B[6];
    result[1] = A[0] * B[1] + A[1] * B[4] + A[2] * B[7];
    result[2] = A[0] * B[2] + A[1] * B[5] + A[2] * B[8];

    result[3] = A[3] * B[0] + A[4] * B[3] + A[5] * B[6];
    result[4] = A[3] * B[1] + A[4] * B[4] + A[5] * B[7];
    result[5] = A[3] * B[2] + A[4] * B[5] + A[5] * B[8];

    result[6] = A[6] * B[0] + A[7] * B[3] + A[8] * B[6];
    result[7] = A[6] * B[1] + A[7] * B[4] + A[8] * B[7];
    result[8] = A[6] * B[2] + A[7] * B[5] + A[8] * B[8];

    return result;
}

正如楼主所说,他不能依赖磁力计,而你的代码需要它。 - strangetimes
1
错误,我的代码会对空值做出反应,仍然提供部分方向。 - J.Jacobs
@J.Jacobs-VP 的解决方案非常出色。应该将其选为被接受的答案。 - AL-zami
当设备处于竖屏状态时,当向右/左倾斜时,滚动会增加/减少,因此我能够检测倾斜方向。但是当设备处于两种横向状态之一时,无论向右或向左倾斜,滚动都会朝0的方向移动。在横向状态下,是否有一种方法可以使滚动在一个方向上增加,在另一个方向上减少?我指的是仅使用加速度计的情况。谢谢。 - superjos

3

这个解决方案能否给我一个237度的例子? - Ircover
是的,阅读文档,onOrientationChanged会给你一个从0到360的角度。 - Hoan Nguyen

2

我做了类似以下的事情:

public class MainActivity extends AppCompatActivity
{
    SensorManager sensorManager;
    Sensor sensor;
    ImageView imageViewProtractorPointer;

    /////////////////////////////////////
    ///////////// onResume //////////////
    /////////////////////////////////////
    @Override
    protected void onResume()
    {
        super.onResume();
        // register sensor listener again if return to application
        if(sensor !=null) sensorManager.registerListener(sensorListener,sensor,SensorManager.SENSOR_DELAY_NORMAL);
    }
    /////////////////////////////////////
    ///////////// onCreate //////////////
    /////////////////////////////////////
    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        imageViewProtractorPointer = (ImageView)findViewById(R.id.imageView2);
        // get the SensorManager
        sensorManager = (SensorManager)getSystemService(Context.SENSOR_SERVICE);
        // get the Sensor ACCELEROMETER
        sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
    }
    /////////////////////////////////////
    ///////////// onPause ///////////////
    /////////////////////////////////////
    @Override
    protected void onPause()
    {
        // Unregister the sensor listener to prevent battery drain if not in use
        super.onPause();
        if(sensor !=null) sensorManager.unregisterListener(sensorListener);
    }

    /////////////////////////////////////////////
    /////////// SensorEventListener /////////////
    /////////////////////////////////////////////
    SensorEventListener sensorListener = new SensorEventListener()
    {
        @Override
        public void onSensorChanged(SensorEvent sensorEvent)
        {
            // i will use values from 0 to 9 without decimal
            int x = (int)sensorEvent.values[0];
            int y = (int)sensorEvent.values[1];
            int angle = 0;

            if(y>=0 && x<=0) angle = x*10;
            if(x<=0 && y<=0) angle = (y*10)-90;
            if(x>=0 && y<=0) angle = (-x*10)-180;
            if(x>=0 && y>=0) angle = (-y*10)-270;

            imageViewProtractorPointer.setRotation((float)angle);
        }
        @Override
        public void onAccuracyChanged(Sensor sensor, int i){}
    };
}

如果您想了解我的if语句,请参考此图片image

为了我的使用,我将屏幕锁定在竖屏模式下,并使用2个图像显示屏幕上的角度,这是我的截图screenshot

我仍然需要让它变得更好,只是没有足够的时间。

希望这可以帮助您,如果您需要完整的代码,请告诉我。


你好,能否添加更多的解释? - t0m


1
你需要在清单文件中添加一些权限。文档说明如下:

如果请求的类型和唤醒属性与默认传感器匹配且应用程序具有必要的权限,则返回该传感器;否则返回 null。

我知道这听起来有些违反直觉,但是你需要的权限是:
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>

(或者是这些的子集)。
请参见此处:使用传感器时的manifest.xml

如果你在谈论地磁问题,我尝试了一些指南针应用程序 - 它们都说我的设备没有必要的传感器。 - Ircover

0

对于仍然困惑于这个问题的人,假设你想获取手机的方向(方位角、俯仰角和翻滚角),但有时磁场不稳定,导致你得到的方向也不稳定。上面的答案可能会帮助你获得俯仰角和翻滚角,但你仍然无法获得方位角。他们说这是不可能的。然后你变得绝望了。那么,你应该怎么做来解决这个问题呢?

如果你只关心方向而不关心北方在哪里,这是我的建议,尝试使用这个传感器,在我的情况下它非常好用:

TYPE_GAME_ROTATION_VECTOR

0
您可以像这样尝试使用GEOMAGNETIC_ROTATION_VECTOR:
private SensorManager mSensorManager;
private Sensor mSensor;
...
mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_GEOMAGNETIC_ROTATION_VECTOR)

使用以下代码计算传感器信息:

...
// Rotation matrix based on current readings from accelerometer and magnetometer.
final float[] rotationMatrix = new float[9];
mSensorManager.getRotationMatrix(rotationMatrix, null, accelerometerReading, magnetometerReading);

// Express the updated rotation matrix as three orientation angles.
final float[] orientationAngles = new float[3];
mSensorManager.getOrientation(rotationMatrix, orientationAngles);

摘自Android文档:https://developer.android.com/guide/topics/sensors/sensors_position.html

在清单文件中添加适当的权限。

希望这能帮到你。


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