Статьи

Android SDK дополненной реальности: местоположение и расстояние

Добро пожаловать во вторую часть нашей серии статей о создании приложений дополненной реальности на платформе Android. В части 1 вы узнали, как использовать камеру, рисовать текст поверх вида камеры и получать различные значения от различных датчиков на устройстве. Теперь мы добавим данные GPS, смешаем их с какой-то математикой и точно определим местоположение фиксированного объекта в пространстве над камерой.


  1. Android SDK дополненной реальности: настройка камеры и сенсора
  2. Android SDK дополненной реальности: местоположение и расстояние

Этот урок движется в довольно быстром темпе. Мы ожидаем, что читатели будут знакомы с созданием и выполнением проектов Android. Мы также ожидаем, что у читателей будет устройство Android, которое является достаточно мощным для запуска приложений AR, таких как Nexus S. Большую часть тестирования этого приложения необходимо будет выполнить на устройстве, поскольку приложение в значительной степени зависит от камеры, датчиков и местоположения. данные, которые не всегда доступны в эмуляторе Android.

Мы предоставили образец приложения вместе с этим руководством для загрузки. Этот проект будет улучшен в течение серии учебных пособий по AR. Итак, загрузите проект Android и следуйте за ним!


Для создаваемого нами приложения AR на основе определения местоположения очень важно знать, где находится устройство. И, для точности, нам нужен лучший поставщик местоположения, доступный на устройстве. Давайте начнем.


Приложения, которые используют любые данные о местоположении с устройства, требуют определенных разрешений на местоположение. Для этого есть два варианта. Одним из вариантов является использование местоположения курса, которое обеспечивает местоположение на основе сотовых вышек или известных точек доступа Wi-Fi. Этот уровень детализации отлично подходит для определения, в каком городе вы находитесь и, возможно, в районе. Тем не менее, он, вероятно, не сможет определить, на какой улице или в каком блоке вы находитесь. Другой вариант заключается в использовании точного местоположения, которое использует GPS на устройстве для точного определения местоположения. Это занимает немного больше времени, но использует спутники GPS и вышки сотовой связи для повышения точности результатов определения местоположения. Точные данные о местоположении обычно могут помочь вам определить, на какой стороне улицы вы находитесь. Это уровень точности и точности, который обычно используется для приложений AR.

Поэтому вот разрешение, которое вы должны добавить в файл манифеста:

1
2
<uses-permission
    android:name=»android.permission.ACCESS_FINE_LOCATION» />

Теперь, когда у нас есть разрешение на доступ к службам GPS устройства, давайте начнем прислушиваться к тому, что оно говорит. Давайте добавим эту информацию непосредственно в класс OverlayView (для простоты, а не для дизайна).

Класс OverlayView теперь должен реализовывать LocationListener.

1
2
3
public class OverlayView extends View implements SensorEventListener, LocationListener {
    // …
}

Затем реализуйте методы обязательного переопределения интерфейса LocationListener:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
private Location lastLocation = null;
public void onLocationChanged(Location location) {
    lastLocation = location;
}
  
public void onProviderDisabled(String provider) {
    // …
}
  
public void onProviderEnabled(String provider) {
    // …
}
  
public void onStatusChanged(String provider, int status, Bundle extras) {
    // …
}

Затем зарегистрируйтесь для получения изменений местоположения в методе конструктора OverlayView (context):

01
02
03
04
05
06
07
08
09
10
locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
Criteria criteria = new Criteria();
criteria.setAccuracy(Criteria.ACCURACY_FINE);
criteria.setPowerRequirement(Criteria.NO_REQUIREMENT);
  
String best = locationManager.getBestProvider(criteria, true);
  
Log.v(DEBUG_TAG,»Best provider: » + best);
  
locationManager.requestLocationUpdates(best, 50, 0, this);

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


Эта часть урока обращается к некоторой математике — или мы сделали бы, если бы не некоторые хорошие помощники. В этой двумерной реализации AR мы не особенно заботимся об этом расстоянии, может быть полезно применить некоторое масштабирование света или даже пошаговое масштабирование (например, три размера), чтобы отображать точки интереса на различных расстояниях , Другое использование может состоять в том, чтобы показывать только местоположения в пределах или за пределами определенного диапазона от устройства.

Однако нас очень интересует направление к объекту относительно устройства. В навигации это называется подшипник. Путь между двумя точками на сферическом объекте, таком как планета Земля, является самым коротким, следуя тому, что называется расстоянием большого круга. Если вы используете методы AR для поиска местоположения или отображения, где какой-либо фиксированный объект может быть относительно вашего местоположения, это разумный механизм расстояния. По мере приближения направление может измениться, но вы все равно будете следовать кратчайшим путем к этому месту.

Альтернативным решением может быть вычисление прямой линии в трехмерном пространстве между двумя точками на сфере. Этот метод предполагает, что путь через сферу, а не вокруг ее поверхности, возможен или интересен. Таким образом, если вы попытаетесь определить местонахождение, скажем, Сиднея, Австралия, из Сан-Франциско, штат Калифорния, вам может понадобиться указать точку почти прямо на землю. Несмотря на забаву, этот механизм не очень полезен, если только вы не находите звезды.

Мы могли бы получить немного математики о том, как это работает. Действительно, мы бы использовали формулу haversine вручную, чтобы определить медведя из одного места в другое. Однако объект Location, удобно переданный нам в методе onLocationChanged (), содержит много полезных навигационных методов, которые могут нам помочь. Два из этих методов являются distanceTo () и BearTo ().

Но что мы собираемся получить расстояние и отношение к? В конечном итоге вы хотите, чтобы местоположения определялись из базы данных или канала. Затем вы можете отфильтровать соответствующие объекты для отображения на виде по расстоянию, если вы того пожелаете.

На данный момент, однако, мы создадим единственное место для поиска: гора Вашингтон, самая высокая вершина на северо-востоке США и место, где происходит одна из самых плохих погодных условий в мире (мы также можем легко увидеть ее из нашего дома , что делает тестирование немного проще).
Установите статические координаты для горы Вашингтон так:

1
2
3
4
5
6
private final static Location mountWashington = new Location(«manual»);
static {
    mountWashington.setLatitude(44.27179d);
    mountWashington.setLongitude(-71.3039d);
    mountWashington.setAltitude(1916.5d);
}

Совет № 1 для разработки и тестирования AR-приложений на основе определения местоположения: используйте самые точные данные о местоположении, которые вы можете получить. Если ваши данные не точны, они не будут отображаться как точные на экране. При работе с локациями даже несколько очков могут иметь огромное значение. Например, разница между 44 северными и 44,1 северными точками составляет более 11 километров!

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

1
float curBearingToMW = lastLocation.bearingTo(mountWashington);

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


Теперь, когда вы можете определить направление к цели, вам может быть интересно, как телефон знает, в каком направлении он направлен. К счастью, все необходимые нам данные датчиков уже на месте. Осталось только использовать эти данные, чтобы определить направление телефона.

Как мы это делаем? Ну, у телефона есть компас. Можем ли мы просто использовать это? Не напрямую. Компас сообщает магнитное поле вокруг осей x, y и z в микротесле. Если ваш ответ на это утверждение что-то вроде «Да?» тогда вы также поняли, что это нельзя использовать напрямую — нам нужно преобразовать данные в формат, который говорит нам, куда указывает телефон. И, указывая, мы действительно имеем в виду, куда указывает камера .

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

Первый вспомогательный метод, который мы можем использовать, удобно расположен в классе SensorManager: getOrientation (). Этот метод берет матрицу вращения и возвращает вектор со значениями азимута, шага и крена. Значение азимута — это вращение вокруг оси Z, а ось Z — это то, что указывает прямо вниз к центру планеты. Значение крена — это вращение вокруг оси Y, где ось Y является касательной к планетарной сфере и указывает на геомагнитный север. Значение шага — это вращение вокруг оси X, которое является векторным произведением оси Z и оси Y — оно указывает на то, что можно назвать магнитным западом.

Но где взять матрицу вращения? Как это происходит, Android SDK снова имеет помощника для этого в классе SensorManager с именем getRotationMatrix (). Назовите его, передавая вектор акселерометра и вектор компаса, вот так:

1
2
3
4
5
// compute rotation matrix
float rotation[] = new float[9];
float identity[] = new float[9];
boolean gotRotation = SensorManager.getRotationMatrix(rotation,
    identity, lastAccelerometer, lastCompass);

Теперь мы можем вычислить ориентацию:

1
2
3
4
5
if (gotRotation) {
    // orientation vector
    float orientation[] = new float[3];
    SensorManager.getOrientation(rotation, orientation);
}

Итак, теперь мы знаем, как устройство ориентировано относительно планеты. Быстро, куда указывает камера? Хорошо, давайте переназначим матрицу вращения так, чтобы камера была направлена ​​вдоль положительного направления оси Y, прежде чем мы вычислим вектор ориентации:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
if (gotRotation) {
    float cameraRotation[] = new float[9];
    // remap such that the camera is pointing straight down the Y axis
    SensorManager.remapCoordinateSystem(rotation, SensorManager.AXIS_X,
            SensorManager.AXIS_Z, cameraRotation);
  
    // orientation vector
    float orientation[] = new float[3];
    SensorManager.getOrientation(cameraRotation, orientation);
    if (gotRotation) {
    float cameraRotation[] = new float[9];
    // remap such that the camera is pointing along the positive direction of the Y axis
    SensorManager.remapCoordinateSystem(rotation, SensorManager.AXIS_X,
            SensorManager.AXIS_Z, cameraRotation);
  
    // orientation vector
    float orientation[] = new float[3];
    SensorManager.getOrientation(cameraRotation, orientation);
}

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


Теперь у нас есть вся информация, необходимая для размещения цели на экране: направление камеры и расположение цели относительно нашего местоположения. В настоящее время у нас есть два вектора: ориентация камеры и направление к цели. Нам нужно сопоставить экран (или, собственно, View) с диапазоном значений поворота, а затем нанести целевую точку на View, когда она находится в пределах поля зрения изображения с камеры, как показано на экране.

Несколько вещей, о которых нужно знать: На данный момент наш вектор ориентации использует радианы для единиц, а подшипник использует градусы. Нам нужно быть уверенным, что мы работаем с теми же единицами и с единицами соответствующего типа. Вычисление направления вернуло градусы, и мы можем использовать повороты в градусах на объекте Canvas, поэтому мы преобразуем значения в градусы, когда они выражены в радианах. Пакет java.lang.Math предоставляет методы для преобразования между градусами и радианами.

Напомним, ранее, что вектор ориентации начинается с азимута или вращения вокруг оси Z, которое направлено прямо вниз. С осью Y, указывающей на геомагнитный север, азимут будет сравниваться с нашим азимутом, и это будет определять, как далеко левая или правая цель находится на экране — если она вообще на экране (когда она находится вне поля зрения камера).

Точно так же, шаг используется для определения того, как далеко вверх или вниз должна быть нарисована цель на экране. Это может или не может быть то, что требует ваше приложение. Наконец, поворот изменил бы виртуальный горизонт, который мог бы отображаться на экране — то есть это было бы полным поворотом экрана. Опять же, это может быть не интересно для вашего приложения. Это зависит от того, что вы после.

Мы применим все три к нашему примеру приложения, только для ударов. В какой степени экран охватывает поле зрения реального мира, зависит только от вас. Однако класс Camera.Parameters может предоставлять поле обзора камеры устройства, настроенное производителем с использованием методов getVerticalViewAngle () и getHor horizontalViewAngle (). Удобно, а?

1
2
3
Camera.Parameters params = Camera.open().getParameters();
float verticalFOV = params.getVerticalViewAngle();
float horizontalFOV = params.getHorizontalViewAngle();

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
// use roll for screen rotation
canvas.rotate((float)(0.0f- Math.toDegrees(orientation[2])));
// Translate, but normalize for the FOV of the camera — basically, pixels per degree, times degrees == pixels
float dx = (float) ( (canvas.getWidth()/ horizontalFOV) * (Math.toDegrees(orientation[0])-curBearingToMW));
float dy = (float) ( (canvas.getHeight()/ verticalFOV) * Math.toDegrees(orientation[1])) ;
                  
// wait to translate the dx so the horizon doesn’t get pushed off
canvas.translate(0.0f, 0.0f-dy);
  
// make our line big enough to draw regardless of rotation and translation
canvas.drawLine(0f — canvas.getHeight(), canvas.getHeight()/2, canvas.getWidth()+canvas.getHeight(), canvas.getHeight()/2, targetPaint);
  
  
// now translate the dx
canvas.translate(0.0f-dx, 0.0f);
  
// draw our point — we’ve rotated and translated this to the right spot already
canvas.drawCircle(canvas.getWidth()/2, canvas.getHeight()/2, 8.0f, targetPaint);

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

Конечный результат будет выглядеть примерно так:


(Извините за содержание. Снимки экрана на улице сложно.)

Вы заметите, что горизонт на снимке экрана вращается против часовой стрелки. Это потому, что телефон, когда мы делали скриншот, вращался по часовой стрелке. Таким образом, горизонт на экране совпадает с реальным.

Теперь вы расширили свое представление о реальном мире, разместив информацию о том, что камера видит прямо на экране! Это было не так сложно, правда? Вы прошли долгий путь, но когда вы начнете, у АР есть намного больше.


Если вы запустили пример приложения, вы могли заметить, что линия и цель подпрыгивают повсюду. Это потому, что датчики очень чувствительны. Что нам нужно сделать, это сгладить данные. Лучше всего это сделать с помощью фильтра нижних частот; то есть фильтр, который блокирует высокочастотные данные, но допускает низкочастотные данные. Это сгладит данные и уменьшит колебания на экране. Мы обсудим это в следующем уроке по AR.

В зависимости от вашего местоположения и местоположения цели, вы можете обнаружить, что направление кажется странным. Чем ближе вы находитесь к геомагнитному северу, тем дальше от истинного севера будет показываться компас. На самом деле, там, где мы находимся, Истинный Север находится на целых 16 градусов. Это весьма заметно при нахождении большого объекта — например, горы Вашингтон. Мы обсудим это в следующем уроке по AR.

Вы также, вероятно, заметили, что если вы запускаете приложение какое-то время, оно имеет тенденцию перегружать все устройство. Это потому, что мы сейчас занимаемся математическим волшебством, которое занимает слишком много времени для метода onDraw (). Как вы знаете, операции, которые занимают время, всегда должны выполняться вне потока пользовательского интерфейса. Созданный нами объект OverlayView самодостаточен, он использует собственные датчики и службы GPS устройства. Это означает, что он должен выполнять потоки автономным, безопасным способом.

Опять же, это проблемы, которые, вероятно, будут рассмотрены в будущих уроках этой серии.


На этом заканчивается вторая часть нашего учебника по AR. Дополненная реальность — захватывающий жанр приложений на платформе Android. Android SDK предоставляет все, включая множество математических помощников, необходимых для разработки гладких, интересных AR-приложений, но не все устройства соответствуют требованиям к оборудованию, необходимым для этих приложений. К счастью, новейшее поколение устройств Android является самым мощным из всех, и многие из них соответствуют или превосходят спецификации оборудования, необходимые для разработки настоящих AR-приложений.


Разработчики мобильных приложений Лорен Дарси и Шейн Кондер являются соавторами нескольких книг по разработке Android: углубленная книга по программированию под названием « Разработка беспроводных приложений для Android» и « Самс научи себя разрабатывать приложения для Android за 24 часа» . Когда они не пишут, они тратят свое время на разработку мобильного программного обеспечения в своей компании и оказание консультационных услуг. С ними можно связаться по электронной почте [email protected] , через их блог на androidbook.blogspot.com и в Twitter @androidwireless .