Как рассчитать азимут, шаг, ориентацию, когда мое устройство Android не плоское?

Я использую датчики гравитации и магнитного поля Android для расчета ориентации с помощью SensorManager.getRotationMatrix и SensorManager.getOrientation. Это дает мне номера азимута, шага и ориентации. Результаты выглядят разумно, когда устройство лежит на столе.

Однако в манифесте я отключил переключение между портретным и ландшафтным режимом, поэтому getWindowManager(). GetDefaultDisplay(). GetRotation() всегда равен нулю. Когда я поворачиваю устройство на 90 градусов, чтобы оно стояло вертикально, у меня возникают проблемы. Иногда цифры кажутся совершенно неправильными, и я понял, что это связано с блокировкой карданного подвеса. Однако другие приложения, похоже, не имеют этой проблемы. Например, я сравнил свое приложение с двумя бесплатными приложениями для тестирования датчиков ( Sensor Tester (Dicotomica) и Sensor Monitoring (R's Software)). Мое приложение соглашается с этими приложениями, когда устройство плоское, но когда я поворачиваю устройство в вертикальное положение, могут быть значительные различия. Кажется, что эти два приложения согласуются друг с другом, так как же они справляются с этой проблемой?

3 ответа

Решение

Я думаю, что лучший способ определить ваши углы ориентации, когда устройство не плоское, это использовать более подходящую систему угловых координат, чем стандартные углы Эйлера, которые вы получаете от SensorManager.getOrientation(...), Я предлагаю тот, который я опишу здесь, на math.stackexchange.com. Я также поместил некоторый код, который реализует это в ответе здесь. Apart from a good definition of azimuth, it also has a definition of the pitch angle which is exactly the angle given by Math.acos(rotationMatrix[8]) that is mentioned in another answer here.

Вы можете получить полную информацию по двум ссылкам, которые я дал в первом абзаце. Тем не менее, в итоге, ваша матрица вращения R от SensorManager.getRotationMatrix(...)

уравнение с определением матрицы R

где (Ex, Ey, Ez), (Nx, Ny, Nz) и (Gx, Gy, Gz) - векторы, указывающие на восток, север и в направлении силы тяжести. Тогда требуемый угол азимута

уравнение, определяющее угол азимута

Когда устройство не плоское, вы должны позвонить remapCoordinateSystem(inR, AXIS_X, AXIS_Z, outR); перед звонком getOrientation,

azimuth возвращается getOrientation получается путем ортогонального проецирования устройства устройства Y axis в мир East-North plane а затем рассчитать угол между результирующим вектором проекции и северной осью.

Теперь мы обычно думаем о направлении как о направлении, на которое указывает задняя камера. Это направление -Z где Z ось устройства, указывающая на экран. Когда устройство плоское, мы не думаем о направлении и не принимаем то, что когда-либо давали. Но когда он не плоский, мы ожидаем, что это направление -Z, Но getOrientation рассчитать направление Y axisТаким образом, мы должны поменять местами Y and Z axes перед звонком getOrientation, Это именно то, что remapCoordinateSystem(inR, AXIS_X, AXIS_Z, outR) делает, это держать X axis нетронутыми и карта Z to Y,

Итак, как вы знаете, когда remap или нет. Вы можете сделать это, проверив

float inclination = (float) Math.acos(rotationMatrix[8]);
if (result.inclination < TWENTY_FIVE_DEGREE_IN_RADIAN 
            || result.inclination > ONE_FIFTY_FIVE_DEGREE_IN_RADIAN)
{
    // device is flat just call getOrientation
}
else
{
    // call remap
}

Наклон выше - это угол между экраном устройства и мировой плоскостью Восток-Север. Он показывает, насколько сильно устройство наклоняется.

Я добавлю пример, который сработал для меня, используя ответ Хоана, а также выделю некоторые новые ресурсы, которые доступны сейчас и могут помочь при просмотре приложений, которым необходимо использовать гироскопические датчики.

Во-первых, есть лаборатория Google Code, доступная с образцом приложения - я обнаружил, что готовый образец приложения очень полезен для понимания поведения устройств.

Дисплей приложения выглядит так:

Таблицы кодов и окончательная версия приложения доступны по этим ссылкам (на момент написания):

В частности, это позволяет вам экспериментировать следующим образом (это проще сделать, чем описать ...):

  • положите устройство на стол и поэкспериментируйте с вращением на столе в горизонтальном положении, поднимая верхнюю и нижнюю части (т. е. поднимите верхнюю часть дисплея, где обычно находится ваша передняя камера, со стола, оставив нижнюю часть, где находится кнопка вашего дома. обычно, если он у вас есть, на столе. Аналогичным образом поднимите левый и правый края устройства. Обратите внимание на значения азимута, тангажа и крена в приложении.

  • Приложите устройство к дверце шкафа в портретном режиме и снова поэкспериментируйте, перемещая его. Откройте дверь, чтобы тоже увидеть эффект. Поверните в альбомную ориентацию, чтобы увидеть разницу.

Ключевым моментом, который, я думаю, вы можете видеть из вышесказанного, является то, что все очень просто и хорошо работает, когда оно плоское, и все сложно и взаимосвязано, когда оно не плоское.

Рассматривая конкретный вариант использования, мне приходилось отображать угол наклона и крена в вертикальном положении в портретном режиме, у меня сработало следующее:

       when (event?.sensor?.type) {
            Sensor.TYPE_ROTATION_VECTOR -> {

                // Calculate the rotation matrix
                val rotMatrix = FloatArray(9)
                var rotationMatrixAdjusted = FloatArray(9)
                val azimuthChanged: (Float) -> Unit
                val orientation = FloatArray(3)
                SensorManager.getRotationMatrixFromVector(rotMatrix, event.values);
                SensorManager.remapCoordinateSystem(rotMatrix,
                        SensorManager.AXIS_Y, SensorManager.AXIS_MINUS_X,
                        rotationMatrixAdjusted);
                SensorManager.getOrientation(rotationMatrixAdjusted, orientation)

                val rollAngleRadians = orientation[1]
                val pitchAngleRadians = orientation[2]
                              
                //Report results back to listener
                thisListener.onRollPitchEvent(rollAngleRadians, pitchAngleRadians)
            }
        }
Другие вопросы по тегам