Получения текущих углов наклона устройстве с помощью сенсоров — интересная задача. Но проблема заключается не в том, как получить значения, а как их отфильтровать. Дело все в том, что показания акселерометра очень неточные, гироскоп имеет проблему «дрифта», то есть его значения постоянно уменьшаются при неподвижном состоянии, а магнитный сенсор зависит от окружающего его магнитного поля.

image

Решением этой проблемы является совмещение показаний гироскопа и акселерометра и их фильтрация с помощью алгоритма Калмана, как это делают здесь.

В поисках найти код на Java для всего этого наткнулся на статью «Android sensor fusion tutorial». Их программа показала себя не лучшим образом при измерении с помощью акселерометра + магнитного сенсора + гироскопа: значения сильно колебались и зависели от магнитного поля (при поднесении магнита).

Лучшим вариантом стало решение от Google для Cardboard. Этот API дает показания высокой точности почти без колебаний и значения не зависят от магнитного поля, так как для измерений используются только гироскоп и акселерометр.

Здесь рассказано о том, как использовать лишь часть API для отслеживания поворота головы (проект пишу в Android Studio):

1. Скачать библиотеки cardboard.jar и libprotobuf-java-2.6-nano.jar отсюда или отсюда;

2. Поместить их в папку lib проекта;

3. Связать проект с этими библиотеками: File > Project Structure > Dependencies > (+) > Выбрать нужные файлы. Должно получиться вот так:

image

4. Класс HeadTransform скачиваем и кладем вместе со своими классами. (API не позволяет пользоваться некоторыми методами этого класса, поэтому мы положим его копию в свой пакет. HeadTransform отвечает только за некоторые математические преобразования, поэтому может использоваться отдельно);

5. В нужной Activity объявляем:

HeadTracker track;
float [] angles = new float[3];

6. В onCreate:

track = HeadTracker.createFromContext(this);

7. В onResume:

track.startTracking();

8. В onStop:

track.stopTracking();


9. Для получения текущих углов пишем:

HeadTransform trans = new HeadTransform();
track.getLastHeadView(trans.getHeadView(), 0);
trans.getEulerAngles(angles, 0);

10. Теперь в массиве angles будут углы:

• Pitch (X axis): [-pi/2, pi/2]
• Yaw (Y axis): [-pi, pi]
• Roll (Z axis): [-pi, pi]

Подробнее:https://developers.google.com/cardboard/android/latest/reference/com/google/vrtoolkit/cardboard/HeadTransform#getEulerAngles

11. Получать эти значения можно в каком-либо Thread с небольшой задержкой;

12. Значения углов очень точные, так как используются и акселерометр и гироскоп. Благодаря коррекции отклонений гироскопа, решена проблема «дрифта» показаний;

13. Для графических игр лучше использовать матрицу переноса как сказано здесь. Пример: https://github.com/googlesamples/cardboard-java;

14. Подробнее про Cardboard API:https://developers.google.com/cardboard/;

15. Почитать исходный код cardboard.jar можно здесь или здесь (декомпилированые старые версии 0.5.1);

16. Мой пример;

17. Готово!