1. Wprowadzenie
ARCore to platforma do tworzenia aplikacji rzeczywistości rozszerzonej (AR) na urządzenia mobilne. ARCore Depth API od Google zapewnia dostęp do obrazu głębi dla każdej klatki w sesji ARCore. Każdy piksel na obrazie głębi zawiera pomiar odległości od aparatu do otoczenia.
Interfejs Raw Depth API udostępnia obrazy głębi, które nie są przekazywane przez operacje filtrowania przestrzeni ekranu zaprojektowane w celu wygładzania i interpolacji wyników. Te wartości są dokładniejsze pod względem geometrycznym, ale mogą zawierać brakujące dane i być mniej dopasowane do powiązanego obrazu z kamery.
Te ćwiczenia z programowania pokazują, jak używać interfejsu Raw Depth API do przeprowadzania analizy geometrii 3D sceny. Stworzysz prostą aplikację z rozszerzoną rzeczywistością, która wykorzystuje surowe dane o głębi do wykrywania i wizualizacji geometrii świata.
Interfejsy Depth API i Raw Depth API są obsługiwane tylko na niektórych urządzeniach z ARCore. Interfejs Depth API jest dostępny tylko na Androidzie.
Co utworzysz
W tym ćwiczeniu w Codelabs utworzysz aplikację, która wykorzystuje surowe obrazy głębi dla każdej klatki do przeprowadzania analizy geometrycznej otaczającego Cię świata. Ta aplikacja:
- Sprawdź, czy urządzenie docelowe obsługuje głębię.
- Pobierz surowy obraz głębi dla każdej klatki z kamery.
- Przekształć surowe obrazy głębi w punkty 3D i odfiltruj je na podstawie pewności i geometrii.
- Użyj surowej chmury punktów głębi, aby segmentować interesujące obiekty 3D.
|
Podgląd tego, co stworzysz. |
Uwaga: jeśli napotkasz problemy, przejdź do ostatniej sekcji, w której znajdziesz wskazówki dotyczące rozwiązywania problemów.
2. Wymagania wstępne
Aby ukończyć to ćwiczenie, potrzebujesz określonego sprzętu i oprogramowania.
Wymagania sprzętowe
- Urządzenie obsługujące ARCore z włączonym debugowaniem USB, podłączone kablem USB do komputera używanego do programowania. Urządzenie musi też obsługiwać interfejs Depth API.
Wymagania dotyczące oprogramowania
- pakiet SDK ARCore w wersji 1.31.0 lub nowszej.
- Komputer używany do programowania z zainstalowanym Androidem Studio (w wersji 4.0.1 lub nowszej).
3. Skonfiguruj
Konfigurowanie komputera używanego do programowania
Podłącz urządzenie ARCore do komputera za pomocą kabla USB. Upewnij się, że urządzenie umożliwia debugowanie USB. Otwórz terminal i uruchom polecenie adb devices, jak pokazano poniżej:
adb devices List of devices attached <DEVICE_SERIAL_NUMBER> device
Wartość <DEVICE_SERIAL_NUMBER> będzie ciągiem znaków unikalnym dla Twojego urządzenia. Zanim przejdziesz dalej, upewnij się, że widzisz dokładnie 1 urządzenie.
Pobieranie i instalowanie kodu
Możesz sklonować repozytorium:
git clone https://github.com/googlecodelabs/arcore-rawdepthapi
Możesz też pobrać plik ZIP i go rozpakować:
Aby rozpocząć pracę z kodem, wykonaj te czynności.
- Uruchom Android Studio i kliknij Open an existing Android Studio project (Otwórz istniejący projekt Android Studio).
- Przejdź do lokalnego katalogu, w którym został zapisany plik ZIP z danymi o głębi.
- Kliknij dwukrotnie katalog
arcore_rawdepthapi_codelab.
Katalog arcore_rawdepthapi_codelab to pojedynczy projekt Gradle z wieloma modułami. Jeśli w lewym górnym rogu Androida Studio nie widzisz jeszcze okienka Projekt, w menu kliknij Projekty.
Wynik powinien wyglądać tak:
| Ten projekt zawiera te moduły:
|
Będziesz pracować w part0_work. Dostępne są też kompletne rozwiązania dla każdej części ćwiczenia (w Codelabs). Każdy moduł to aplikacja, którą można skompilować.
4. Uruchamianie aplikacji startowej
Aby uruchomić aplikację startową Raw Depth, wykonaj te czynności.
- Kliknij Uruchom > Uruchom… > ‘part0_work'.
- W oknie Wybierz miejsce docelowe wdrożenia wybierz urządzenie z listy Połączone urządzenia i kliknij OK.
Android Studio skompiluje początkową aplikację i uruchomi ją na urządzeniu.
| Gdy uruchomisz aplikację po raz pierwszy, poprosi ona o uprawnienia do korzystania z aparatu. Aby kontynuować, kliknij Zezwól. |
| Obecnie aplikacja nic nie robi. Jest to najbardziej podstawowa aplikacja AR, która wyświetla obraz z kamery, ale nie robi nic więcej.Istniejący kod jest podobny do przykładu Hello AR opublikowanego w pakiecie ARCore SDK. |
Następnie użyjesz interfejsu Raw Depth API, aby pobrać geometrię sceny wokół siebie.
5. Konfigurowanie interfejsu Raw Depth API (część 1)
Sprawdź, czy urządzenie docelowe obsługuje głębię.
Nie wszystkie urządzenia obsługujące ARCore mogą korzystać z interfejsu Depth API. Zanim dodasz do aplikacji funkcję głębi w funkcji onResume() modułu RawDepthCodelabActivity.java, w której tworzona jest nowa sesja, upewnij się, że urządzenie docelowe obsługuje głębię.
Znajdź istniejący kod:
// Create the ARCore session.
session = new Session(/* context= */ this);
Zaktualizuj ją, aby aplikacja działała tylko na urządzeniach obsługujących Depth API.
// Create the ARCore session.
session = new Session(/* context= */ this);
if (!session.isDepthModeSupported(Config.DepthMode.RAW_DEPTH_ONLY)) {
message =
"This device does not support the ARCore Raw Depth API. See" +
"https://developers.google.com/ar/devices for
a list of devices that do.";
}
Włączanie surowej głębi
Interfejs Raw Depth API udostępnia wygładzony obraz głębi i odpowiadający mu obraz ufności zawierający ufność głębi dla każdego piksela na obrazie głębi. Włącz Raw Depth, aktualizując poniższy kod w instrukcji try-catch, którą właśnie zmodyfikowano.
try {
// ************ New code to add ***************
// Enable raw depth estimation and auto focus mode while ARCore is running.
Config config = session.getConfig();
config.setDepthMode(Config.DepthMode.RAW_DEPTH_ONLY);
config.setFocusMode(Config.FocusMode.AUTO);
session.configure(config);
// ************ End new code to add ***************
session.resume();
} catch (CameraNotAvailableException e) {
messageSnackbarHelper.showError(this, "Camera not available. Try restarting the app.");
session = null;
return;
}
Sesja AR jest teraz odpowiednio skonfigurowana i aplikacja może korzystać z funkcji opartych na głębi.
Wywoływanie interfejsu Depth API
Następnie wywołaj interfejs Depth API, aby pobrać obrazy głębi dla każdej klatki. Utwórz nowy plik, aby umieścić dane o głębi w nowej klasie. Kliknij prawym przyciskiem myszy folder rawdepth i wybierz New > Java Class. Spowoduje to utworzenie pustego pliku. Dodaj do tych zajęć:
src/main/java/com/google/ar/core/codelab/rawdepth/DepthData.java
package com.google.ar.core.codelab.rawdepth;
import android.media.Image;
import android.opengl.Matrix;
import com.google.ar.core.Anchor;
import com.google.ar.core.CameraIntrinsics;
import com.google.ar.core.Frame;
import com.google.ar.core.exceptions.NotYetAvailableException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.ShortBuffer;
/**
* Convert depth data from ARCore depth images to 3D pointclouds. Points are added by calling the
* Raw Depth API, and reprojected into 3D space.
*/
public class DepthData {
public static final int FLOATS_PER_POINT = 4; // X,Y,Z,confidence.
}
Ta klasa służy do konwertowania obrazów głębi na chmury punktów. Chmury punktów reprezentują geometrię sceny za pomocą listy punktów, z których każdy ma współrzędne 3D (x, y, z) i poziom ufności z zakresu od 0 do 1.
Dodaj wywołania, aby wypełnić te wartości za pomocą interfejsu Raw Depth API, dodając create()metodę u dołu klasy. Ta metoda wysyła zapytanie o najnowsze obrazy głębi i pewności, a następnie zapisuje wynikową chmurę punktów. Obrazy głębi i ufności będą zawierać pasujące dane.
public static FloatBuffer create(Frame frame, Anchor cameraPoseAnchor) {
try {
Image depthImage = frame.acquireRawDepthImage16Bits();
Image confidenceImage = frame.acquireRawDepthConfidenceImage();
// Retrieve the intrinsic camera parameters corresponding to the depth image to
// transform 2D depth pixels into 3D points. See more information about the depth values
// at
// https://developers.google.com/ar/develop/java/depth/overview#understand-depth-values.
final CameraIntrinsics intrinsics = frame.getCamera().getTextureIntrinsics();
float[] modelMatrix = new float[16];
cameraPoseAnchor.getPose().toMatrix(modelMatrix, 0);
final FloatBuffer points = convertRawDepthImagesTo3dPointBuffer(
depthImage, confidenceImage, intrinsics, modelMatrix);
depthImage.close();
confidenceImage.close();
return points;
} catch (NotYetAvailableException e) {
// This normally means that depth data is not available yet.
// This is normal, so you don't have to spam the logcat with this.
}
return null;
}
|
|
|
|
|
|
|
|
Kod zapisuje też w tym momencie punkt zakotwiczenia kamery, dzięki czemu informacje o głębi można przekształcić we współrzędne świata, wywołując metodę pomocniczą convertRawDepthImagesTo3dPointBuffer(). Ta metoda pomocnicza pobiera każdy piksel z obrazu głębi i za pomocą parametrów wewnętrznych kamery przekształca głębię w punkt 3D względem kamery. Następnie punkt zakotwiczenia kamery jest używany do przekształcenia pozycji punktu na współrzędne świata. Każdy piksel jest przekształcany w punkt 3D (w metrach) i zapisywany wraz z poziomem ufności.
Dodaj do pliku DepthData.java tę metodę pomocniczą:
/** Apply camera intrinsics to convert depth image into a 3D pointcloud. */
private static FloatBuffer convertRawDepthImagesTo3dPointBuffer(
Image depth, Image confidence, CameraIntrinsics cameraTextureIntrinsics, float[] modelMatrix) {
// Java uses big endian so change the endianness to ensure
// that the depth data is in the correct byte order.
final Image.Plane depthImagePlane = depth.getPlanes()[0];
ByteBuffer depthByteBufferOriginal = depthImagePlane.getBuffer();
ByteBuffer depthByteBuffer = ByteBuffer.allocate(depthByteBufferOriginal.capacity());
depthByteBuffer.order(ByteOrder.LITTLE_ENDIAN);
while (depthByteBufferOriginal.hasRemaining()) {
depthByteBuffer.put(depthByteBufferOriginal.get());
}
depthByteBuffer.rewind();
ShortBuffer depthBuffer = depthByteBuffer.asShortBuffer();
final Image.Plane confidenceImagePlane = confidence.getPlanes()[0];
ByteBuffer confidenceBufferOriginal = confidenceImagePlane.getBuffer();
ByteBuffer confidenceBuffer = ByteBuffer.allocate(confidenceBufferOriginal.capacity());
confidenceBuffer.order(ByteOrder.LITTLE_ENDIAN);
while (confidenceBufferOriginal.hasRemaining()) {
confidenceBuffer.put(confidenceBufferOriginal.get());
}
confidenceBuffer.rewind();
// To transform 2D depth pixels into 3D points, retrieve the intrinsic camera parameters
// corresponding to the depth image. See more information about the depth values at
// https://developers.google.com/ar/develop/java/depth/overview#understand-depth-values.
final int[] intrinsicsDimensions = cameraTextureIntrinsics.getImageDimensions();
final int depthWidth = depth.getWidth();
final int depthHeight = depth.getHeight();
final float fx =
cameraTextureIntrinsics.getFocalLength()[0] * depthWidth / intrinsicsDimensions[0];
final float fy =
cameraTextureIntrinsics.getFocalLength()[1] * depthHeight / intrinsicsDimensions[1];
final float cx =
cameraTextureIntrinsics.getPrincipalPoint()[0] * depthWidth / intrinsicsDimensions[0];
final float cy =
cameraTextureIntrinsics.getPrincipalPoint()[1] * depthHeight / intrinsicsDimensions[1];
// Allocate the destination point buffer. If the number of depth pixels is larger than
// `maxNumberOfPointsToRender` we uniformly subsample. The raw depth image may have
// different resolutions on different devices.
final float maxNumberOfPointsToRender = 20000;
int step = (int) Math.ceil(Math.sqrt(depthWidth * depthHeight / maxNumberOfPointsToRender));
FloatBuffer points = FloatBuffer.allocate(depthWidth / step * depthHeight / step * FLOATS_PER_POINT);
float[] pointCamera = new float[4];
float[] pointWorld = new float[4];
for (int y = 0; y < depthHeight; y += step) {
for (int x = 0; x < depthWidth; x += step) {
// Depth images are tightly packed, so it's OK to not use row and pixel strides.
int depthMillimeters = depthBuffer.get(y * depthWidth + x); // Depth image pixels are in mm.
if (depthMillimeters == 0) {
// Pixels with value zero are invalid, meaning depth estimates are missing from
// this location.
continue;
}
final float depthMeters = depthMillimeters / 1000.0f; // Depth image pixels are in mm.
// Retrieve the confidence value for this pixel.
final byte confidencePixelValue =
confidenceBuffer.get(
y * confidenceImagePlane.getRowStride()
+ x * confidenceImagePlane.getPixelStride());
final float confidenceNormalized = ((float) (confidencePixelValue & 0xff)) / 255.0f;
// Unproject the depth into a 3D point in camera coordinates.
pointCamera[0] = depthMeters * (x - cx) / fx;
pointCamera[1] = depthMeters * (cy - y) / fy;
pointCamera[2] = -depthMeters;
pointCamera[3] = 1;
// Apply model matrix to transform point into world coordinates.
Matrix.multiplyMV(pointWorld, 0, modelMatrix, 0, pointCamera, 0);
points.put(pointWorld[0]); // X.
points.put(pointWorld[1]); // Y.
points.put(pointWorld[2]); // Z.
points.put(confidenceNormalized);
}
}
points.rewind();
return points;
}
Uzyskiwanie najnowszych danych o głębi w formacie RAW dla każdej klatki
Zmodyfikuj aplikację, aby pobierać informacje o głębi i dopasowywać je do współrzędnych świata dla każdej pozycji.
W RawDepthCodelabActivity.java w metodzie onDrawFrame() znajdź te wiersze:
Frame frame = session.update();
Camera camera = frame.getCamera();
// If the frame is ready, render the camera preview image to the GL surface.
backgroundRenderer.draw(frame);
Tuż pod nim dodaj te wiersze:
// Retrieve the depth data for this frame.
FloatBuffer points = DepthData.create(frame, session.createAnchor(camera.getPose()));
if (points == null) {
return;
}
if (messageSnackbarHelper.isShowing() && points != null) {
messageSnackbarHelper.hide(this);
}
6. Renderowanie danych o głębi (część 2)
Teraz, gdy masz chmurę punktów głębi, możesz zobaczyć, jak dane wyglądają po wyrenderowaniu na ekranie.
Dodawanie renderera do wizualizacji punktów głębi
Dodaj moduł renderujący, aby wizualizować punkty głębi.
Najpierw dodaj nową klasę, która będzie zawierać logikę renderowania. Ta klasa wykonuje operacje OpenGL, aby zainicjować shadery do wizualizacji chmury punktów głębi.
| Dodawanie klasy DepthRenderer
|
Wypełnij tę klasę tym kodem:
src/main/java/com/google/ar/core/codelab/common/rendering/DepthRenderer.java
package com.google.ar.core.codelab.common.rendering;
import android.content.Context;
import android.opengl.GLES20;
import android.opengl.Matrix;
import com.google.ar.core.Camera;
import com.google.ar.core.codelab.rawdepth.DepthData;
import java.io.IOException;
import java.nio.FloatBuffer;
public class DepthRenderer {
private static final String TAG = DepthRenderer.class.getSimpleName();
// Shader names.
private static final String VERTEX_SHADER_NAME = "shaders/depth_point_cloud.vert";
private static final String FRAGMENT_SHADER_NAME = "shaders/depth_point_cloud.frag";
public static final int BYTES_PER_FLOAT = Float.SIZE / 8;
private static final int BYTES_PER_POINT = BYTES_PER_FLOAT * DepthData.FLOATS_PER_POINT;
private static final int INITIAL_BUFFER_POINTS = 1000;
private int arrayBuffer;
private int arrayBufferSize;
private int programName;
private int positionAttribute;
private int modelViewProjectionUniform;
private int pointSizeUniform;
private int numPoints = 0;
public DepthRenderer() {}
public void createOnGlThread(Context context) throws IOException {
ShaderUtil.checkGLError(TAG, "Bind");
int[] buffers = new int[1];
GLES20.glGenBuffers(1, buffers, 0);
arrayBuffer = buffers[0];
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, arrayBuffer);
arrayBufferSize = INITIAL_BUFFER_POINTS * BYTES_PER_POINT;
GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, arrayBufferSize, null, GLES20.GL_DYNAMIC_DRAW);
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);
ShaderUtil.checkGLError(TAG, "Create");
int vertexShader =
ShaderUtil.loadGLShader(TAG, context, GLES20.GL_VERTEX_SHADER, VERTEX_SHADER_NAME);
int fragmentShader =
ShaderUtil.loadGLShader(TAG, context, GLES20.GL_FRAGMENT_SHADER, FRAGMENT_SHADER_NAME);
programName = GLES20.glCreateProgram();
GLES20.glAttachShader(programName, vertexShader);
GLES20.glAttachShader(programName, fragmentShader);
GLES20.glLinkProgram(programName);
GLES20.glUseProgram(programName);
ShaderUtil.checkGLError(TAG, "Program");
positionAttribute = GLES20.glGetAttribLocation(programName, "a_Position");
modelViewProjectionUniform = GLES20.glGetUniformLocation(programName, "u_ModelViewProjection");
// Sets the point size, in pixels.
pointSizeUniform = GLES20.glGetUniformLocation(programName, "u_PointSize");
ShaderUtil.checkGLError(TAG, "Init complete");
}
}
Renderowanie danych o głębi
Następnie podaj źródło shaderów renderowania. Dodaj poniższą metodę update() na dole klasy DepthRenderer. Ta metoda pobiera najnowsze informacje o głębi i kopiuje dane chmury punktów do procesora graficznego.
/**
* Update the OpenGL buffer contents to the provided point. Repeated calls with the same point
* cloud will be ignored.
*/
public void update(FloatBuffer points) {
ShaderUtil.checkGLError(TAG, "Update");
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, arrayBuffer);
// If the array buffer is not large enough to fit the new point cloud, resize it.
points.rewind();
numPoints = points.remaining() / DepthData.FLOATS_PER_POINT;
if (numPoints * BYTES_PER_POINT > arrayBufferSize) {
while (numPoints * BYTES_PER_POINT > arrayBufferSize) {
arrayBufferSize *= 2;
}
GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, arrayBufferSize, null, GLES20.GL_DYNAMIC_DRAW);
}
GLES20.glBufferSubData(
GLES20.GL_ARRAY_BUFFER, 0, numPoints * BYTES_PER_POINT, points);
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);
ShaderUtil.checkGLError(TAG, "Update complete");
}
Wyświetl najnowsze dane na ekranie, dodając metodę draw() na końcu klasy DepthRenderer. Ta metoda wykorzystuje informacje z chmury punktów 3D i rzutuje je z powrotem na widok z kamery, aby można było je wyświetlić na ekranie.
/** Render the point cloud. The ARCore point cloud is given in world space. */
public void draw(Camera camera) {
float[] projectionMatrix = new float[16];
camera.getProjectionMatrix(projectionMatrix, 0, 0.1f, 100.0f);
float[] viewMatrix = new float[16];
camera.getViewMatrix(viewMatrix, 0);
float[] viewProjection = new float[16];
Matrix.multiplyMM(viewProjection, 0, projectionMatrix, 0, viewMatrix, 0);
ShaderUtil.checkGLError(TAG, "Draw");
GLES20.glUseProgram(programName);
GLES20.glEnableVertexAttribArray(positionAttribute);
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, arrayBuffer);
GLES20.glVertexAttribPointer(positionAttribute, 4, GLES20.GL_FLOAT, false, BYTES_PER_POINT, 0);
GLES20.glUniformMatrix4fv(modelViewProjectionUniform, 1, false, viewProjection, 0);
// Set point size to 5 pixels.
GLES20.glUniform1f(pointSizeUniform, 5.0f);
GLES20.glDrawArrays(GLES20.GL_POINTS, 0, numPoints);
GLES20.glDisableVertexAttribArray(positionAttribute);
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);
ShaderUtil.checkGLError(TAG, "Draw complete");
}
Za pomocą zmiennej pointSizeUniform możesz ustawić rozmiar punktu w pikselach. W przykładowej aplikacji wartość pointSizeUniform jest ustawiona na 5 pikseli.
Dodawanie nowych shaderów
Istnieje wiele sposobów wyświetlania głębi i danych o głębi w aplikacji. W tym artykule dodasz kilka shaderów i utworzysz prostą wizualizację mapowania kolorów.
Dodaj nowe shadery .vert i .frag do katalogu src/main/assets/shaders/.
| Dodawanie nowego shadera .vertW Android Studio:
|
W nowym pliku .vert dodaj ten kod:
src/main/assets/shaders/depth_point_cloud.vert
uniform mat4 u_ModelViewProjection;
uniform float u_PointSize;
attribute vec4 a_Position;
varying vec4 v_Color;
// Return an interpolated color in a 6 degree polynomial interpolation.
vec3 GetPolynomialColor(in float x,
in vec4 kRedVec4, in vec4 kGreenVec4, in vec4 kBlueVec4,
in vec2 kRedVec2, in vec2 kGreenVec2, in vec2 kBlueVec2) {
// Moves the color space a little bit to avoid pure red.
// Removes this line for more contrast.
x = clamp(x * 0.9 + 0.03, 0.0, 1.0);
vec4 v4 = vec4(1.0, x, x * x, x * x * x);
vec2 v2 = v4.zw * v4.z;
return vec3(
dot(v4, kRedVec4) + dot(v2, kRedVec2),
dot(v4, kGreenVec4) + dot(v2, kGreenVec2),
dot(v4, kBlueVec4) + dot(v2, kBlueVec2)
);
}
// Return a smooth Percept colormap based upon the Turbo colormap.
vec3 PerceptColormap(in float x) {
const vec4 kRedVec4 = vec4(0.55305649, 3.00913185, -5.46192616, -11.11819092);
const vec4 kGreenVec4 = vec4(0.16207513, 0.17712472, 15.24091500, -36.50657960);
const vec4 kBlueVec4 = vec4(-0.05195877, 5.18000081, -30.94853351, 81.96403246);
const vec2 kRedVec2 = vec2(27.81927491, -14.87899417);
const vec2 kGreenVec2 = vec2(25.95549545, -5.02738237);
const vec2 kBlueVec2 = vec2(-86.53476570, 30.23299484);
const float kInvalidDepthThreshold = 0.01;
return step(kInvalidDepthThreshold, x) *
GetPolynomialColor(x, kRedVec4, kGreenVec4, kBlueVec4,
kRedVec2, kGreenVec2, kBlueVec2);
}
void main() {
// Color the pointcloud by height.
float kMinHeightMeters = -2.0f;
float kMaxHeightMeters = 2.0f;
float normalizedHeight = clamp((a_Position.y - kMinHeightMeters) / (kMaxHeightMeters - kMinHeightMeters), 0.0, 1.0);
v_Color = vec4(PerceptColormap(normalizedHeight), 1.0);
gl_Position = u_ModelViewProjection * vec4(a_Position.xyz, 1.0);
gl_PointSize = u_PointSize;
}
Ten shader wykorzystuje mapę kolorów Turbo, aby poprawić wizualizację. Wykonuje te czynności:
- Pobiera wysokość każdego punktu (oś Y we współrzędnych świata).
- Oblicza kolor powiązany z daną wysokością (czerwony – nisko, niebieski – wysoko).
- Oblicza pozycję każdego punktu na ekranie.
- Ustawia rozmiar (w pikselach) każdego punktu zgodnie z definicją w metodzie
DepthRenderer.update().
W tym samym katalogu utwórz shader fragmentów o nazwie depth_point_cloud.frag, powtarzając te same czynności opisane w tej sekcji.
Następnie dodaj do tego nowego pliku poniższy kod, aby renderować każdy punkt jako pojedynczy wierzchołek o jednolitym kolorze zdefiniowanym w programie cieniowania wierzchołków.
src/main/assets/shaders/depth_point_cloud.frag
precision mediump float;
varying vec4 v_Color;
void main() {
gl_FragColor = v_Color;
}
Aby zastosować to renderowanie, dodaj wywołania do klasy DepthRenderer w klasie RawDepthCodelabActivity.
src/main/java/com/google/ar/core/codelab/common/rendering/RawDepthCodelabActivity.java
import com.google.ar.core.codelab.common.rendering.DepthRenderer;
U góry zajęć dodaj prywatnego uczestnika obok ikony backgroundRenderer.
private final DepthRenderer depthRenderer = new DepthRenderer();
Element depthRenderer musi być zainicjowany w obrębie elementu RawDepthCodelabActivity.onSurfaceCreated(), tak jak istniejący element backgroundRenderer.
depthRenderer.createOnGlThread(/*context=*/ this);
Na końcu bloku try-catch w funkcji onDrawFrame dodaj ten kod, aby wyświetlić najnowszą głębię bieżącej klatki.
// Visualize depth points.
depthRenderer.update(points);
depthRenderer.draw(camera);
Po wprowadzeniu tych zmian aplikacja powinna się teraz prawidłowo skompilować i wyświetlić chmurę punktów głębi.
| Przykład wizualizacji surowej chmury punktów głębi
|
7. Analizowanie chmur punktów 3D (część 3)
Dane o głębi możesz analizować po sprawdzeniu, czy są dostępne w sesji AR. Ważnym narzędziem do analizowania głębi jest wartość ufności dla każdego piksela. Używaj wartości ufności do analizowania chmur punktów 3D.
Unieważnianie pikseli o niskim poziomie ufności
Wartość pewności została pobrana dla każdego piksela głębi i zapisana obok każdego punktu w DepthData, ale nie została jeszcze użyta.
Wartości confidenceNormalized mieszczą się w zakresie od 0 do 1, gdzie 0 oznacza niską ufność, a 1 – pełną ufność. Zmodyfikuj metodę convertRawDepthImagesTo3dPointBuffer() w klasie DepthData, aby uniknąć zapisywania pikseli, których poziom ufności jest zbyt niski, aby były przydatne.
final float confidenceNormalized = ((float) (confidencePixelValue & 0xff)) / 255.0f;
// ******** New code to add ************
if (confidenceNormalized < 0.3) {
// Ignores "low-confidence" pixels.
continue;
}
// ******** End of new code to add *********
Wypróbuj różne progi poziomu ufności, aby sprawdzić, ile punktów głębi jest zachowywanych na każdym poziomie.
|
|
|
|
|
Poziom ufności >= 0,1 | Poziom ufności >= 0,3 | Poziom ufności >= 0,5 | Poziom ufności >= 0,7 | Poziom ufności >= 0,9 |
Filtrowanie pikseli według odległości
Możesz też filtrować piksele głębi według odległości. Kolejne kroki dotyczą geometrii blisko aparatu. Aby zoptymalizować wydajność, możesz zignorować punkty, które są zbyt oddalone.
Zaktualizuj dodany kod sprawdzania wiarygodności, wpisując:
src/main/java/com/google/ar/core/codelab/rawdepth/DepthData.java
if (confidenceNormalized < 0.3 || depthMeters > 1.5) {
// Ignore "low-confidence" pixels or depth that is too far away.
continue;
}
Teraz zobaczysz tylko punkty o wysokim poziomie zaufania i punkty znajdujące się blisko.
| Filtrowanie według odległościOgranicza chmurę punktów do obszaru w odległości 1,5 m od kamery. |
Porównywanie punktów i płaszczyzn 3D
Możesz porównywać punkty i płaszczyzny geometrii 3D oraz używać ich do wzajemnego filtrowania, np. usuwać punkty znajdujące się blisko obserwowanych płaszczyzn AR.
W tym kroku pozostaną tylko punkty „niepłaskie”, które zwykle reprezentują powierzchnie obiektów w środowisku. Dodaj metodę filterUsingPlanes() na dole klasy DepthData. Ta metoda iteracyjnie sprawdza istniejące punkty, porównuje każdy punkt z każdą płaszczyzną i unieważnia wszystkie punkty, które znajdują się zbyt blisko płaszczyzny AR. W ten sposób pozostawia obszary niepłaskie, które wyróżniają obiekty w scenie.
src/main/java/com/google/ar/core/codelab/rawdepth/DepthData.java
public static void filterUsingPlanes(FloatBuffer points, Collection<Plane> allPlanes) {
float[] planeNormal = new float[3];
// Allocate the output buffer.
int numPoints = points.remaining() / DepthData.FLOATS_PER_POINT;
// Check each plane against each point.
for (Plane plane : allPlanes) {
if (plane.getTrackingState() != TrackingState.TRACKING || plane.getSubsumedBy() != null) {
continue;
}
// Compute the normal vector of the plane.
Pose planePose = plane.getCenterPose();
planePose.getTransformedAxis(1, 1.0f, planeNormal, 0);
// Filter points that are too close to the plane.
for (int index = 0; index < numPoints; ++index) {
// Retrieves the next point.
final float x = points.get(FLOATS_PER_POINT * index);
final float y = points.get(FLOATS_PER_POINT * index + 1);
final float z = points.get(FLOATS_PER_POINT * index + 2);
// Transform point to be in world coordinates, to match plane info.
float distance = (x - planePose.tx()) * planeNormal[0]
+ (y - planePose.ty()) * planeNormal[1]
+ (z - planePose.tz()) * planeNormal[2];
// Controls the size of objects detected.
// Smaller values mean smaller objects will be kept.
// Larger values will only allow detection of larger objects, but also helps reduce noise.
if (Math.abs(distance) > 0.03) {
continue; // Keep this point, since it's far enough away from the plane.
}
// Invalidate points that are too close to planar surfaces.
points.put(FLOATS_PER_POINT * index, 0);
points.put(FLOATS_PER_POINT * index + 1, 0);
points.put(FLOATS_PER_POINT * index + 2, 0);
points.put(FLOATS_PER_POINT * index + 3, 0);
}
}
}
Możesz dodać tę metodę do RawDepthCodelabActivity w metodzie onDrawFrame:
// ********** New code to add ************
// Filter the depth data.
DepthData.filterUsingPlanes(points, session.getAllTrackables(Plane.class));
// ********** End new code to add *******
// Visualize depth points.
depthRenderer.update(points);
depthRenderer.draw(camera);
Po uruchomieniu ćwiczeń z programowania renderowany jest teraz podzbiór punktów. Te punkty reprezentują obiekty w scenie, z pominięciem płaskich powierzchni, na których się znajdują. Możesz używać tych danych do szacowania rozmiaru i położenia obiektów przez grupowanie punktów.
|
|
|
|
Filiżanka herbaty | Mikrofon | Słuchawki | Poduszka |
Punkty klastra
Ten przewodnik zawiera bardzo prosty algorytm klastrowania chmury punktów. Zaktualizuj instrukcje, aby pogrupować pobrane chmury punktów w klastry zdefiniowane przez osie wyrównane do osi.
src/main/java/com/google/ar/core/codelab/rawdepth/RawDepthCodelabActivity.java
import com.google.ar.core.codelab.common.helpers.AABB;
import com.google.ar.core.codelab.common.helpers.PointClusteringHelper;
import com.google.ar.core.codelab.common.rendering.BoxRenderer;
import java.util.List;
Dodaj do tych zajęć element BoxRenderer u góry pliku, obok innych elementów renderujących.
private final BoxRenderer boxRenderer = new BoxRenderer();
W metodzie onSurfaceCreated() dodaj ten kod obok innych modułów renderujących:
boxRenderer.createOnGlThread(/*context=*/this);
Na koniec dodaj te wiersze do onDrawFrame() w RawDepthCodelabActivity, aby pogrupować pobrane chmury punktów w klastry i wyrenderować wyniki jako osie wyrównane do osi.
// Visualize depth points.
depthRenderer.update(points);
depthRenderer.draw(camera);
// ************ New code to add ***************
// Draw boxes around clusters of points.
PointClusteringHelper clusteringHelper = new PointClusteringHelper(points);
List<AABB> clusters = clusteringHelper.findClusters();
for (AABB aabb : clusters) {
boxRenderer.draw(aabb, camera);
}
// ************ End new code to add ***************
|
|
|
|
Filiżanka herbaty | Mikrofon | Słuchawki | Poduszka |
Możesz teraz pobierać surowe dane o głębi w ramach sesji ARCore, przekształcać informacje o głębi w chmury punktów 3D oraz wykonywać na tych punktach podstawowe operacje filtrowania i renderowania.
8. Kompilacja, uruchamianie i testowanie
Skompiluj, uruchom i przetestuj aplikację.
Tworzenie i uruchamianie aplikacji
Aby skompilować i uruchomić aplikację, wykonaj te czynności:
- Podłącz urządzenie obsługujące ARCore za pomocą kabla USB.
- Uruchom projekt, klikając przycisk ► na pasku menu.
- Poczekaj, aż aplikacja zostanie skompilowana i wdrożona na urządzeniu.
Gdy po raz pierwszy spróbujesz wdrożyć aplikację na urządzeniu, musisz
Zezwalaj na debugowanie USB
na urządzeniu. Aby kontynuować, wybierz OK.
Gdy po raz pierwszy uruchomisz aplikację na urządzeniu, pojawi się pytanie, czy aplikacja ma uprawnienia do korzystania z aparatu urządzenia. Aby dalej korzystać z funkcji AR, musisz zezwolić na dostęp.
Testowanie aplikacji
Po uruchomieniu aplikacji możesz przetestować jej podstawowe działanie, trzymając urządzenie, poruszając się po pomieszczeniu i powoli skanując obszar. Przed przejściem do następnego kroku spróbuj zebrać dane przez co najmniej 10 sekund i zeskanować obszar z kilku kierunków.
9. Gratulacje
Gratulacje! Udało Ci się utworzyć i uruchomić pierwszą aplikację rzeczywistości rozszerzonej opartą na głębi przy użyciu interfejsu ARCore Raw Depth API od Google. Jesteśmy ciekawi, co uda Ci się stworzyć.
10. Rozwiązywanie problemów
Konfigurowanie urządzenia z Androidem na potrzeby programowania
- Podłącz urządzenie do komputera używanego do programowania za pomocą kabla USB. Jeśli tworzysz aplikacje w systemie Windows, może być konieczne zainstalowanie odpowiedniego sterownika do USB dla urządzenia.
- Aby włączyć debugowanie USB w oknie Opcje programisty, wykonaj te czynności:
- Otwórz aplikację Ustawienia.
- Jeśli masz urządzenie z Androidem w wersji 8.0 lub nowszej, wybierz System.
- Przewiń w dół i kliknij Informacje o telefonie.
- Przewiń na dół i 7 razy kliknij Numer kompilacji.
- Wróć do poprzedniego ekranu, przewiń w dół i kliknij Opcje programisty.
- W oknie Opcje programisty przewiń w dół, aby znaleźć i włączyć Debugowanie USB.
Więcej szczegółowych informacji o tym procesie znajdziesz na stronie Google dla deweloperów aplikacji na Androida.
Błędy kompilacji związane z licencjami
Jeśli podczas kompilacji wystąpi błąd związany z licencjami (Failed to install the following Android SDK packages as some licences have not been accepted), możesz przejrzeć i zaakceptować te licencje za pomocą tych poleceń:
cd <path to Android SDK>
tools/bin/sdkmanager --licenses

























