В этом гайде вы узнаете, как добавить 3D-модели в реальный мир. Библиотека ARCore от Google позволяет добавлять на 2D-изображение (картинка или видео) полноценные 3D-модели.
Вам необходимо предоставить системе некое опорное изображение, которое ARCore будет искать в реальном мире, чтобы на его основе добавить на изображение 3D-модель. Дополненная реальность уже широко используются, например, в книгах, газетах, журналах и т.д.
Прежде чем погрузиться в этот туториал, вам стоит ознакомиться с предыдущими двумя статьями на эту тему, которые познакомят вас с основными AR-терминами:
Что такое изображения дополненной реальности?
Согласно документации для разработчиков, изображения дополненной реальности в ARCore позволяют создавать приложения дополненной реальности, которые могут «оживлять» 2D-изображения, например, плакаты или упаковки продуктов.
Вы загружаете в ARCore какие-то опорные изображения, а он вам затем сообщает об их обнаружении во время AR-сессии, например во время съёмки видео. И эта информация используется для расположения 3D-модели на 2D-изображении.
Ограничения использования изображений дополненной реальности
Вот некоторые ограничения, с которыми вы можете столкнуться при использовании изображений дополненной реальности:
- ARCore может обрабатывать только до 20 опорных изображений одновременно.
- Физическая плоскость в реальном мире должна быть плоской, а её площадь должна быть больше, чем 15 см х 15 см.
- ARCore не может отслеживать движущиеся изображения и объекты.
Выбор подходящего опорного изображения
Вот несколько советов для выбора хорошего опорного изображения для ARCore:
- Изображения дополненной реальности поддерживают форматы PNG, JPEG и JPG.
- Неважно, цветное будет изображение или чёрно-белое, главное, чтобы оно было высокой контрастности.
- Разрешение изображения должно быть не менее 300 х 300 пикселей.
- Использование изображений с высоким разрешением не означает улучшение производительности.
- Следует избегать изображений с повторяющимися паттернами (например, узорами или горошком).
- Используйте инструмент arcoreimg, чтобы оценить, насколько подходит ваше изображение для работы. Рекомендуется оценка не менее 75 баллов.
Как использовать инструмент arcoreimg:
- Загрузите ARCore SDK для Android по этой ссылке.
- Распакуйте zip-содержимое файла в любое место.
- В извлеченной папке перейдите по пути tools > arcoreimg > windows (даже если у вас Linux или macOS).
- Откройте командную строку в этой директории.
- И введите эту команду:
arcoreimg.exe eval-img --input_image_path=dog.png
Замените dog.png на полный путь к вашему изображению.
Начало работы с приложением дополненной реальности
Теперь, когда вы ознакомились с ARCore и выбрали хорошее изображение с оценкой 75+, пришло время приступить к написанию кода приложения.
Создание фрагмента
Мы создадим фрагмент и добавим его в нашу Activity. Создаём класс с именем CustomArFragment
и наследуем его от ArFragment
. Вот код для CustomArFragment
:
package com.ayusch.augmentedimages;
import android.util.Log;
import com.google.ar.core.Config;
import com.google.ar.core.Session;
import com.google.ar.sceneform.ux.ArFragment;
public class CustomArFragment extends ArFragment {
@Override
protected Config getSessionConfiguration(Session session) {
getPlaneDiscoveryController().setInstructionView(null);
Config config = new Config(session);
config.setUpdateMode(Config.UpdateMode.LATEST_CAMERA_IMAGE);
session.configure(config);
getArSceneView().setupSession(session);
return config;
}
}
Прежде всего, мы отключаем обнаружение плоскости. Делая это, мы убираем с экрана значок руки, который появляется сразу после инициализации фрагмента и говорит пользователю о необходимости перемещения своего смартфона для поиска плоскости. Нам это больше не нужно, поскольку мы обнаруживаем не случайные плоскости, а конкретное изображение.
Затем мы устанавливаем режим обновления для сессии LATEST_CAMERA_IMAGE
. Это гарантирует, что мы будем узнавать об обновлениях изображения всякий раз, когда обновится кадр камеры.
Настройка базы данных изображений
Добавьте выбранное опорное изображение (которое вы хотите обнаружить в физическом мире) в папку assets (создайте её, если её ещё нет). Теперь мы можем добавлять изображения в нашу базу данных.
Мы создадим эту базу данных, как только будет создан фрагмент. В логи мы выведем результат этой операции:
if ((((MainActivity) getActivity()).setupAugmentedImagesDb(config, session))) {
Log.d("SetupAugImgDb", "Success");
} else {
Log.e("SetupAugImgDb","Faliure setting up db");
}
Вот как будет выглядеть CustomArFragment
:
package com.ayusch.augmentedimages;
import android.util.Log;
import com.google.ar.core.Config;
import com.google.ar.core.Session;
import com.google.ar.sceneform.ux.ArFragment;
public class CustomArFragment extends ArFragment {
@Override
protected Config getSessionConfiguration(Session session) {
getPlaneDiscoveryController().setInstructionView(null);
Config config = new Config(session);
config.setUpdateMode(Config.UpdateMode.LATEST_CAMERA_IMAGE);
session.configure(config);
getArSceneView().setupSession(session);
if ((((MainActivity) getActivity()).setupAugmentedImagesDb(config, session))) {
Log.d("SetupAugImgDb", "Success");
} else {
Log.e("SetupAugImgDb","Faliure setting up db");
}
return config;
}
}
Вскоре мы добавим метод setupAugmentedImagesDb
в MainActivity
. Теперь давайте добавим CustomArFragment
в наш activity_main.xml
:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<fragment
android:id="@+id/sceneform_fragment"
android:name="com.ayusch.augmentedimages.CustomArFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</android.support.constraint.ConstraintLayout>
Добавление изображения в базу данных
Сейчас мы настроим нашу базу данных изображений, обнаружим опорное изображение в реальном мире и добавим 3D-модель на изображение.
Давайте начнём с настройки нашей базы данных. Создайте публичный метод setupAugmentedImagesDb
в классе MainActivity
:
public boolean setupAugmentedImagesDb(Config config, Session session) {
AugmentedImageDatabase augmentedImageDatabase;
Bitmap bitmap = loadAugmentedImage();
if (bitmap == null) {
return false;
}
augmentedImageDatabase = new AugmentedImageDatabase(session);
augmentedImageDatabase.addImage("tiger", bitmap);
config.setAugmentedImageDatabase(augmentedImageDatabase);
return true;
}
private Bitmap loadAugmentedImage() {
try (InputStream is = getAssets().open("blanket.jpeg")) {
return BitmapFactory.decodeStream(is);
} catch (IOException e) {
Log.e("ImageLoad", "IO Exception", e);
}
return null;
}
Мы также создали метод loadAugmentedImage
, который загружает изображение из папки ресурсов и возвращает растровое изображение.
В setupAugmentedImagesDb
мы сначала инициализируем нашу базу данных для текущей сессии, а затем добавляем изображение в эту базу данных. Мы назвали наше изображение tiger. Затем мы устанавливаем эту базу данных в конфиг и возвращаем true
, сообщая о том, что изображение успешно добавлено.
Обнаружение опорных изображений в реальном мире
Теперь мы начнем обнаруживать наши опорные изображения в реальном мире. Для этого мы создадим слушателя, который будет вызываться каждый раз при обновлении видеокадра, и этот кадр будет проанализирован на предмет наличия там опорного изображения.
Добавьте эту строку в метод onCreate()
в MainActivity
:
arFragment.getArSceneView().getScene().addOnUpdateListener(this::onUpdateFrame);
Теперь добавьте метод onUpdateFrame
в MainActivity
:
@RequiresApi(api = Build.VERSION_CODES.N)
private void onUpdateFrame(FrameTime frameTime) {
Frame frame = arFragment.getArSceneView().getArFrame();
Collection<AugmentedImage> augmentedImages = frame.getUpdatedTrackables(AugmentedImage.class);
for (AugmentedImage augmentedImage : augmentedImages) {
if (augmentedImage.getTrackingState() == TrackingState.TRACKING) {
if (augmentedImage.getName().equals("tiger") && shouldAddModel) {
placeObject(arFragment, augmentedImage.createAnchor(augmentedImage.getCenterPose()), Uri.parse("Mesh_BengalTiger.sfb"));
shouldAddModel = false;
}
}
}
}
В первой строке мы получаем сам кадр. Кадр можно представить, как обычный скриншот из видео. Если вы знакомы с тем, как работает видео, вы знаете, что это просто набор изображений, которые очень быстро сменяют друг друга, создавая впечатление чего-то движущегося. Мы просто берём одну из этих картинок.
После того, как мы получили кадр, мы анализируем его на предмет наличия на нём нашего опорного изображения. Мы берём список всех элементов, отслеженных ARCore, используя frame.getUpdatedTrackables
. Затем мы перебираем её и проверяем, присутствует ли в кадре наше изображение tiger.
Если совпадение найдено, то мы просто берём и размещаем 3D-модель поверх обнаруженного изображения.
Примечание. Флаг
shouldAddModel
используется для того, чтобы мы добавляли 3D-модель только один раз.
Размещение 3D-модели над опорным изображением
Теперь, когда мы нашли наше опорное изображение в реальном мире, мы можем добавлять 3D-модель поверх него. Добавим методы placeObject
и addNodeToScene
:
placeObject
: этот метод используется для построения отрендеренного объекта по заданномуUri
. Как только рендеринг завершён, объект передаётся в методaddNodeToScene
, где объект прикрепляется к узлу, и этот узел помещается на сцену.addNodeToScene
: этот метод создаёт узел из полученного якоря, создаёт другой узел, к которому присоединяется визуализируемый объект, затем добавляет этот узел в якорный узел и помещает его на сцену.
Вот так теперь выглядит MainActivity
:
package com.ayusch.augmentedimages;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Build;
import android.support.annotation.RequiresApi;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;
import com.google.ar.core.Anchor;
import com.google.ar.core.AugmentedImage;
import com.google.ar.core.AugmentedImageDatabase;
import com.google.ar.core.Config;
import com.google.ar.core.Frame;
import com.google.ar.core.Session;
import com.google.ar.core.TrackingState;
import com.google.ar.sceneform.AnchorNode;
import com.google.ar.sceneform.FrameTime;
import com.google.ar.sceneform.rendering.ModelRenderable;
import com.google.ar.sceneform.rendering.Renderable;
import com.google.ar.sceneform.ux.ArFragment;
import com.google.ar.sceneform.ux.TransformableNode;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
public class MainActivity extends AppCompatActivity {
ArFragment arFragment;
boolean shouldAddModel = true;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
arFragment = (CustomArFragment) getSupportFragmentManager().findFragmentById(R.id.sceneform_fragment);
arFragment.getPlaneDiscoveryController().hide();
arFragment.getArSceneView().getScene().addOnUpdateListener(this::onUpdateFrame);
}
@RequiresApi(api = Build.VERSION_CODES.N)
private void placeObject(ArFragment arFragment, Anchor anchor, Uri uri) {
ModelRenderable.builder()
.setSource(arFragment.getContext(), uri)
.build()
.thenAccept(modelRenderable -> addNodeToScene(arFragment, anchor, modelRenderable))
.exceptionally(throwable -> {
Toast.makeText(arFragment.getContext(), "Error:" + throwable.getMessage(), Toast.LENGTH_LONG).show();
return null;
}
);
}
@RequiresApi(api = Build.VERSION_CODES.N)
private void onUpdateFrame(FrameTime frameTime) {
Frame frame = arFragment.getArSceneView().getArFrame();
Collection<AugmentedImage> augmentedImages = frame.getUpdatedTrackables(AugmentedImage.class);
for (AugmentedImage augmentedImage : augmentedImages) {
if (augmentedImage.getTrackingState() == TrackingState.TRACKING) {
if (augmentedImage.getName().equals("tiger") && shouldAddModel) {
placeObject(arFragment, augmentedImage.createAnchor(augmentedImage.getCenterPose()), Uri.parse("Mesh_BengalTiger.sfb"));
shouldAddModel = false;
}
}
}
}
public boolean setupAugmentedImagesDb(Config config, Session session) {
AugmentedImageDatabase augmentedImageDatabase;
Bitmap bitmap = loadAugmentedImage();
if (bitmap == null) {
return false;
}
augmentedImageDatabase = new AugmentedImageDatabase(session);
augmentedImageDatabase.addImage("tiger", bitmap);
config.setAugmentedImageDatabase(augmentedImageDatabase);
return true;
}
private Bitmap loadAugmentedImage() {
try (InputStream is = getAssets().open("blanket.jpeg")) {
return BitmapFactory.decodeStream(is);
} catch (IOException e) {
Log.e("ImageLoad", "IO Exception", e);
}
return null;
}
private void addNodeToScene(ArFragment arFragment, Anchor anchor, Renderable renderable) {
AnchorNode anchorNode = new AnchorNode(anchor);
TransformableNode node = new TransformableNode(arFragment.getTransformationSystem());
node.setRenderable(renderable);
node.setParent(anchorNode);
arFragment.getArSceneView().getScene().addChild(anchorNode);
node.select();
}
}
Теперь запустите ваше приложение. Вы должны увидеть экран, как показано ниже. Подвигайте телефон немного над опорным объектом. И как только ARCore обнаружит опорное изображение в реальном мире, добавит на него вашу 3D-модель.
Читайте также: Создание вашего первого ARCore-приложения
Автор: Devcolibri