1. Przegląd
ARCore to platforma do tworzenia aplikacji rzeczywistości rozszerzonej na Androida. Rozszerzone obrazy umożliwiają tworzenie aplikacji AR, które rozpoznają wstępnie zarejestrowane obrazy 2D w świecie rzeczywistym i umieszczają na nich wirtualne treści.
W tym Codelabs dowiesz się, jak zmodyfikować istniejącą przykładową aplikację ARCore, aby uwzględniała obrazy rozszerzone, które są ruchome lub nieruchome.
Co utworzysz
W tym ćwiczeniu będziesz pracować na podstawie gotowej przykładowej aplikacji ARCore. Po jego ukończeniu Twoja aplikacja będzie mogła:
- wykrywanie obrazu docelowego i dołączanie do niego wirtualnego labiryntu;
- śledzić poruszający się obiekt, dopóki znajduje się w polu widzenia kamery;

Czy to Twoja pierwsza aplikacja ARCore?
Czy zamierzasz pisać przykładowy kod w tym ćwiczeniu, czy tylko chcesz przeczytać te strony?
Czego się nauczysz
- Jak używać rozszerzonych obrazów w ARCore w Javie
- Jak ocenić, czy obraz może zostać rozpoznany przez ARCore
- Jak dołączyć wirtualną treść do obrazu i śledzić jej ruch
Wymagania wstępne
Aby ukończyć to ćwiczenie, potrzebujesz określonego sprzętu i oprogramowania.
Wymagania sprzętowe
- Urządzenie obsługujące ARCore podłączone kablem USB do komputera używanego do programowania
Wymagania dotyczące oprogramowania
- ARCore APK w wersji 1.9.0 lub nowszej. Ten plik APK jest zwykle automatycznie instalowany na urządzeniu za pomocą Sklepu Play.
- komputer używany do programowania z Androidem Studio (w wersji 3.1 lub nowszej);
- Dostęp do internetu, ponieważ podczas programowania musisz pobrać biblioteki.
Skoro masz już wszystko gotowe, zaczynajmy!
2. Konfigurowanie środowiska programistycznego
Pobieranie pakietu SDK
Zaczniemy od pobrania najnowszego pakietu ARCore Android SDK z GitHub. Rozpakuj go w wybranej lokalizacji. W tym ćwiczeniu najwcześniejsza wersja pakietu SDK to 1.18.1. Folder będzie oznaczony jako arcore-android-sdk-x.xx.x. Dokładna wartość będzie odpowiadać wersji używanego pakietu SDK.
Uruchom Android Studio i kliknij Open an existing Android Studio project (Otwórz istniejący projekt Android Studio).

Przejdź do tego rozpakowanego folderu:
arcore-android-sdk-x.xx.x/samples/augmented_image_java
Kliknij Otwórz.
Poczekaj, aż Android Studio zsynchronizuje projekt. Jeśli w Android Studio brakuje wymaganych komponentów, może pojawić się komunikat o błędzie Install missing platform and sync project. Aby rozwiązać problem, postępuj zgodnie z instrukcjami.
Uruchamianie przykładowej aplikacji
Projekt aplikacji ARCore działa, więc możemy go przetestować.
Podłącz urządzenie ARCore do komputera używanego do programowania i użyj menu Uruchom > Uruchom „aplikację”, aby uruchomić na urządzeniu wersję debugowania. W oknie dialogowym z prośbą o wybranie urządzenia, na którym ma zostać uruchomiony test, wybierz podłączone urządzenie i kliknij OK.


Ten przykładowy projekt korzysta z targetSdkVersion 28. Jeśli wystąpi błąd kompilacji, np. Failed to find Build Tools revision 28.0.3, postępuj zgodnie z instrukcjami w Android Studio, aby pobrać i zainstalować wymaganą wersję narzędzi do kompilacji Androida.
Jeśli wszystko przebiegnie prawidłowo, na urządzeniu uruchomi się przykładowa aplikacja, która poprosi Cię o przyznanie uprawnień do robienia zdjęć i nagrywania filmów. Aby przyznać uprawnienia, kliknij ZEZWÓL.
Testowanie za pomocą przykładowego obrazu
Po skonfigurowaniu środowiska programistycznego możesz przetestować aplikację, przekazując jej obraz do analizy.
Wróć do Android Studio i w oknie Project (Projekt) otwórz app > assets (aplikacja > zasoby) i kliknij dwukrotnie plik default.jpg, aby go otworzyć.

Skieruj aparat urządzenia na obraz Ziemi na ekranie i postępuj zgodnie z instrukcjami, aby dopasować skanowany obraz do celownika.
Na obrazie pojawi się ramka, tak jak poniżej:

Następnie wprowadzimy drobne ulepszenia w aplikacji przykładowej.
3. Wyświetlanie modelu labiryntu na obrazie 2D
Możesz zacząć korzystać z rozszerzonych obrazów, wyświetlając na nich model 3D.
Pobieranie modelu 3D
W tym ćwiczeniu użyjemy obrazu „Circle Maze - Green” autorstwa Evol, który jest dostępny na licencji CC-BY 3.0. Kopię tego modelu 3D przechowuję w repozytorium GitHub tego ćwiczenia, które znajdziesz tutaj.
Aby pobrać model i uwzględnić go w Android Studio, wykonaj te czynności.
- Otwórz repozytorium GitHub tego ćwiczenia w katalogu third_party.
- Kliknij GreenMaze_obj.zip, a potem przycisk Pobierz.
Spowoduje to pobranie pliku o nazwie GreenMaze_obj.zip.
- W Android Studio utwórz katalog
green-mazew folderze app > assets > models. - Rozpakuj
GreenMaze_obj.zipi skopiuj zawartość do tej lokalizacji:arcore-android-sdk-x.xx.x/samples/augmented_image_java/app/src/main/assets/models/green-maze - W Android Studio otwórz app > assets > models > green-maze.
W tym folderze powinny znajdować się 2 pliki: GreenMaze.obj i GreenMaze.mtl.

Renderowanie modelu labiryntu
Aby wyświetlić model 3D na istniejącym obrazie 2D, wykonaj te czynności:GreenMaze.obj
W AugmentedImageRenderer.java dodaj zmienną składową o nazwie mazeRenderer, aby renderować model labiryntu. Ponieważ labirynt powinien być dołączony do obrazu, warto umieścić element mazeRenderer w klasie AugmentedImageRenderer.
AugmentedImageRenderer.java
// Add a member variable to hold the maze model.
private final ObjectRenderer mazeRenderer = new ObjectRenderer();
W funkcji createOnGlThread() załaduj GreenMaze.obj. Dla uproszczenia użyj tej samej tekstury ramki co tekstura.
AugmentedImageRenderer.java
// Replace the definition of the createOnGlThread() function with the
// following code, which loads GreenMaze.obj.
public void createOnGlThread(Context context) throws IOException {
mazeRenderer.createOnGlThread(
context, "models/green-maze/GreenMaze.obj", "models/frame_base.png");
mazeRenderer.setMaterialProperties(0.0f, 3.5f, 1.0f, 6.0f);
}
Zastąp definicję funkcji draw() tym kodem: Dostosowuje rozmiar labiryntu do rozmiaru wykrytego obrazu i wyświetla go na ekranie.
AugmentedImageRenderer.java
// Adjust size of detected image and render it on-screen
public void draw(
float[] viewMatrix,
float[] projectionMatrix,
AugmentedImage augmentedImage,
Anchor centerAnchor,
float[] colorCorrectionRgba) {
float[] tintColor =
convertHexToColor(TINT_COLORS_HEX[augmentedImage.getIndex() % TINT_COLORS_HEX.length]);
final float mazeEdgeSize = 492.65f; // Magic number of maze size
final float maxImageEdgeSize = Math.max(augmentedImage.getExtentX(), augmentedImage.getExtentZ()); // Get largest detected image edge size
Pose anchorPose = centerAnchor.getPose();
float mazeScaleFactor = maxImageEdgeSize / mazeEdgeSize; // scale to set Maze to image size
float[] modelMatrix = new float[16];
// OpenGL Matrix operation is in the order: Scale, rotation and Translation
// So the manual adjustment is after scale
// The 251.3f and 129.0f is magic number from the maze obj file
// You mustWe need to do this adjustment because the maze obj file
// is not centered around origin. Normally when you
// work with your own model, you don't have this problem.
Pose mazeModelLocalOffset = Pose.makeTranslation(
-251.3f * mazeScaleFactor,
0.0f,
129.0f * mazeScaleFactor);
anchorPose.compose(mazeModelLocalOffset).toMatrix(modelMatrix, 0);
mazeRenderer.updateModelMatrix(modelMatrix, mazeScaleFactor, mazeScaleFactor/10.0f, mazeScaleFactor); // This line relies on a change in ObjectRenderer.updateModelMatrix later in this codelab.
mazeRenderer.draw(viewMatrix, projectionMatrix, colorCorrectionRgba, tintColor);
}
Teraz labirynt powinien wyświetlać się na obrazie default.jpg Ziemi.
Uwaga: ponieważ nie masz pełnej kontroli nad tym przykładowym modelem 3D, powyższy kod używa kilku „magicznych” liczb. Wymiary modelu labiryntu to 492,65 x 120 x 492,65, a jego środek znajduje się w punkcie (251,3, 60, -129,0). Zakres wartości współrzędnych X, Y i Z wierzchołków to odpowiednio [5,02, 497,67], [0, 120] i [-375,17, 117,25]. Dlatego skala modelu labiryntu musi wynosić image_size / 492.65. Symbol mazeModelLocalOffset został wprowadzony, ponieważ model 3D labiryntu nie jest wyśrodkowany względem początku układu współrzędnych (0, 0, 0).
Ściana labiryntu jest nadal nieco za wysoka, aby zmieścić się na zdjęciu. Utwórz funkcję pomocniczą updateModelMatrix(), która może skalować nierównomiernie osie X, Y i Z, aby zwiększyć wysokość labiryntu o 0,1. Pamiętaj, aby zachować istniejącą funkcję updateModelMatrix(float[] modelMatrix, float scaleFactor) i dodać przeciążenie funkcji updateModelMatrix(float[] modelMatrix, float scaleFactorX, float scaleFactorY, float scaleFactorZ) jako nową funkcję.
common/rendering/ObjectRenderer.java
// Scale X, Y, Z coordinates unevenly
public void updateModelMatrix(float[] modelMatrix, float scaleFactorX, float scaleFactorY, float scaleFactorZ) {
float[] scaleMatrix = new float[16];
Matrix.setIdentityM(scaleMatrix, 0);
scaleMatrix[0] = scaleFactorX;
scaleMatrix[5] = scaleFactorY;
scaleMatrix[10] = scaleFactorZ;
Matrix.multiplyMM(this.modelMatrix, 0, modelMatrix, 0, scaleMatrix, 0);
}
Uruchom kod. Labirynt powinien teraz idealnie pasować do obrazu.

4. Dodawanie Andy’ego do labiryntu
Teraz, gdy masz już labirynt, dodaj postać, która będzie się w nim poruszać. Użyj pliku andy.obj dołączonego do pakietu ARCore SDK na Androida. Zachowaj teksturę ramki obrazu, ponieważ różni się ona od zielonego labiryntu renderowanego na obrazie.
W AugmentedImageRenderer.java dodaj prywatny ObjectRenderer, aby wyrenderować Andy’ego.
AugmentedImageRenderer.java
// Render for Andy
private final ObjectRenderer andyRenderer = new ObjectRenderer();
Następnie zainicjuj andyRenderer na końcu createOnGlThread().
AugmentedImageRenderer.java
public void createOnGlThread(Context context) throws IOException {
// Initialize andyRenderer
andyRenderer.createOnGlThread(
context, "models/andy.obj", "models/andy.png");
andyRenderer.setMaterialProperties(0.0f, 3.5f, 1.0f, 6.0f);
}
Na koniec wyrenderuj Andy’ego stojącego na szczycie labiryntu na końcu funkcji draw().
AugmentedImageRenderer.java
public void draw(
float[] viewMatrix,
float[] projectionMatrix,
AugmentedImage augmentedImage,
Anchor centerAnchor,
float[] colorCorrectionRgba) {
// Render Andy, standing on top of the maze
Pose andyModelLocalOffset = Pose.makeTranslation(
0.0f,
0.1f,
0.0f);
anchorPose.compose(andyModelLocalOffset).toMatrix(modelMatrix, 0);
andyRenderer.updateModelMatrix(modelMatrix, 0.05f); // 0.05f is a Magic number to scale
andyRenderer.draw(viewMatrix, projectionMatrix, colorCorrectionRgba, tintColor);
}
Uruchom kod. Powinien być widoczny Andy stojący na szczycie labiryntu.

Określanie docelowej jakości obrazu
ARCore rozpoznaje obrazy na podstawie cech wizualnych. Ze względu na różnice w jakości nie wszystkie obrazy można łatwo rozpoznać.
arcoreimg to narzędzie wiersza poleceń, które pozwala określić, jak rozpoznawalny będzie obraz dla ARCore. Wynikiem jest liczba z zakresu od 0 do 100, przy czym 100 oznacza najłatwiejszy do rozpoznania.
Oto przykład:
arcore-android-sdk-x.xx.x/tools/arcoreimg/macos$
$ ./arcoreimg eval-img --input_image_path=/Users/username/maze.jpg
100
maze.jpg ma wartość 100, więc ARCore łatwo ją rozpoznaje.
5. Opcjonalnie: poruszaj się Andy po labiryncie
Na koniec możesz dodać kod, aby poruszać się po labiryncie. Na przykład użyj silnika fizycznego typu open source jBullet do obsługi symulacji fizyki. Możesz pominąć tę część.
Pobierz PhysicsController.java i dodaj go do projektu w katalogu.
arcore-android-sdk-x.xx.x/samples/augmented_image_java/app/src/main/java/com/google/ar/core/examples/java/augmentedimage/
W Android Studio dodaj plik GreenMaze.obj do katalogu zasobów projektu, aby można go było wczytać w czasie działania aplikacji. Skopiuj GreenMaze.obj z app > assets > models > green-maze do app > assets.
Dodaj te zależności do pliku build.gradle aplikacji.
app/build.gradle
// jbullet library
implementation 'cz.advel.jbullet:jbullet:20101010-1'
Zdefiniuj zmienną andyPose, aby zapisać pozycję bieżącej pozy Andy'ego.
AugmentedImageRenderer.java
// Create a new pose for the Andy
private Pose andyPose = Pose.IDENTITY;
Zmodyfikuj AugmentedImageRenderer.java, aby renderować Andy’ego za pomocą nowej zmiennej andyPose.
AugmentedImageRenderer.java
public void draw(
float[] viewMatrix,
float[] projectionMatrix,
AugmentedImage augmentedImage,
Anchor centerAnchor,
float[] colorCorrectionRgba) {
// Use these code to replace previous code for rendering the Andy object
//
// Adjust the Andy's rendering position
// The Andy's pose is at the maze's vertex's coordinate
Pose andyPoseInImageSpace = Pose.makeTranslation(
andyPose.tx() * mazeScaleFactor,
andyPose.ty() * mazeScaleFactor,
andyPose.tz() * mazeScaleFactor);
anchorPose.compose(andyPoseInImageSpace).toMatrix(modelMatrix, 0);
andyRenderer.updateModelMatrix(modelMatrix, 0.05f);
andyRenderer.draw(viewMatrix, projectionMatrix, colorCorrectionRgba, tintColor);
}
Dodaliśmy nową funkcję użytkową updateAndyPose(), która umożliwia otrzymywanie aktualizacji pozycji Andy’ego.
AugmentedImageRenderer.java
// Receive Andy pose updates
public void updateAndyPose(Pose pose) {
andyPose = pose;
}
W AugmentedImageActivity.java utwórz obiekt PhysicsController, który używa silnika fizycznego JBullet do zarządzania wszystkimi funkcjami związanymi z fizyką.
AugmentedImageActivity.java
import com.google.ar.core.Pose;
// Declare the PhysicsController object
private PhysicsController physicsController;
W silniku fizyki używamy sztywnej kuli do reprezentowania Andy’ego i aktualizowania jego pozycji na podstawie pozycji kuli. Wywołaj funkcję PhysicsController, aby zaktualizować fizykę, gdy tylko aplikacja rozpozna obraz. Aby poruszać piłką jak w prawdziwym świecie, zastosuj rzeczywistą grawitację, aby przesuwać ją w labiryncie.
AugmentedImageActivity.java
// Update the case clause for TRACKING to call PhysicsController
// whenever the app recognizes an image
private void drawAugmentedImages(
...
case TRACKING:
// Switch to UI Thread to update View
this.runOnUiThread(
new Runnable() {
@Override
public void run() {
fitToScanView.setVisibility(View.GONE);
}
});
// Create a new anchor for newly found images
if (!augmentedImageMap.containsKey(augmentedImage.getIndex())) {
Anchor centerPoseAnchor = augmentedImage.createAnchor(augmentedImage.getCenterPose());
augmentedImageMap.put(
augmentedImage.getIndex(), Pair.create(augmentedImage, centerPoseAnchor));
physicsController = new PhysicsController(this);
} else {
Pose ballPose = physicsController.getBallPose();
augmentedImageRenderer.updateAndyPose(ballPose);
// Use real world gravity, (0, -10, 0), as gravity
// Convert to Physics world coordinate(maze mesh has to be static)
// Use the converted coordinate as a force to move the ball
Pose worldGravityPose = Pose.makeTranslation(0, -10f, 0);
Pose mazeGravityPose = augmentedImage.getCenterPose().inverse().compose(worldGravityPose);
float mazeGravity[] = mazeGravityPose.getTranslation();
physicsController.applyGravityToBall(mazeGravity);
physicsController.updatePhysics();
}
break;
Uruchom aplikację. Andy powinien teraz realistycznie poruszać się, gdy przechylisz obraz.
W przykładzie poniżej obraz jest wyświetlany na innym telefonie, ale możesz użyć dowolnego urządzenia, np. tabletu, okładki książki w wersji papierowej lub wydrukowanego papieru przymocowanego do płaskiego obiektu.

To wszystko. Spróbuj przeprowadzić Andy’ego przez labirynt. Wskazówka: łatwiej znaleźć wyjście, gdy trzymasz obraz celu do góry nogami.
6. Gratulacje
Gratulacje! To już koniec tego ćwiczenia.
- Utworzenie i uruchomienie przykładowej aplikacji ARCore AugmentedImage Java.
- Zaktualizowano przykład, aby wyświetlał model labiryntu na obrazie w odpowiedniej skali.
- Wykorzystaj pozę na zdjęciu, aby zrobić coś zabawnego.
Jeśli chcesz zapoznać się z pełnym kodem, możesz go pobrać tutaj.