ARCore-Rohtiefe

1. Einführung

ARCore ist eine Plattform zum Erstellen von Augmented-Reality-Apps (AR) 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 ein Entfernungsmaß von der Kamera zur Umgebung.

Die Raw Depth API liefert Tiefenbilder, die nicht durch Filtervorgänge nach dem Bildschirmbereich weitergegeben werden, um die Ergebnisse zu glätten und zu interpolieren. Diese Werte sind geometrisch genauer, enthalten jedoch möglicherweise fehlende Daten und sind weniger auf das zugehörige Kamerabild ausgerichtet.

In diesem Codelab wird gezeigt, wie du mit der Raw Depth API eine 3D-Geometrieanalyse der Szene durchführen kannst. Sie erstellen eine einfache AR-fähige App, die mithilfe von Tiefenrohdaten Geometrie der Welt erkennt und visualisiert.

Die Depth API und die Raw Depth API werden nur auf bestimmten ARCore-fähigen Geräten unterstützt. Die Depth API ist nur für Android verfügbar.

Aufgaben

In diesem Codelab erstellen Sie eine App, die für jeden Frame Rohbilder mit Tiefeninformationen verwendet, um geometrische Analysen Ihrer Umgebung durchzuführen. Diese App wird:

  1. Prüfen Sie, ob das Zielgerät „Tiefe“ unterstützt.
  2. Rufen Sie das unbearbeitete Tiefenbild für jeden Kameraframe ab.
  3. Projizieren Sie detaillierte Bilder in 3D-Formen und filtern Sie diese Punkte nach Konfidenz und Geometrie.
  4. Mithilfe der rohen Tiefenpunktwolke können Sie interessante 3D-Objekte segmentieren.

Vorschau auf das, was Sie entwickeln werden.

Hinweis: Falls dabei Probleme auftreten, springen Sie zum letzten Abschnitt, um Tipps zur Fehlerbehebung zu erhalten.

2. Vorbereitung

Für dieses Codelab benötigst du bestimmte Hardware und Software.

Hardwareanforderungen

  • Ein ARCore-kompatibles Gerät mit aktiviertem USB-Debugging, das über ein USB-Kabel an Ihren Entwicklungscomputer angeschlossen ist. Dieses Gerät muss auch die Depth API unterstützen.

Softwareanforderungen

3. Einrichten

Entwicklungscomputer einrichten

Verbinden Sie Ihr ARCore-Gerät über das USB-Kabel mit Ihrem Computer. Ihr Gerät muss USB-Debugging zulassen. Öffnen Sie ein Terminal und führen Sie adb devices wie unten gezeigt aus:

adb devices

List of devices attached
<DEVICE_SERIAL_NUMBER>    device

Die <DEVICE_SERIAL_NUMBER> ist ein eindeutiger String für Ihr Gerät. Vergewissere dich, dass du genau ein Gerät siehst, bevor du fortfährst.

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:

Führen Sie die folgenden Schritte aus, um mit dem Code zu arbeiten.

  1. Starten Sie Android Studio und wählen Sie Vorhandenes Android Studio-Projekt öffnen aus.
  2. Wechseln Sie in das lokale Verzeichnis, in dem Sie die ZIP-Datei mit der Rohtiefe gespeichert haben.
  3. Doppelklicken Sie auf das Verzeichnis arcore_rawdepthapi_codelab.

Das Verzeichnis arcore_rawdepthapi_codelab ist ein einzelnes Gradle-Projekt mit mehreren Modulen. Wenn der Projektbereich oben links in Android Studio nicht bereits im Projektbereich angezeigt wird, klicken Sie im Drop-down-Menü auf Projects (Projekte).

Das Ergebnis sollte wie folgt aussehen:

Dieses Projekt enthält die folgenden Module:

  • part0_work: Die Start-App. Wenn Sie dieses Codelab ausführen, sollten Sie Änderungen an diesem Modul vornehmen. Alle anderen Teile enthalten Referenzcode.
  • part1: Referenzcode dafür, wie Ihre Änderungen aussehen sollten, wenn Sie Teil 1 abschließen.
  • part2: Referenzcode, wenn Sie Teil 2 abschließen.
  • part3_completed: Referenzcode, wenn Sie Teil 3 (am Ende des Codelabs) abschließen.

Sie werden im Modul part0_work arbeiten. Es gibt auch vollständige Lösungen für jeden Teil des Codelabs. Jedes Modul ist eine programmierbare App.

4. Start-App ausführen

Führe die folgenden Schritte aus, um die Start-App für Rohtiefen auszuführen.

  1. Wählen Sie Ausführen > Ausführen... &gt; "part0_work".
  2. Wählen Sie im Dialogfeld Select Deployment Target (Bereitstellungsziel auswählen) Ihr Gerät in der Liste Connected Devices (Verbundene Geräte) aus und klicken Sie auf OK.

Android Studio erstellt die erste App und führt sie auf deinem Gerät aus.

Wenn Sie die App zum ersten Mal starten, wird die Berechtigung „KAMERA“ angefordert. Tippen Sie zum Fortfahren auf Zulassen.

Im Moment ist mit der App nichts passiert. Dies ist die einfachste AR-App, die eine Kameraansicht deiner Umgebung zeigt, aber nichts anderes tut.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 Szene um Sie herum abzurufen.

5. Raw Depth API einrichten (Teil 1)

Achten Sie darauf, dass das Zielgerät „Tiefe“ unterstützt

Die Depth API kann nicht auf allen von ARCore unterstützten Geräten ausgeführt werden. Prüfe, ob das Zielgerät „Tiefe“ unterstützt, bevor du deiner App in der onResume()-Funktion von RawDepthCodelabActivity.java Funktionen hinzufügst, wo eine neue Sitzung erstellt wird.

Suchen Sie den vorhandenen Code:

// Create the ARCore session.
session = new Session(/* context= */ this);

Aktualisieren Sie sie, damit die Anwendung 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.";
}

Rohtiefe aktivieren

Die Raw Depth API liefert ein nicht geglättetes Tiefenbild und ein entsprechendes Konfidenzbild, das die Konfidenz der einzelnen Pixel im Rohtiefenbild enthält. Aktivieren Sie die Rohtiefe, 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 Session ist jetzt entsprechend konfiguriert und die App kann tiefenbasierte 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 Geometrie der Szene in Form 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 zum Füllen dieser Werte mit der Raw Depth API hinzu, indem Sie am Ende der Klasse eine create()-Methode hinzufügen. Mit dieser Methode werden die neuesten Tiefen- und Konfidenzbilder abgefragt, wobei die resultierende Punktwolke gespeichert wird. Die Bilder für Tiefe und Konfidenz haben ü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;
}

acquireCameraImage()

acquireDepthImage16Bits()

acquireRawDepthImage16Bits()

acquireRawDepthConfidenceImage()

Der Code speichert zu diesem Zeitpunkt auch den Kameraanker, sodass die Tiefeninformationen durch Aufrufen der Hilfsmethode convertRawDepthImagesTo3dPointBuffer() in Weltkoordinaten umgewandelt werden können. Diese Hilfsmethode nimmt jedes Pixel im Tiefenbild und nutzt die intrinsischen Eigenschaften der Kamera, um die Tiefe relativ zur Kamera in einen 3D-Punkt zu projizieren. Dann wird der Kameraanker verwendet, um die Position des Punkts in Weltkoordinaten zu konvertieren. Jedes vorhandene Pixel wird in einen 3D-Punkt (in Einheiten von Metern) konvertiert und zusammen mit seiner Konfidenz 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 Rohtiefendaten für jeden Frame abrufen

Passen Sie die App an, um Tiefeninformationen abzurufen, und richten Sie sie für jede Pose an Weltkoordinaten aus.

Suchen Sie in RawDepthCodelabActivity.java in der Methode onDrawFrame() die 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)

Da Sie nun mit einer Tiefen-Punktwolke experimentieren können, sollten Sie sich ansehen, wie die auf dem Bildschirm gerenderten Daten aussehen.

Renderer hinzufügen, um Tiefenpunkte zu visualisieren

Fügen Sie einen Renderer hinzu, um die Tiefenpunkte zu visualisieren.

Fügen Sie zunächst eine neue Klasse für die Rendering-Logik hinzu. Diese Klasse führt die OpenGL-Vorgänge aus, um Shader zu initialisieren und die Tiefenpunktwolke zu visualisieren.

DepthRenderer-Klasse hinzufügen

  1. Klicken Sie mit der rechten Maustaste auf das Quellverzeichnis rendering.
  2. Wählen Sie „New > Java Class“ aus.
  3. Nennen Sie die Klasse DepthRenderer.

Füllen Sie diese Klasse mit dem folgenden Code:

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 am Ende der Klasse DepthRenderer die folgende Methode update() hinzu. Diese Methode verwendet die neuesten Tiefeninformationen als Eingabe und kopiert die Pointcloud-Daten 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");
    }

Zeichnen Sie die neuesten Daten auf dem Bildschirm, indem Sie am Ende der DepthRenderer-Klasse eine draw()-Methode hinzufügen. Bei dieser Methode werden die 3D-Punktwolkendaten 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 auf verschiedene Größen festlegen. In der Beispiel-App ist pointSizeUniform auf 5 Pixel festgelegt.

Neue Shader hinzufügen

Es gibt viele Möglichkeiten, Tiefendaten in Ihrer App anzusehen und darzustellen. Hier fügen Sie einige Shader hinzu und erstellen eine einfache Farbzuordnungsvisualisierung.

Fügen Sie dem Verzeichnis src/main/assets/shaders/ neue Shader vom Typ .vert und .frag hinzu.

Neuer .vert-Shader wird hinzugefügt

In Android Studio:

  1. Klicken Sie mit der rechten Maustaste auf das Shader-Verzeichnis.
  2. Wählen Sie Neu -> Datei
  3. Nennen Sie es depth_point_cloud.vert.
  4. Legen Sie sie als Textdatei fest.

Fügen Sie der neuen Vert-Datei den folgenden Code hinzu:

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;
}

Bei diesem Shader wird zur Verbesserung der Visualisierung die Turbo-Farbkarte verwendet. Dabei werden folgende Schritte ausgeführt:

  1. Ruft die Höhe jedes Punkts ab (y-Achse in Weltkoordinaten).
  2. Berechnet eine der Höhe entsprechende Farbe (rot=niedrig, blau=hoch).
  3. Berechnet die Bildschirmposition jedes Punkts.
  4. Legt die Größe (in Pixeln) für jeden Punkt fest, wie in der Methode DepthRenderer.update() definiert.

Erstellen Sie im selben Verzeichnis einen Fragment-Shader und nennen Sie ihn depth_point_cloud.frag. Wiederholen Sie dabei die gleichen Schritte in diesem Abschnitt.

Fügen Sie dann den folgenden Code in diese neue Datei ein, um jeden Punkt als einzelnen Scheitelpunkt einheitlicher Farbe zu rendern, wie im Scheitel-Shader definiert.

src/main/assets/shaders/depth_point_cloud.frag

precision mediump float;
varying vec4 v_Color;

void main() {
    gl_FragColor = v_Color;
}

Fügen Sie der Klasse DepthRenderer in Ihrem RawDepthCodelabActivity Aufrufe hinzu, um dieses Rendering anzuwenden.

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();

depthRenderer muss genau wie die vorhandene backgroundRenderer innerhalb von RawDepthCodelabActivity.onSurfaceCreated() initialisiert werden.

depthRenderer.createOnGlThread(/*context=*/ this);

Fügen Sie den folgenden Code am Ende des Try-Catch-Blocks in onDrawFrame ein, um die neueste Tiefe für den aktuellen Frame anzuzeigen.

// Visualize depth points.
depthRenderer.update(points);
depthRenderer.draw(camera);

Mit diesen Änderungen sollte die App jetzt erfolgreich erstellt werden und die Tiefenpunktwolke zeigen.

Beispiel für eine Punktwolkenvisualisierung mit Rohtiefe

  • Jedes Punktbeispiel wird nach seiner Tiefe gefärbt.
  • Rote Punkte sind nahe, grüne/blaue Punkte sind weiter entfernt
  • Fehlende Daten oder „Löcher“ sind in Bereichen mit unzureichenden Bildmerkmalen sichtbar, z. B. bei leeren weißen Wänden oder Decken.
  • Sie können mit der gerenderten Punktgröße experimentieren, indem Sie die Linie GLES20.glUniform1f(pointSizeUniform, 5.0f); innerhalb von DepthRenderer.draw() anpassen. Links sehen Sie die Punktgrößen 5 und 10.

7. 3D-Punktwolken analysieren (Teil 3)

Sie können Tiefendaten analysieren, sobald Sie überprüft haben, ob sie in einer AR-Sitzung vorhanden sind. Ein wichtiges Tool zur Analyse der Tiefe ist der Konfidenzwert für jedes Pixel. Analysieren Sie anhand von Konfidenzwerten 3D-Punktwolken.

Pixel mit niedrigem Konfidenzwert entwerten

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 Zuverlässigkeit und 1 für eine hohe Zuverlässigkeit steht. Ändern Sie die Methode convertRawDepthImagesTo3dPointBuffer() in der Klasse DepthData, um keine Pixel zu speichern, deren Konfidenz zu niedrig ist, um nützlich zu sein.

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 *********

Probieren Sie verschiedene Schwellenwerte für das Konfidenzniveau aus, um zu sehen, wie viele Tiefenpunkte auf jedem Level eingehalten werden.

Konfidenz >= 0,1

Konfidenz >= 0,3

Konfidenz >= 0,5

Konfidenz >= 0,7

Konfidenz >= 0,9

Pixel nach Abstand filtern

Sie können die Tiefenpixel auch nach Entfernung filtern. Bei den nächsten Schritten geht es um Geometrie in der Nähe der Kamera. Zur Leistungsoptimierung können Sie Punkte ignorieren, die zu weit entfernt sind.

Aktualisieren Sie den gerade hinzugefügten Code zur Konfidenzprüfung mit folgendem 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 Werte mit hoher Zuverlässigkeit und Abschlusspunkte angezeigt.

Nach Entfernung filtern

Beschränkt die Punktwolke auf einen Umkreis von 1,5 Metern von der Kamera.

3D-Punkte und Ebenen vergleichen

Sie können die geometrischen 3D-Punkte und -Ebenen vergleichen und miteinander filtern. So können Sie beispielsweise Punkte entfernen, die nahe an beobachteten AR-Ebenen liegen.

Bei diesem Schritt bleibt nur "nicht planar" Punkte, die in der Regel Oberflächen auf Objekten in der Umgebung darstellen. Fügen Sie am Ende der Klasse DepthData die Methode filterUsingPlanes() hinzu. Diese Methode durchläuft die vorhandenen Punkte, vergleicht jeden Punkt mit jeder Ebene und macht alle Punkte ungültig, die sich zu nahe an einer AR-Ebene befinden. So bleiben nicht planare Bereiche, die Objekte in der Szene hervorheben.

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 Methode onDrawFrame 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);

Beim Ausführen des Codelabs wird jetzt eine Teilmenge von Punkten gerendert. Diese Punkte stellen die Objekte in der Szene dar und ignorieren die flachen Oberflächen, auf denen die Objekte ruhen. Sie können diese Daten verwenden, um die Größe und Position von Objekten zu schätzen, indem Sie Punkte gruppieren.

Tasse Tee

Mikrofon

Kopfhörer

Pillow

Clusterpunkte

Dieses Codelab enthält einen sehr vereinfachten Punktcloud-Clustering-Algorithmus. Aktualisieren Sie das Codelab, um die abgerufenen Punktwolken in Clustern zu gruppieren, die durch achsenorientierte Begrenzungsrahmen definiert sind.

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() neben den anderen Renderern Folgendes hinzu:

boxRenderer.createOnGlThread(/*context=*/this);

Fügen Sie abschließend die folgenden Zeilen zu onDrawFrame() in RawDepthCodelabActivity hinzu, um die abgerufenen Punktwolken in Clustern zu gruppieren und die Ergebnisse als achsenorientierte 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 nun die Rohtiefe in einer ARCore-Sitzung abrufen, die Tiefeninformationen in 3D-Punktwolken umwandeln und für diese Punkte grundlegende Filter- und Rendering-Vorgänge ausführen.

8. Build-Run-Test

Erstellen Sie Ihre Anwendung, führen Sie sie aus und testen Sie sie.

App erstellen und ausführen

Führen Sie die folgenden Schritte aus, um Ihre App zu erstellen und auszuführen:

  1. Schließen Sie ein ARCore-kompatibles Gerät über USB an.
  2. Führen Sie das Projekt über die Schaltfläche ► in der Menüleiste aus.
  3. 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 Folgendes tun:

USB-Debugging zulassen

auf dem Gerät. Wähle „OK“ aus, um fortzufahren.

Wenn du deine App zum ersten Mal auf dem Gerät ausführst, wirst du gefragt, ob die App die Berechtigung zum Verwenden der Gerätekamera hat. Du musst den Zugriff erlauben, 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 in Ihrem Raum bewegen und langsam einen Bereich scannen. Versuchen Sie, mindestens 10 Sekunden an Daten zu erfassen und den Bereich aus verschiedenen Richtungen zu scannen, bevor Sie mit dem nächsten Schritt fortfahren.

9. Glückwunsch

Herzlichen Glückwunsch! Sie haben mit der ARCore Raw Depth API von Google Ihre erste tiefenbasierte Augmented Reality-App erfolgreich erstellt und ausgeführt. Wir sind gespannt, was Sie entwickeln werden!

10. Fehlerbehebung

Android-Gerät für die Entwicklung einrichten

  1. Verbinden Sie Ihr Gerät über ein USB-Kabel mit dem Entwicklungscomputer. Wenn Sie die Entwicklung unter Windows verwenden, müssen Sie möglicherweise den entsprechenden USB-Treiber für Ihr Gerät installieren.
  2. Führen Sie die folgenden Schritte aus, um USB-Debugging im Fenster Entwickleroptionen zu aktivieren:
  • Ö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 suchen und zu aktivieren.

Weitere Informationen zu diesem Vorgang finden Sie auf der Google-Website für Android-Entwickler.

Wenn ein Build-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 mit den folgenden Befehlen prüfen und akzeptieren:

cd <path to Android SDK>

tools/bin/sdkmanager --licenses

Häufig gestellte Fragen