Compensate:可以补偿倾斜和俯仰的 Android指南针

我试图在我的 Android 手机(Nexus 4)上制作一个应用程序,该应用程序将在模型船中使用。

但是,指南针只有在手机背面平坦时才稳定。如果我将其向上倾斜(例如翻开一页 booK),则指南针的航向会偏离-多达 50 *。

我已经尝试了 Sensor.TYPE_MAGNETIC_FIELD 与 Sensor.TYPE_GRITY 和 Sensor.TYPE_ACCELEROMETER,效果是一样的。

我使用了here和许多其他地方提到的解决方案。我的数学不是很好,但这一定是一个常见问题,我发现没有 API 来处理它令人沮丧。

我已经在这个问题上工作了 3 天,仍然没有找到任何解决方案,但是当我使用Compass from Catch时,无论手机倾斜多少,它们都会保持稳定。

所有我想要做的是创建一个指南针,如果手机指向说北,那么指南针将读取北,而不是跳来跳去,当手机通过任何其他轴(滚动或俯仰)移动。

任何人都可以请帮助之前,我不得不放弃我的项目。

谢谢,亚当

37

由于巧合,我已经考虑这个问题好几个星期了,因为

作为一名数学家,我对我在其他地方看到的任何答案都不满意。

我需要一个很好的答案,我工作的应用程序。

我已经把我使用的数学here on math.stackexchange.com,并且我已经粘贴了我在使用的代码。该代码从原始TYPE_GRITYTYPE_MAGNETIC_FIELD传感器数据计算方位角和俯仰,而不需要任何 API 调用,例如SensorManager.getRotationMatrix(...)SensorManager.getOrientation(...),因此可以通过使用低通滤波器来改善代码。

我对任何人对我的这种方法的任何反馈都非常感兴趣。我发现它在我自己的应用程序中就像一个梦想,只要设备面朝上,垂直或介于两者之间。但是,正如我在 math.stackexchange.com 文章中提到的那样,随着设备接近被颠倒,会出现一些问题。在这种情况下,需要仔细定义一个人想要的行为。

    import android.app.Activity;
    import android.hardware.Sensor;
    import android.hardware.SensorEvent;
    import android.hardware.SensorEventListener;
    import android.hardware.SensorManager;
    import android.view.Suce;
    public static class OrientationSensor implements  SensorEventListener {
    public final static int SENSOR_UNAILABLE = -1;
    // references to other objects
    SensorManager m_sm;
    SensorEventListener m_parent;   // non-null if this class should call its parent after onSensorChanged(...) and onAccuracyChanged(...) notifications
    Activity m_activity;            // current activity for call to getWindowManager().getDefaultDisplay().getRotation()
    // raw inputs from Android sensors
    float m_Norm_Gravity;           // length of raw gravity vector received in onSensorChanged(...).  NB: should be about 10
    float[] m_NormGravityVector;    // Normalised gravity vector, (i.e. length of this vector is 1), which points straight up into space
    float m_Norm_MagField;          // length of raw magnetic field vector received in onSensorChanged(...). 
    float[] m_NormMagFieldValues;   // Normalised magnetic field vector, (i.e. length of this vector is 1)
    // accuracy specifications. SENSOR_UNAILABLE if unknown, otherwise SensorManager.SENSOR_STATUS_UNRELIABLE, SENSOR_STATUS_ACCURACY_LOW, SENSOR_STATUS_ACCURACY_MEDIUM or SENSOR_STATUS_ACCURACY_HIGH
    int m_GravityAccuracy;          // accuracy of gravity sensor
    int m_MagneticFieldAccuracy;    // accuracy of magnetic field sensor
    // values calculated once gravity and magnetic field vectors are available
    float[] m_NormEastVector;       // normalised cross product of raw gravity vector with magnetic field values, points east
    float[] m_NormNorthVector;      // Normalised vector pointing to magnetic north
    boolean m_OrientationOK;        // set true if m_azimuth_radians and m_pitch_radians have successfully been calculated following a call to onSensorChanged(...)
    float m_azimuth_radians;        // angle of the device from magnetic north
    float m_pitch_radians;          // tilt angle of the device from the horizontal.  m_pitch_radians = 0 if the device if flat, m_pitch_radians = Math.PI/2 means the device is upright.
    float m_pitch_axis_radians;     // angle which defines the axis for the rotation m_pitch_radians
    public OrientationSensor(SensorManager sm, SensorEventListener parent) {
        m_sm = sm;
        m_parent = parent;
        m_activity = null;
        m_NormGravityVector = m_NormMagFieldValues = null;
        m_NormEastVector = new float[3];
        m_NormNorthVector = new float[3];
        m_OrientationOK = false;
    }
    public int Register(Activity activity, int sensorSpeed) {
        m_activity = activity;  // current activity required for call to getWindowManager().getDefaultDisplay().getRotation()
        m_NormGravityVector = new float[3];
        m_NormMagFieldValues = new float[3];
        m_OrientationOK = false;
        int count = 0;
        Sensor SensorGravity = m_sm.getDefaultSensor(Sensor.TYPE_GRITY);
        if (SensorGravity != null) {
            m_sm.registerListener(this, SensorGravity, sensorSpeed);
            m_GravityAccuracy = SensorManager.SENSOR_STATUS_ACCURACY_HIGH;
            count++;
        } else {
            m_GravityAccuracy = SENSOR_UNAILABLE;
        }
        Sensor SensorMagField = m_sm.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
        if (SensorMagField != null) {
            m_sm.registerListener(this, SensorMagField, sensorSpeed);
            m_MagneticFieldAccuracy = SensorManager.SENSOR_STATUS_ACCURACY_HIGH;     
            count++;
        } else {
            m_MagneticFieldAccuracy = SENSOR_UNAILABLE;
        }
        return count;
    }
    public void Unregister() {
        m_activity = null;
        m_NormGravityVector = m_NormMagFieldValues = null;
        m_OrientationOK = false;
        m_sm.unregisterListener(this);
    }
    @Override
    public void onSensorChanged(SensorEvent evnt) {
        int SensorType = evnt.sensor.getType();
        switch(SensorType) {
            case Sensor.TYPE_GRITY:
                if (m_NormGravityVector == null) m_NormGravityVector = new float[3];
                System.arraycopy(evnt.values, 0, m_NormGravityVector, 0, m_NormGravityVector.length);                   
                m_Norm_Gravity = (float)Math.sqrt(m_NormGravityVector[0]*m_NormGravityVector[0] + m_NormGravityVector[1]*m_NormGravityVector[1] + m_NormGravityVector[2]*m_NormGravityVector[2]);
                for(int i=0; i < m_NormGravityVector.length; i++) m_NormGravityVector[i] /= m_Norm_Gravity;
                break;
            case Sensor.TYPE_MAGNETIC_FIELD:
                if (m_NormMagFieldValues == null) m_NormMagFieldValues = new float[3];
                System.arraycopy(evnt.values, 0, m_NormMagFieldValues, 0, m_NormMagFieldValues.length);
                m_Norm_MagField = (float)Math.sqrt(m_NormMagFieldValues[0]*m_NormMagFieldValues[0] + m_NormMagFieldValues[1]*m_NormMagFieldValues[1] + m_NormMagFieldValues[2]*m_NormMagFieldValues[2]);
                for(int i=0; i < m_NormMagFieldValues.length; i++) m_NormMagFieldValues[i] /= m_Norm_MagField;  
                break;
        }
        if (m_NormGravityVector != null && m_NormMagFieldValues != null) {
            // first calculate the horizontal vector that points due east
            float East_x = m_NormMagFieldValues[1]*m_NormGravityVector[2] - m_NormMagFieldValues[2]*m_NormGravityVector[1];
            float East_y = m_NormMagFieldValues[2]*m_NormGravityVector[0] - m_NormMagFieldValues[0]*m_NormGravityVector[2];
            float East_z = m_NormMagFieldValues[0]*m_NormGravityVector[1] - m_NormMagFieldValues[1]*m_NormGravityVector[0];
            float norm_East = (float)Math.sqrt(East_x * East_x + East_y * East_y + East_z * East_z);
            if (m_Norm_Gravity * m_Norm_MagField * norm_East < 0.1f) {  // Typical values are  > 100.
                m_OrientationOK = false; // device is close to free fall (or in space?), or close to magnetic north pole.
            } else {
                m_NormEastVector[0] = East_x / norm_East; m_NormEastVector[1] = East_y / norm_East; m_NormEastVector[2] = East_z / norm_East;
                // next calculate the horizontal vector that points due north                   
                float M_dot_G = (m_NormGravityVector[0] *m_NormMagFieldValues[0] + m_NormGravityVector[1]*m_NormMagFieldValues[1] + m_NormGravityVector[2]*m_NormMagFieldValues[2]);
                float North_x = m_NormMagFieldValues[0] - m_NormGravityVector[0] * M_dot_G;
                float North_y = m_NormMagFieldValues[1] - m_NormGravityVector[1] * M_dot_G;
                float North_z = m_NormMagFieldValues[2] - m_NormGravityVector[2] * M_dot_G;
                float norm_North = (float)Math.sqrt(North_x * North_x + North_y * North_y + North_z * North_z);
                m_NormNorthVector[0] = North_x / norm_North; m_NormNorthVector[1] = North_y / norm_North; m_NormNorthVector[2] = North_z / norm_North;
                // take account of screen rotation away from its natural rotation
                int rotation = m_activity.getWindowManager().getDefaultDisplay().getRotation();
                float screen_adjustment = 0;
                switch(rotation) {
                    case Suce.ROTATION_0:   screen_adjustment =          0;         break;
                    case Suce.ROTATION_90:  screen_adjustment =   (float)Math.PI/2; break;
                    case Suce.ROTATION_180: screen_adjustment =   (float)Math.PI;   break;
                    case Suce.ROTATION_270: screen_adjustment = 3*(float)Math.PI/2; break;
                }
                // NB: the rotation matrix has now effectively been calculated. It consists of the three vectors m_NormEastVector[], m_NormNorthVector[] and m_NormGravityVector[]
                // calculate all the required angles from the rotation matrix
                // NB: see https://math.stackexchange.com/questions/381649/whats-the-best--angular-co-ordinate-system-for-working-with-smartfone-apps
                float sin = m_NormEastVector[1] -  m_NormNorthVector[0], cos = m_NormEastVector[0] +  m_NormNorthVector[1];
                m_azimuth_radians = (float) (sin != 0 && cos != 0 ? Math.atan2(sin, cos) : 0);
                m_pitch_radians = (float) Math.acos(m_NormGravityVector[2]);
                sin = -m_NormEastVector[1] -  m_NormNorthVector[0]; cos = m_NormEastVector[0] -  m_NormNorthVector[1];
                float aximuth_plus_two_pitch_axis_radians = (float)(sin != 0 && cos != 0 ? Math.atan2(sin, cos) : 0);
                m_pitch_axis_radians = (float)(aximuth_plus_two_pitch_axis_radians - m_azimuth_radians) / 2;
                m_azimuth_radians += screen_adjustment;
                m_pitch_axis_radians += screen_adjustment;
                m_OrientationOK = true;                                 
            }
        }
        if (m_parent != null) m_parent.onSensorChanged(evnt);
    }
    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy) {
        int SensorType = sensor.getType();
        switch(SensorType) {
            case Sensor.TYPE_GRITY: m_GravityAccuracy = accuracy; break;
            case Sensor.TYPE_MAGNETIC_FIELD: m_MagneticFieldAccuracy = accuracy; break;
        }
        if (m_parent != null) m_parent.onAccuracyChanged(sensor, accuracy);
    }
}
16

好吧,我想我解决了。

而不是使用 Sensor.TYPE_ACCELEROMETER(或 TYPE_GRITY)和 Sensor.TYPE_MAGNETIC_FIELD,我使用 Sensor.TYPE_ROTATION_VECTOR 与:

float[] roationV = new float[16];
SensorManager.getRotationMatrixFromVector(roationV, rotationVector);
float[] orientationValuesV = new float[3];
SensorManager.getOrientation(roationV, orientationValuesV);

无论电话的滚动或俯仰如何,这都返回了稳定的方位角。

如果您查看here at the Android Motion Sensors,就在表 1 下方,它表示 ROTATION 传感器是指南针,增强现实等的理想选择。

所以很容易,当你知道如何....但是,我还没有测试这个随着时间的推移,看看是否引入了错误。

2

您遇到的问题可能是Gimbal lock。如果您考虑一下,当手机直立时,俯仰为正负 90 度,则方位角和滚转是一回事。如果您研究数学,您会发现在这种情况下,方位角滚动或方位角滚动都已明确定义,但它们并未单独定义。因此,当音高接近正负 90 度时,您可能会选择不稳定的坐标。

2

这是在不受俯仰或滚转影响的情况下获得磁航向的另一种方式。

private final static double PI = Math.PI;
private final static double TWO_PI = PI*2;
 case Sensor.TYPE_ROTATION_VECTOR:
                float[] orientation = new float[3];
                float[] rotationMatrix = new float[9];
                SensorManager.getRotationMatrixFromVector(rotationMatrix, rawValues);
                SensorManager.getOrientation(rotationMatrix, orientation);
                float heading = mod(orientation[0] + TWO_PI,TWO_PI);//important
                //do something with the heading
                break;
private double mod(double a, double b){
        return a % b;
    }

本站系公益性非盈利分享网址,本文来自用户投稿,不代表边看边学立场,如若转载,请注明出处

(567)
盆腔积液多少cm可以抽液:可以将IN转换为CM的尺寸图(conversion chart for inches to centi
上一篇
怎么删除页脚:删除 mailchimp中的页脚
下一篇

相关推荐

  • android 视频编码深入理解MediaCodec API

    Android 视频编码是指将原始视频数据经过压缩编码后,生成新的视频数据,以便减少视频文件的体积,提高传输速度,以及更好地在 Android 设备上播放。…

    2023-01-13 10:58:18
    0 78 54
  • android websocket框架实现实时双向通信的最佳解决方案

    Android WebSocket 框架是一种基于WebSocket协议的客户端库,用于在Android上创建和管理WebSocket连接。它支持标准的WebSocket API,并且可以轻松集成到Android应用程序中。…

    2023-01-14 03:33:43
    0 73 71
  • android selector用法:栏

    示例示例android 用法:是Android中的一种资源类型,它可以用来替代常规的图片资源,它可以根据不同的状态来更改控件的背景图片或者文字颜色。…

    2023-06-10 01:56:25
    0 94 94
  • android 系统签名 Discover the Benefits of System Signing

    Android 系统签名是一种安全机制,它可以防止应用程序在安装或更新时被恶意修改。Android 系统签名使用数字签名来标识应用程序的发布者,并确保应用程序的完整性和安全性。…

    2023-06-20 09:13:10
    0 91 44
  • android跳转页面代码:从Android应用程序跳转到另一个Activity

    示例示例android跳转页面代码,可以使用Intent来实现,具体代码如下:// 创建Intent对象…

    2023-01-11 04:26:17
    0 22 75
  • android 获取cpu占用率:Android CPU使用率监控

    Android 获取 CPU 占用率的方法有以下几种:使用 Android 系统提供的 API:…

    2023-04-09 04:59:26
    0 82 43
  • android studio c++开发从入门到精通

    Android Studio 是 Google 推出的一款基于 IntelliJ IDEA 的 Android 开发工具,它支持 Java 和 C++ 语言开发 Android 应用程序。…

    2023-10-11 05:07:35
    0 28 26
  • android ttf字体:Welcome

    Android ttf字体是指TrueType字体,是一种常见的字体格式,可以在Android系统中使用。它具有良好的可移植性和可扩展性,可以在不同的设备上显示出相同的文本效果。…

    2023-07-15 07:41:36
    0 39 19

发表评论

登录 后才能评论

评论列表(36条)