Profondità non elaborata di ARCore

1. Introduzione

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

L'API Raw Depth fornisce immagini di profondità che non vengono sottoposte a operazioni di filtraggio dello spazio schermo progettate per uniformare e interpolare i risultati. Questi valori sono più precisi dal punto di vista geometrico, ma potrebbero contenere dati mancanti ed essere meno allineati all'immagine della videocamera associata.

Questo codelab mostra come utilizzare l'API Raw Depth per eseguire l'analisi della geometria 3D della scena. Creerai una semplice app con funzionalità AR che utilizza i 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 compatibili con 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 per eseguire l'analisi geometrica del mondo che ti circonda. Questa app:

  1. Controlla se il dispositivo di destinazione supporta la profondità.
  2. Recupera l'immagine di profondità grezza per ogni frame della videocamera.
  3. Riprojettare le immagini di profondità non elaborate in punti 3D e filtrare questi punti in base alla confidenza e alla geometria.
  4. Utilizza la nuvola di punti di profondità grezza per segmentare gli oggetti 3D di interesse.

Anteprima di ciò che creerai.

Nota: se riscontri problemi durante la procedura, vai all'ultima sezione per alcuni suggerimenti per la 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, collegato tramite un cavo USB alla macchina di sviluppo. Inoltre, il dispositivo deve 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

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

Scaricare e installare il codice

Puoi clonare il repository:

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

In alternativa, scarica un file ZIP ed estrailo:

Per iniziare a lavorare con 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 Raw Depth.
  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 in alto a sinistra di Android Studio non è già visualizzato, fai clic su Progetti dal menu a discesa.

Il risultato dovrebbe essere simile a questo:

Questo progetto contiene i seguenti moduli:

  • part0_work: l'app iniziale. Devi apportare modifiche a questo modulo quando svolgi questo codelab. Tutte le altre parti contengono il codice di riferimento.
  • part1: codice di riferimento di come dovrebbero apparire le modifiche al termine della parte 1.
  • part2: il codice di riferimento quando completi la parte 2.
  • part3_completed: il codice di riferimento quando completi la parte 3, ovvero la fine del codelab.

Lavorerai nel modulo part0_work. Sono disponibili anche soluzioni complete per ogni parte del codelab. Ogni modulo è un'app compilabile.

4. Esegui l'app di base

Segui questi passaggi per eseguire l'app iniziale Raw Depth.

  1. Vai a Esegui > Esegui… > 'part0_work'.
  2. Nella finestra di dialogo Seleziona target di distribuzione, 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ù semplice, che mostra una visualizzazione della scena dalla videocamera, ma non fa altro.Il codice esistente è simile all'esempio Hello AR pubblicato con l'SDK ARCore.

Successivamente, utilizzerai l'API Raw Depth per recuperare la geometria della scena che ti circonda.

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à alla tua app all'interno della funzione onResume() di RawDepthCodelabActivity.java, in cui viene creata una nuova sessione.

Trova il codice esistente:

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

Aggiornalo per assicurarti che l'applicazione venga eseguita solo su dispositivi che supportano 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 Raw Depth

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 dell'immagine di profondità non uniforme. Attiva la profondità grezza aggiornando il seguente codice nell'istruzione try-catch appena modificata.

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ò utilizzare le funzionalità basate sulla profondità.

Chiama l'API Depth

Successivamente, chiama l'API Depth per recuperare le immagini di profondità per ogni frame. Incapsula 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, ognuno dei quali ha 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() nella parte inferiore della classe. Questo metodo esegue query sulle immagini di profondità e confidenza più recenti, memorizzando 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()

In questo momento, il codice memorizza anche l'ancoraggio della videocamera, in modo che le informazioni sulla profondità possano essere trasformate in coordinate mondiali chiamando un metodo helper convertRawDepthImagesTo3dPointBuffer(). Questo metodo helper prende ogni pixel nell'immagine di profondità e utilizza i parametri intrinseci della fotocamera per eseguire la proiezione inversa della profondità in un punto 3D relativo alla fotocamera. L'ancora della videocamera viene quindi utilizzata per convertire la posizione del punto in coordinate mondiali. Ogni pixel esistente viene convertito in un punto 3D (in metri) e memorizzato 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;
    }

Ottenere i dati Raw Depth più recenti per ogni frame

Modifica l'app per recuperare le informazioni sulla profondità e allinearle alle coordinate mondiali 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. Esegui il rendering dei dati sulla profondità (parte 2)

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

Aggiungere un renderer per visualizzare i punti di profondità

Aggiungi un renderer per visualizzare i punti di profondità.

Innanzitutto, aggiungi una nuova classe per contenere la logica di rendering. Questa classe esegue le operazioni OpenGL per inizializzare gli shader per visualizzare la nuvola di punti di profondità.

Aggiungi la classe DepthRenderer

  1. Fai clic con il tasto destro del mouse sulla directory di origine rendering.
  2. Seleziona New > Java Class.
  3. Assegna un nome alla classe 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");
    }
}

Visualizzare i dati di profondità

A questo punto, fornisci l'origine degli shader di rendering. Aggiungi il seguente metodo update() in fondo alla classe DepthRenderer. Questo metodo prende come input le informazioni sulla profondità più recenti 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");
    }

Visualizza 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 nuovamente nella visuale 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 la dimensione del punto su dimensioni diverse,in pixel, utilizzando la variabile pointSizeUniform. pointSizeUniform è impostato su 5 pixel nell'app di esempio.

Aggiungi nuovi shader

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

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

Aggiunta di un nuovo shader .vert

In Android Studio:

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

Nel nuovo file .vert, aggiungi il seguente codice:

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 shader utilizza la mappa dei colori Turbo per una migliore visualizzazione. Esegue i seguenti passaggi:

  1. Recupera l'elevazione di ogni punto (asse Y nelle coordinate mondiali).
  2. Calcola un colore associato a quell'altitudine (rosso=basso, blu=alto).
  3. Calcola la posizione sullo schermo di ogni punto.
  4. Imposta le dimensioni (in pixel) di ogni punto, come definito nel metodo DepthRenderer.update().

Crea uno shader di framenti nella stessa directory e chiamalo depth_point_cloud.frag, ripetendo gli stessi passaggi di questa sezione.

Poi aggiungi il seguente codice a questo nuovo file per eseguire il rendering di ogni punto come un singolo vertice di colore uniforme, come definito nello shader dei vertici.

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 try-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, ora la build dell'app dovrebbe essere eseguita correttamente e la nuvola di punti di profondità dovrebbe essere visualizzata.

Esempio di visualizzazione della nuvola di punti di profondità non elaborata

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

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

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

Invalidare i pixel con bassa confidenza

Hai recuperato il valore di affidabilità per ogni pixel di profondità e l'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 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 a ogni livello.

Affidabilità >= 0,1

Affidabilità >= 0,3

Confidenza >= 0,5

Affidabilità >= 0,7

Affidabilità >= 0,9

Filtrare i pixel per distanza

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

Aggiorna il codice di controllo dell'affidabilità che hai appena aggiunto con quanto segue:

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 con confidenza elevata e quelli vicini.

Filtro per distanza

Limita la nuvola di punti a 1,5 metri dalla videocamera.

Confrontare punti e piani 3D

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

Questo passaggio lascerà solo i punti "non planari" che tendono a rappresentare le superfici degli oggetti nell'ambiente. Aggiungi il metodo filterUsingPlanes() in fondo alla classe DepthData. Questo metodo scorre i punti esistenti, controlla ogni punto rispetto a ogni piano e invalida qualsiasi punto troppo vicino a un piano AR, lasciando aree non planari che mettono in evidenza 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);

L'esecuzione del codelab ora comporta il rendering di un sottoinsieme di punti. Questi punti rappresentano gli oggetti nella scena, ignorando le superfici piane su cui poggiano 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 di nuvole di punti molto semplice. Aggiorna il codelab per raggruppare le nuvole di punti recuperate in cluster definiti da bounding box allineati agli assi.

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 nella parte superiore del file, insieme agli 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 e visualizzare i risultati come bounding box allineati agli assi.

      // 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à grezza tramite una sessione ARCore, convertire le informazioni sulla profondità in nuvole di punti 3D ed eseguire operazioni di rendering e filtraggio di base su questi punti.

8. Build-Run-Test

Crea, esegui e testa la tua app.

Crea ed esegui la tua app

Per creare ed eseguire l'app:

  1. Collega un dispositivo supportato da ARCore tramite USB.
  2. Esegui il progetto con il pulsante ► nella barra dei menu.
  3. Attendi che l'app venga creata e implementata sul dispositivo.

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

Consenti debug USB

sul dispositivo. Seleziona OK per continuare.

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

Test dell'app

Quando esegui l'app, puoi testarne il comportamento di base tenendo in mano il dispositivo, spostandoti nello spazio e scansionando lentamente un'area. Prova a raccogliere almeno 10 secondi di dati e a scansionare l'area da diverse direzioni prima di passare 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 scoprire cosa realizzerai.

10. Risoluzione dei problemi

Configurare il dispositivo Android per lo sviluppo

  1. Collega il dispositivo alla macchina di sviluppo con un cavo USB. Se sviluppi utilizzando Windows, potresti dover 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 il dispositivo utilizza 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 verso il basso 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 di Google per gli sviluppatori Android.

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

cd <path to Android SDK>

tools/bin/sdkmanager --licenses

Domande frequenti