Profondità non elaborata di ARCore

1. Introduzione

ARCore è una piattaforma per la creazione di app di realtà aumentata (AR) sui dispositivi mobili. L'API ARCore Depth di Google fornisce l'accesso a un'immagine di profondità per ogni frame in una sessione ARCore. Ogni pixel nell'immagine di profondità fornisce una misurazione della distanza tra la fotocamera e l'ambiente.

L'API Raw Depth fornisce immagini di profondità che non vengono trasmesse attraverso operazioni di filtro dello spazio sullo schermo progettate per livellare e interpolare i risultati. Questi valori sono più precisi dal punto di vista geometrico, ma potrebbero contenere dati mancanti ed essere meno allineati con l'immagine della fotocamera associata.

Questo codelab mostra come utilizzare l'API Raw Depth per eseguire l'analisi della geometria 3D della scena. Creerai una semplice app compatibile con l'AR che utilizza dati di profondità non elaborati per rilevare e visualizzare la geometria del mondo.

Le API Depth e Raw Depth sono supportate solo su un sottoinsieme di dispositivi abilitati per ARCore. L'API Depth è disponibile solo su Android.

Cosa creerai

In questo codelab, creerai un'app che utilizza immagini di profondità non elaborate per ogni frame al fine di eseguire analisi geometriche del mondo che ti circonda. Questa app:

  1. Controlla se il dispositivo di destinazione supporta la profondità.
  2. Recupera l'immagine di profondità non elaborata per ogni fotogramma della fotocamera.
  3. Riproietta le immagini di profondità non elaborate in punti 3D e filtra questi punti in base a confidenza e geometria.
  4. Utilizza la nuvola di punti di profondità non elaborata per segmentare gli oggetti 3D di interesse.

Dai un'occhiata in anteprima a ciò che creerai.

Nota: se riscontri problemi durante la procedura, passa all'ultima sezione per alcuni suggerimenti sulla risoluzione dei problemi.

2. Prerequisiti

Per completare questo codelab, ti serviranno hardware e software specifici.

Requisiti hardware

  • Un dispositivo supportato da ARCore con il debug USB attivato e collegato tramite cavo USB al computer di sviluppo. Questo dispositivo deve anche supportare l'API Depth.

Requisiti software

3. Configura

Configura la macchina di sviluppo

Collega il dispositivo ARCore al computer tramite il cavo USB. Assicurati che il dispositivo consenta il debug USB. Apri un terminale ed esegui adb devices, come mostrato di seguito:

adb devices

List of devices attached
<DEVICE_SERIAL_NUMBER>    device

Il <DEVICE_SERIAL_NUMBER> sarà una stringa univoca per il tuo dispositivo. Prima di continuare, assicurati di vedere esattamente un solo dispositivo.

Scarica e installa il codice

Puoi clonare il repository:

git clone https://github.com/googlecodelabs/arcore-rawdepthapi

Oppure scarica un file ZIP ed estrailo:

Per iniziare a utilizzare il codice, segui questi passaggi.

  1. Avvia Android Studio e scegli Apri un progetto Android Studio esistente.
  2. Vai alla directory locale in cui hai archiviato il file ZIP di profondità non elaborata.
  3. Fai doppio clic sulla directory arcore_rawdepthapi_codelab.

La directory arcore_rawdepthapi_codelab è un singolo progetto Gradle con più moduli. Se il riquadro Progetto nella parte superiore sinistra di Android Studio non è già visualizzato nel riquadro Progetto, fai clic su Progetti dal menu a discesa.

Il risultato dovrebbe essere simile al seguente:

Questo progetto contiene i seguenti moduli:

  • part0_work: l'app iniziale. Dovresti apportare modifiche a questo modulo durante questo codelab. Tutte le altre parti contengono codice di riferimento.
  • part1: fai riferimento al codice di come dovrebbero essere le modifiche quando completi la Parte 1.
  • part2: codice di riferimento quando completi la Parte 2.
  • part3_completed: codice di riferimento quando completi la Parte 3, ovvero la fine del codelab.

Lavorerai nel modulo part0_work. Ci sono anche soluzioni complete per ogni parte del codelab. Ogni modulo è un'app generabile.

4. Esegui l'app iniziale

Segui questi passaggi per eseguire l'app iniziale di profondità non elaborata.

  1. Vai a Esegui >. Esegui... &gt; "part0_work".
  2. Nella finestra di dialogo Seleziona la destinazione del deployment, seleziona il tuo dispositivo dall'elenco Dispositivi connessi e fai clic su OK.

Android Studio creerà l'app iniziale e la eseguirà sul tuo dispositivo.

Quando esegui l'app per la prima volta, ti verrà richiesta l'autorizzazione CAMERA. Tocca Consenti per continuare.

Al momento, l'app non fa nulla.Si tratta dell'applicazione AR più basilare, che mostra una visione della scena con la fotocamera, ma non esegue altre operazioni.Il codice esistente è simile all'esempio ARCore pubblicato con l'SDK ARCore.

Poi utilizzerai l'API Raw Depth per recuperare la geometria della scena intorno a te.

5. Configurare l'API Raw Depth (parte 1)

Assicurati che il dispositivo di destinazione supporti la profondità

Non tutti i dispositivi supportati da ARCore possono eseguire l'API Depth. Assicurati che il dispositivo di destinazione supporti la profondità prima di aggiungere funzionalità all'app all'interno della funzione onResume() di RawDepthCodelabActivity.java, dove viene creata una nuova sessione.

Individua il codice esistente:

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

Aggiornala per assicurarti che l'applicazione venga eseguita solo su dispositivi in grado di supportare l'API Depth.

// 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.";
}

Attiva profondità non elaborata

L'API Raw Depth fornisce un'immagine di profondità non uniforme e un'immagine di confidenza corrispondente contenente la confidenza della profondità per ogni pixel nell'immagine di profondità non elaborata. Attiva la profondità non elaborata aggiornando il seguente codice nell'istruzione test-catch che hai appena modificato.

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

Ora la sessione AR è configurata correttamente e l'app può usare funzionalità basate sulla profondità.

Chiama l'API Depth

Successivamente, chiama l'API Depth per recuperare immagini depth per ogni frame. Incapsulare i dati di profondità in una nuova classe creando un nuovo file. Fai clic con il tasto destro del mouse sulla cartella rawdepth e seleziona New > Java Class. Viene creato un file vuoto. Aggiungi quanto segue a questo corso:

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.

}

Questa classe viene utilizzata per convertire le immagini di profondità in nuvole di punti. Le nuvole di punti rappresentano la geometria della scena con un elenco di punti ciascuno con una coordinata 3D (x, y, z) e un valore di confidenza compreso tra 0 e 1.

Aggiungi chiamate per compilare questi valori utilizzando l'API Raw Depth aggiungendo un metodo create() in fondo alla classe. Questo metodo esegue query sulle più recenti immagini di profondità e confidenza, archiviando la nuvola di punti risultante. Le immagini di profondità e affidabilità avranno dati corrispondenti.

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

Al momento il codice memorizza anche l'ancoraggio della fotocamera, in modo che le informazioni sulla profondità possano essere trasformate in coordinate mondiali chiamando un metodo di supporto convertRawDepthImagesTo3dPointBuffer(). Questo metodo di supporto prende in esame ogni pixel nell'immagine di profondità e utilizza le funzioni intrinseche della fotocamera per annullare la proiezione della profondità in un punto 3D rispetto alla fotocamera. Quindi viene utilizzato l'ancoraggio della fotocamera per convertire la posizione del punto in coordinate del mondo. Ogni pixel esistente viene convertito in un punto 3D (in unità di metri) e archiviato insieme alla sua confidenza.

Aggiungi il seguente metodo helper a DepthData.java:

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

Recupera i dati più recenti sulla profondità non elaborata per ogni frame

Modifica l'app per recuperare le informazioni sulla profondità e allinearle alle coordinate del mondo per ogni posa.

In RawDepthCodelabActivity.java, nel metodo onDrawFrame(), trova le righe esistenti:

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

Aggiungi le seguenti righe appena sotto:

// 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. Rendering dei dati di profondità (parte 2)

Ora che hai una nuvola di punti di profondità con cui sperimentare, è il momento di vedere come appaiono i dati sullo schermo.

Aggiungi un renderer per visualizzare i punti di profondità

Aggiungi un renderer per visualizzare i punti di profondità.

Per prima cosa, aggiungi una nuova classe che contenga la logica di rendering. Questa classe esegue le operazioni OpenGL per inizializzare gli screenr in modo da visualizzare la nuvola di punti di profondità.

Aggiungi classe DepthRenderer

  1. Fai clic con il tasto destro del mouse sulla directory di origine rendering
  2. Seleziona New > Java Class.
  3. Assegna al corso il nome DepthRenderer.

Compila questa classe con il seguente codice:

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

Visualizza i dati di profondità

Dopodiché, fornisci il codice sorgente per gli Shaper del rendering. Aggiungi questo update()metodo in fondo alla classe DepthRenderer. Questo metodo prende come input le ultime informazioni di profondità e copia i dati della nuvola di punti nella 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");
    }

Recupera i dati più recenti sullo schermo aggiungendo un metodo draw() in fondo alla classe DepthRenderer. Questo metodo prende le informazioni della nuvola di punti 3D e le proietta nella visualizzazione della videocamera in modo che possano essere visualizzate sullo schermo.

    /** 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");
    }

Puoi impostare le dimensioni dei punti con dimensioni diverse,in pixel, utilizzando la variabile pointSizeUniform. pointSizeUniform è impostato su 5 pixel nell'app di esempio.

Aggiungi nuovi Shar

Esistono molti modi per visualizzare i dati sulla profondità nella tua app. Qui aggiungerai alcuni sfumature e creerai una semplice visualizzazione della mappatura dei colori.

Aggiungi nuovi Shar .vert e .frag alla directory src/main/assets/shaders/.

Aggiunta di un nuovo streamr .vert

In Android Studio:

  1. Fai clic con il tasto destro del mouse sulla directory Shars
  2. Seleziona Nuovo -> File
  3. Assegna il nome depth_point_cloud.vert
  4. Impostala come file di testo.

Nel nuovo file .vert, aggiungi il codice seguente:

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

Questo Shar utilizza la mappa colori Turbo per una visualizzazione migliore. Il processo prevede questi passaggi:

  1. Recupera l'elevazione di ciascun punto (asse y nelle coordinate del mondo).
  2. Calcola un colore associato a tale elevazione (rosso=basso, blu=alto).
  3. Calcola la posizione di ogni punto sullo schermo.
  4. Consente di impostare le dimensioni (in pixel) di ogni punto, come definito nel metodo DepthRenderer.update().

Crea uno shaker per frammenti nella stessa directory e assegnagli il nome depth_point_cloud.frag, ripetendo gli stessi passaggi in questa sezione.

Quindi, aggiungi il codice seguente a questo nuovo file per eseguire il rendering di ogni punto come un singolo vertice di colore uniforme, come definito nello Shader vertex.

src/main/assets/shaders/depth_point_cloud.frag

precision mediump float;
varying vec4 v_Color;

void main() {
    gl_FragColor = v_Color;
}

Per applicare questo rendering, aggiungi chiamate alla classe DepthRenderer all'interno di RawDepthCodelabActivity.

src/main/java/com/google/ar/core/codelab/common/rendering/RawDepthCodelabActivity.java

import com.google.ar.core.codelab.common.rendering.DepthRenderer;

Nella parte superiore del corso, aggiungi un membro privato accanto a backgroundRenderer.

private final DepthRenderer depthRenderer = new DepthRenderer();

depthRenderer deve essere inizializzato all'interno di RawDepthCodelabActivity.onSurfaceCreated(), proprio come backgroundRenderer esistente.

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

Aggiungi il seguente codice alla fine del blocco trips-catch all'interno di onDrawFrame per mostrare la profondità più recente per il frame corrente.

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

Con queste modifiche, l'app dovrebbe essere creata correttamente e mostrare la nuvola di punti di profondità.

Esempio di visualizzazione nuvola di punti di profondità non elaborata

  • Ogni punto campione viene colorato in base alla profondità.
  • I punti rossi sono vicini, i punti verdi/blu sono più lontani
  • Alcuni dati mancanti o "buchi" possono essere visualizzati in aree con caratteristiche di immagine insufficienti, come pareti o soffitti bianchi.
  • Puoi giocare con la dimensione del punto visualizzata regolando la linea GLES20.glUniform1f(pointSizeUniform, 5.0f); all'interno di DepthRenderer.draw(). A sinistra sono mostrate le dimensioni in punti 5 e 10.

7. Analizzare le nuvole di punti 3D (parte 3)

Puoi analizzare i dati di profondità dopo aver verificato che esistono in una sessione AR. Uno strumento importante per analizzare la profondità è il valore di confidenza di ogni pixel. Utilizza i valori di confidenza per analizzare le nuvole di punti 3D.

Disattiva i pixel a bassa affidabilità

Hai recuperato il valore di confidenza per ogni pixel di profondità e lo hai salvato insieme a ogni punto all'interno di DepthData, ma non l'hai ancora utilizzato.

I valori di confidenceNormalized sono compresi tra 0 e 1, dove 0 indica una confidenza bassa e 1 indica una confidenza completa. Modifica il metodo convertRawDepthImagesTo3dPointBuffer() nella classe DepthData per evitare di salvare i pixel la cui confidenza è troppo bassa per essere utile.

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

Prova diverse soglie per il livello di confidenza per vedere quanti punti di profondità vengono mantenuti per ogni livello.

Confidenza >= 0,1

Fiducia >= 0,3

Confidenza >= 0,5

Fiducia >= 0,7

Fiducia >= 0,9

Filtra pixel per distanza

Puoi anche filtrare i pixel di profondità in base alla distanza. I passaggi successivi riguardano la geometria in prossimità della fotocamera. Per l'ottimizzazione del rendimento, puoi ignorare i punti troppo lontani.

Aggiorna il codice per il controllo della confidenza che hai appena aggiunto con il codice seguente:

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

Ora vedrai solo i punti di chiusura e con confidenza elevata.

Filtro delle distanze

Limita la nuvola di punti a una distanza massima di 1,5 metri dalla fotocamera.

Confrontare punti e aerei in 3D

Puoi confrontare i punti e i piani 3D della geometria e utilizzarli per filtrarli a vicenda, ad esempio rimuovendo i punti vicini ai piani AR osservati.

Questo passaggio lascerà solo "non planare" punti che tendono a rappresentare superfici su oggetti nell'ambiente. Aggiungi il metodo filterUsingPlanes() in fondo alla classe DepthData. Questo metodo esegue un'iterazione dei punti esistenti, controlla ogni punto rispetto a ogni piano e invalida qualsiasi punto troppo vicino a un piano AR, lasciando aree non planari che evidenziano gli oggetti nella scena.

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

Puoi aggiungere questo metodo a RawDepthCodelabActivity nel metodo 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);

Ora l'esecuzione del codelab genera un sottoinsieme di punti. Questi punti rappresentano gli oggetti nella scena, ignorando le superfici piatte su cui si appoggiano gli oggetti. Puoi utilizzare questi dati per stimare le dimensioni e la posizione degli oggetti raggruppando i punti.

Tazza di tè

Microfono

Cuffie

Cuscino

Punti cluster

Questo codelab contiene un algoritmo di clustering cloud di punti molto semplicistico. Aggiorna il codelab per raggruppare le nuvole di punti recuperate in cluster definiti da riquadri di delimitazione allineati all'asse.

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;

Aggiungi un BoxRenderer a questa classe all'inizio del file, con gli altri renderer.

private final BoxRenderer boxRenderer = new BoxRenderer();

All'interno del metodo onSurfaceCreated(), aggiungi quanto segue insieme agli altri renderer:

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

Infine, aggiungi le seguenti righe a onDrawFrame() all'interno di RawDepthCodelabActivity per raggruppare le nuvole di punti recuperate in cluster ed eseguire il rendering dei risultati come riquadri di delimitazione allineati all'asse.

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

Tazza di tè

Microfono

Cuffie

Cuscino

Ora puoi recuperare la profondità non elaborata tramite una sessione ARCore, convertire le informazioni di profondità in nuvole di punti 3D ed eseguire operazioni di base di filtro e rendering su questi punti.

8. Build-Run-Test

Crea, esegui e testa la tua app.

Crea ed esegui la tua app

Per creare ed eseguire un'app, segui questi passaggi:

  1. Collega un dispositivo supportato da ARCore tramite USB.
  2. Esegui il progetto con il pulsante ◄ nella barra dei menu.
  3. Attendi la creazione e il deployment dell'app sul dispositivo.

La prima volta che tenti di eseguire il deployment dell'app sul tuo dispositivo dovrai

Consenti il debug USB

sul dispositivo. Seleziona OK per continuare.

La prima volta che esegui l'app sul dispositivo, ti verrà chiesto se è autorizzata a utilizzare la fotocamera del dispositivo. Devi consentire l'accesso per continuare a utilizzare la funzionalità AR.

Test dell'app

Quando esegui la tua app, puoi testarne il comportamento di base tenendo il dispositivo, muovendoti nello spazio e scansionando lentamente un'area. Prova a raccogliere almeno 10 secondi di dati e scansiona l'area da diverse direzioni prima di andare al passaggio successivo.

9. Complimenti

Congratulazioni. Hai creato ed eseguito correttamente la tua prima app di realtà aumentata basata sulla profondità utilizzando l'API ARCore Raw Depth di Google. Non vediamo l'ora di vedere cosa realizzerai.

10. Risoluzione dei problemi

Configurazione del dispositivo Android per lo sviluppo

  1. Collega il dispositivo alla macchina di sviluppo con un cavo USB. Se sviluppi utilizzando Windows, potrebbe essere necessario installare il driver USB appropriato per il tuo dispositivo.
  2. Per attivare il Debug USB nella finestra Opzioni sviluppatore, procedi nel seguente modo:
  • Apri l'app Impostazioni.
  • Se sul tuo dispositivo è installato Android 8.0 o versioni successive, seleziona Sistema.
  • Scorri fino in fondo e seleziona Informazioni sullo smartphone.
  • Scorri fino in fondo e tocca Numero build sette volte.
  • Torna alla schermata precedente, scorri fino in fondo e tocca Opzioni sviluppatore.
  • Nella finestra Opzioni sviluppatore, scorri verso il basso per trovare e attivare Debug USB.

Puoi trovare informazioni più dettagliate su questa procedura sul sito web per sviluppatori Android di Google.

Se si verifica un errore di compilazione relativo alle licenze (Failed to install the following Android SDK packages as some licences have not been accepted), puoi usare i seguenti comandi per esaminare e accettare queste licenze:

cd <path to Android SDK>

tools/bin/sdkmanager --licenses

Domande frequenti