1. Einführung
ARCore ist eine Plattform zum Erstellen von Augmented Reality-Apps (AR-Apps) auf Mobilgeräten. Die ARCore Depth API von Google bietet Zugriff auf ein Tiefenbild für jeden Frame in einer ARCore-Sitzung. Jedes Pixel im Tiefenbild liefert eine Entfernungsmessung von der Kamera zur Umgebung.
Die Raw Depth API liefert Tiefenbilder, die nicht durch Screen-Space-Filtervorgänge geleitet werden, die darauf ausgelegt sind, die Ergebnisse zu glätten und zu interpolieren. Diese Werte sind geometrisch genauer, können aber fehlende Daten enthalten und weniger mit dem zugehörigen Kamerabild übereinstimmen.
In diesem Codelab wird gezeigt, wie Sie die Raw Depth API verwenden, um eine 3D-Geometrieanalyse der Szene durchzuführen. Sie entwickeln eine einfache AR-fähige App, die Rohdaten zur Tiefe verwendet, um die Geometrie der Welt zu erkennen und zu visualisieren.
Die APIs für Tiefe und Rohdaten für Tiefe werden nur auf einer Teilmenge von ARCore-kompatiblen Geräten unterstützt. Die Depth API ist nur auf Android-Geräten verfügbar.
Aufgaben
In diesem Codelab erstellen Sie eine App, die Roh-Tiefenbilder für jeden Frame verwendet, um eine geometrische Analyse der Umgebung durchzuführen. Diese App kann:
- Prüfen Sie, ob das Zielgerät die Funktion „Tiefe“ unterstützt.
- Rufen Sie das Roh-Tiefenbild für jeden Kamerabild ab.
- Rohe Tiefenbilder in 3D-Punkte umwandeln und diese Punkte nach Konfidenz und Geometrie filtern.
- Verwenden Sie die Rohdaten-Tiefenpunktwolke, um 3D-Objekte zu segmentieren.
|
Vorschau auf das, was Sie erstellen werden. |
Hinweis: Falls Probleme auftreten, finden Sie im letzten Abschnitt einige Tipps zur Fehlerbehebung.
2. Vorbereitung
Für dieses Codelab benötigen Sie bestimmte Hardware und Software.
Hardwareanforderungen
- Ein von ARCore unterstütztes Gerät mit aktiviertem USB-Debugging, das über ein USB-Kabel mit Ihrem Entwicklungscomputer verbunden ist. Dieses Gerät muss auch die Depth API unterstützen.
Softwareanforderungen
- ARCore SDK 1.31.0 oder höher.
- Ein Entwicklungssystem, auf dem Android Studio (Version 4.0.1 oder höher) installiert ist.
3. Einrichten
Entwicklungscomputer einrichten
Verbinden Sie Ihr ARCore-Gerät über das USB-Kabel mit dem Computer. Achten Sie darauf, dass Ihr Gerät USB-Debugging zulässt. Öffnen Sie ein Terminal und führen Sie adb devices aus, wie unten gezeigt:
adb devices List of devices attached <DEVICE_SERIAL_NUMBER> device
<DEVICE_SERIAL_NUMBER> ist ein für Ihr Gerät eindeutiger String. Achten Sie darauf, dass genau ein Gerät angezeigt wird, bevor Sie fortfahren.
Code herunterladen und installieren
Sie können das Repository entweder klonen:
git clone https://github.com/googlecodelabs/arcore-rawdepthapi
Oder laden Sie eine ZIP-Datei herunter und extrahieren Sie sie:
So können Sie mit dem Code arbeiten:
- Starten Sie Android Studio und wählen Sie Open an existing Android Studio project (Vorhandenes Android Studio-Projekt öffnen) aus.
- Rufen Sie das lokale Verzeichnis auf, in dem Sie die ZIP-Datei mit den Rohdaten für die Tiefe gespeichert haben.
- Doppelklicken Sie auf das Verzeichnis
arcore_rawdepthapi_codelab.
Das Verzeichnis arcore_rawdepthapi_codelab ist ein einzelnes Gradle-Projekt mit mehreren Modulen. Wenn der Bereich „Projekt“ oben links in Android Studio noch nicht angezeigt wird, klicken Sie im Drop-down-Menü auf Projekte.
Das Ergebnis sollte so aussehen:
| Dieses Projekt enthält die folgenden Module:
|
Sie arbeiten im Modul part0_work. Außerdem gibt es vollständige Lösungen für jeden Teil des Codelabs. Jedes Modul ist eine App, die erstellt werden kann.
4. Start-App ausführen
So führen Sie die Raw Depth-Starter-App aus:
- Gehen Sie zu Run > Run... (Ausführen > Ausführen...). > ‘part0_work'.
- Wählen Sie im Dialogfeld Select Deployment Target (Bereitstellungsziel auswählen) Ihr Gerät aus der Liste Connected Devices (Verbundene Geräte) aus und klicken Sie auf OK.
Android Studio erstellt die erste App und führt sie auf Ihrem Gerät aus.
| Wenn Sie die App zum ersten Mal ausführen, wird die Berechtigung CAMERA angefordert. Tippe auf Zulassen, um fortzufahren. |
| Derzeit macht die App nichts. Dies ist die einfachste AR-Anwendung. Sie zeigt eine Kameraansicht Ihrer Szene, aber sonst nichts. Der vorhandene Code ähnelt dem Hello AR-Beispiel, das mit dem ARCore SDK veröffentlicht wurde. |
Als Nächstes verwenden Sie die Raw Depth API, um die Geometrie der Umgebung abzurufen.
5. Raw Depth API einrichten (Teil 1)
Prüfen, ob das Zielgerät Tiefe unterstützt
Nicht alle Geräte mit ARCore-Unterstützung können die Depth API ausführen. Prüfen Sie, ob das Zielgerät Tiefe unterstützt, bevor Sie Ihrer App in der onResume()-Funktion von RawDepthCodelabActivity.java, in der eine neue Sitzung erstellt wird, Funktionen hinzufügen.
So finden Sie den vorhandenen Code:
// Create the ARCore session.
session = new Session(/* context= */ this);
Aktualisieren Sie die App, damit sie nur auf Geräten ausgeführt wird, die die Depth API unterstützen.
// 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.";
}
Rohdaten für Tiefe aktivieren
Die Raw Depth API bietet ein ungeglättetes Tiefenbild und ein entsprechendes Konfidenzbild mit der Tiefenkonfidenz für jedes Pixel im Rohdaten-Tiefenbild. Aktivieren Sie „Raw Depth“, indem Sie den folgenden Code unter der try-catch-Anweisung aktualisieren, die Sie gerade geändert haben.
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;
}
Die AR-Sitzung ist jetzt richtig konfiguriert und die App kann tiefenbasierten Funktionen verwenden.
Depth API aufrufen
Rufen Sie als Nächstes die Depth API auf, um Tiefenbilder für jeden Frame abzurufen. Kapseln Sie die Tiefendaten in einer neuen Klasse, indem Sie eine neue Datei erstellen. Klicken Sie mit der rechten Maustaste auf den Ordner rawdepth und wählen Sie New > Java Class aus. Dadurch wird eine leere Datei erstellt. Fügen Sie diesem Kurs Folgendes hinzu:
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.
}
Mit dieser Klasse werden Tiefenbilder in Punktwolken konvertiert. Punktwolken stellen die Szenengeometrie mit einer Liste von Punkten dar, die jeweils eine 3D-Koordinate (x, y, z) und einen Konfidenzwert im Bereich von 0 bis 1 haben.
Fügen Sie Aufrufe hinzu, um diese Werte mithilfe der Raw Depth API zu erfassen. Fügen Sie dazu unten in der Klasse eine create()-Methode ein. Mit dieser Methode werden die neuesten Tiefen- und Konfidenzbilder abgefragt und die resultierende Punktwolke gespeichert. Die Tiefen- und Konfidenzbilder enthalten übereinstimmende Daten.
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;
}
|
|
|
|
|
|
|
|
Der Code speichert zu diesem Zeitpunkt auch den Kameraanker, sodass die Tiefeninformationen durch Aufrufen einer Hilfsmethode convertRawDepthImagesTo3dPointBuffer() in Weltkoordinaten umgewandelt werden können. Diese Hilfsmethode verwendet jeden Pixel im Tiefenbild und die intrinsischen Kameraparameter, um die Tiefe in einen 3D-Punkt relativ zur Kamera zu deprojizieren. Anschließend wird der Kameraanker verwendet, um die Position des Punktes in Weltkoordinaten zu konvertieren. Jedes vorhandene Pixel wird in einen 3D-Punkt (in Metern) umgewandelt und zusammen mit dem Konfidenzwert gespeichert.
Fügen Sie DepthData.java die folgende Hilfsmethode hinzu:
/** 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;
}
Aktuelle Rohdaten zur Tiefe für jeden Frame abrufen
Ändern Sie die App so, dass sie für jede Pose Tiefeninformationen abruft und an Weltkoordinaten ausrichtet.
Suchen Sie in RawDepthCodelabActivity.java in der Methode onDrawFrame() nach den vorhandenen Zeilen:
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);
Fügen Sie direkt darunter die folgenden Zeilen ein:
// 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. Tiefendaten rendern (Teil 2)
Nachdem Sie nun eine Tiefenpunktwolke haben, können Sie sich ansehen, wie die Daten auf dem Bildschirm gerendert werden.
Renderer zum Visualisieren von Tiefenpunkten hinzufügen
Fügen Sie einen Renderer hinzu, um die Tiefenpunkte zu visualisieren.
Fügen Sie zuerst eine neue Klasse hinzu, die die Rendering-Logik enthält. Diese Klasse führt die OpenGL-Vorgänge aus, um Shader zu initialisieren, mit denen die Tiefenpunktwolke visualisiert wird.
| DepthRenderer-Klasse hinzufügen
|
Fügen Sie in diese Klasse den folgenden Code ein:
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");
}
}
Tiefendaten rendern
Geben Sie als Nächstes die Quelle für die Rendering-Shader an. Fügen Sie die folgende update()-Methode unten in die Klasse DepthRenderer ein. Diese Methode verwendet die neuesten Tiefeninformationen als Eingabe und kopiert die Punktwolkendaten auf die GPU.
/**
* 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");
}
Fügen Sie der Klasse DepthRenderer unten eine draw()-Methode hinzu, um die neuesten Daten auf dem Bildschirm darzustellen. Bei dieser Methode werden die 3D-Punktwolkeninformationen zurück in die Kameraansicht projiziert, damit sie auf dem Bildschirm gerendert werden können.
/** 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");
}
Mit der Variablen pointSizeUniform können Sie die Punktgröße in Pixeln festlegen. pointSizeUniform ist in der Beispiel-App auf 5 Pixel festgelegt.
Neue Shader hinzufügen
Es gibt viele Möglichkeiten, Tiefe in Ihrer App anzuzeigen und Tiefendaten darzustellen. Hier fügen Sie einige Shader hinzu und erstellen eine einfache Farbzuordnungsvisualisierung.
Fügen Sie dem Verzeichnis src/main/assets/shaders/ neue .vert- und .frag-Shader hinzu.
| Neuen .vert-Shader hinzufügenIn Android Studio:
|
Fügen Sie in die neue .vert-Datei den folgenden Code ein:
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;
}
Dieser Shader verwendet die Turbo-Farbkarte für eine verbesserte Visualisierung. Dabei werden die folgenden Schritte ausgeführt:
- Ruft die Höhe jedes Punktes ab (y-Achse in Weltkoordinaten).
- Berechnet eine Farbe, die mit der Höhe verknüpft ist (Rot=niedrig, Blau=hoch).
- Berechnet die Bildschirmposition jedes Punktes.
- Legt die Größe (in Pixel) für jeden Punkt fest, wie in der
DepthRenderer.update()-Methode definiert.
Erstellen Sie im selben Verzeichnis einen Fragment-Shader mit dem Namen depth_point_cloud.frag. Wiederholen Sie dazu die Schritte in diesem Abschnitt.
Fügen Sie dann den folgenden Code in diese neue Datei ein, um jeden Punkt als einzelnen Scheitelpunkt mit einheitlicher Farbe zu rendern, wie im Vertex-Shader definiert.
src/main/assets/shaders/depth_point_cloud.frag
precision mediump float;
varying vec4 v_Color;
void main() {
gl_FragColor = v_Color;
}
Wenn Sie dieses Rendering anwenden möchten, fügen Sie Aufrufe der Klasse DepthRenderer in Ihr RawDepthCodelabActivity ein.
src/main/java/com/google/ar/core/codelab/common/rendering/RawDepthCodelabActivity.java
import com.google.ar.core.codelab.common.rendering.DepthRenderer;
Fügen Sie oben im Kurs neben backgroundRenderer ein privates Mitglied hinzu.
private final DepthRenderer depthRenderer = new DepthRenderer();
Die depthRenderer muss in RawDepthCodelabActivity.onSurfaceCreated() initialisiert werden, genau wie die vorhandene backgroundRenderer.
depthRenderer.createOnGlThread(/*context=*/ this);
Fügen Sie am Ende des try-catch-Blocks in onDrawFrame den folgenden Code ein, um die aktuelle Tiefe für den aktuellen Frame anzuzeigen.
// Visualize depth points.
depthRenderer.update(points);
depthRenderer.draw(camera);
Nach diesen Änderungen sollte die App jetzt erfolgreich erstellt werden und die Tiefenpunktwolke anzeigen.
| Beispiel für die Visualisierung einer Rohpunktwolke mit Tiefeninformationen
|
7. 3D-Punktwolken analysieren (Teil 3)
Sie können Tiefendaten analysieren, sobald Sie überprüft haben, dass sie in einer AR-Sitzung vorhanden sind. Ein wichtiges Tool für die Analyse der Tiefe ist der Konfidenzwert für jedes Pixel. Verwenden Sie Konfidenzwerte, um 3D-Punktwolken zu analysieren.
Pixel mit geringer Konfidenz ungültig machen
Sie haben den Konfidenzwert für jedes Tiefenpixel abgerufen und zusammen mit jedem Punkt in DepthData gespeichert, aber noch nicht verwendet.
Die Werte für confidenceNormalized reichen von 0 bis 1, wobei 0 für eine geringe und 1 für eine hohe Zuverlässigkeit steht. Ändern Sie die Methode convertRawDepthImagesTo3dPointBuffer() in der Klasse DepthData, um zu vermeiden, dass Pixel mit einer zu geringen Wahrscheinlichkeit gespeichert werden.
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 *********
Testen Sie verschiedene Grenzwerte für die Vertrauenswürdigkeit, um zu sehen, wie viele Tiefenpunkte auf jeder Ebene beibehalten werden.
|
|
|
|
|
Konfidenz >= 0,1 | Konfidenz >= 0,3 | Konfidenz >= 0,5 | Konfidenz >= 0,7 | Konfidenz >= 0,9 |
Pixel nach Entfernung filtern
Sie können Tiefenpixel auch nach Entfernung filtern. In den nächsten Schritten geht es um die Geometrie in der Nähe der Kamera. Zur Leistungsoptimierung können Sie Punkte ignorieren, die zu weit entfernt sind.
Aktualisieren Sie den Code zum Überprüfen des Konfidenzwerts, den Sie gerade hinzugefügt haben, mit dem folgenden Code:
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;
}
Jetzt werden nur noch Punkte mit hoher Konfidenz und nahe Punkte angezeigt.
| EntfernungsfilterungBeschränkt die Punktwolke auf einen Bereich von 1,5 Metern um die Kamera. |
3D-Punkte und ‑Ebenen vergleichen
Sie können die 3D-Punkte und -Ebenen der Geometrie vergleichen und sie zum Filtern verwenden, z. B. um Punkte zu entfernen, die sich in der Nähe von beobachteten AR-Ebenen befinden.
In diesem Schritt werden nur „nicht planare“ Punkte beibehalten, die in der Regel Oberflächen auf Objekten in der Umgebung darstellen. Fügen Sie die Methode filterUsingPlanes() unten in die Klasse DepthData ein. Bei dieser Methode werden die vorhandenen Punkte durchlaufen, jeder Punkt wird mit jeder Ebene verglichen und alle Punkte, die zu nah an einer AR-Ebene liegen, werden ungültig gemacht. So bleiben nicht planare Bereiche übrig, in denen Objekte in der Szene hervorgehoben werden.
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);
}
}
}
Sie können diese Methode dem RawDepthCodelabActivity in der onDrawFrame-Methode hinzufügen:
// ********** 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);
Wenn Sie das Codelab jetzt ausführen, wird nur eine Teilmenge der Punkte gerendert. Diese Punkte stellen die Objekte in der Szene dar, wobei die ebenen Oberflächen, auf denen die Objekte stehen, ignoriert werden. Anhand dieser Daten können Sie die Größe und Position von Objekten schätzen, indem Sie Punkte gruppieren.
|
|
|
|
Tasse Tee | Mikrofon | Kopfhörer | Pillow |
Clusterpunkte
Dieses Codelab enthält einen sehr einfachen Algorithmus für das Clustering von Punktwolken. Aktualisieren Sie das Codelab, um die abgerufenen Punktwolken in Clustern zu gruppieren, die durch achsenparallele rechteckige Begrenzungsrahmen definiert werden.
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;
Fügen Sie dieser Klasse oben in der Datei, zusammen mit den anderen Renderern, ein BoxRenderer hinzu.
private final BoxRenderer boxRenderer = new BoxRenderer();
Fügen Sie in der Methode onSurfaceCreated() Folgendes neben den anderen Renderern hinzu:
boxRenderer.createOnGlThread(/*context=*/this);
Fügen Sie schließlich die folgenden Zeilen in onDrawFrame() innerhalb von RawDepthCodelabActivity ein, um die abgerufenen Punktwolken in Clustern zu gruppieren und die Ergebnisse als achsenparallele Begrenzungsrahmen zu rendern.
// 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 ***************
|
|
|
|
Tasse Tee | Mikrofon | Kopfhörer | Pillow |
Sie können jetzt Rohdaten zur Tiefe über eine ARCore-Sitzung abrufen, die Tiefeninformationen in 3D-Punktwolken umwandeln und grundlegende Filter- und Renderingvorgänge für diese Punkte ausführen.
8. Build-Run-Test
App erstellen, ausführen und testen
App erstellen und ausführen
So erstellen und führen Sie Ihre App aus:
- Schließen Sie ein Gerät mit ARCore-Unterstützung über USB an.
- Führen Sie Ihr Projekt mit der Schaltfläche ► in der Menüleiste aus.
- Warten Sie, bis die App erstellt und auf Ihrem Gerät bereitgestellt wurde.
Wenn Sie die App zum ersten Mal auf Ihrem Gerät bereitstellen, müssen Sie
USB-Debugging zulassen
auf dem Gerät. Wähle zum Fortfahren „OK“ aus.
Wenn Sie Ihre App zum ersten Mal auf dem Gerät ausführen, werden Sie gefragt, ob die App die Berechtigung hat, die Kamera Ihres Geräts zu verwenden. Sie müssen den Zugriff zulassen, um die AR-Funktionen weiterhin nutzen zu können.
App testen
Wenn Sie Ihre App ausführen, können Sie ihr grundlegendes Verhalten testen, indem Sie Ihr Gerät halten, sich im Raum bewegen und einen Bereich langsam scannen. Erfassen Sie mindestens 10 Sekunden lang Daten und scannen Sie den Bereich aus verschiedenen Richtungen, bevor Sie mit dem nächsten Schritt fortfahren.
9. Glückwunsch
Herzlichen Glückwunsch! Sie haben Ihre erste tiefenbasierte Augmented Reality-App mit der ARCore Raw Depth API von Google erstellt und ausgeführt. Wir sind gespannt auf Ihre Ideen!
10. Fehlerbehebung
Android-Gerät für die Entwicklung einrichten
- Verbinden Sie Ihr Gerät über ein USB-Kabel mit Ihrem Entwicklercomputer. Wenn Sie unter Windows entwickeln, müssen Sie möglicherweise den entsprechenden USB-Treiber für Ihr Gerät installieren.
- So aktivieren Sie USB-Debugging im Fenster Entwickleroptionen:
- Öffnen Sie die Einstellungen.
- Wenn auf Ihrem Gerät Android 8.0 oder höher installiert ist, wählen Sie System aus.
- Scrollen Sie nach unten und wählen Sie Über das Telefon aus.
- Scrollen Sie nach unten und tippen Sie siebenmal auf die Build-Nummer.
- Kehren Sie zum vorherigen Bildschirm zurück, scrollen Sie nach unten und tippen Sie auf Entwickleroptionen.
- Scrollen Sie im Fenster Entwickleroptionen nach unten, um USB-Debugging zu finden und zu aktivieren.
Fehler beim Erstellen im Zusammenhang mit Lizenzen
Wenn beim Erstellen ein Fehler im Zusammenhang mit Lizenzen (Failed to install the following Android SDK packages as some licences have not been accepted) auftritt, können Sie diese Lizenzen mit den folgenden Befehlen prüfen und akzeptieren:
cd <path to Android SDK>
tools/bin/sdkmanager --licenses

























