Profondeur brute ARCore

1. Introduction

ARCore est une plate-forme qui permet de créer des applications de réalité augmentée (RA) sur des appareils mobiles. L'API ARCore Depth de Google permet d'accéder à une profondeur pour chaque image d'une session ARCore. Chaque pixel de la représentation de profondeur fournit une mesure de distance entre l'appareil photo et l'environnement.

L'API Raw Depth fournit des images de profondeur qui ne sont pas transmises par les opérations de filtrage de l'espace d'écran conçues pour lisser et interpoler les résultats. Ces valeurs sont plus précises d'un point de vue géométrique, mais peuvent contenir des données manquantes et être moins alignées sur l'image de l'appareil photo associée.

Cet atelier de programmation explique comment utiliser l'API Raw Depth pour effectuer une analyse géométrique 3D de la scène. Vous allez créer une application simple de RA qui utilise des données de profondeur brutes pour détecter et visualiser la géométrie du monde.

Les API Depth et Raw Depth ne sont compatibles qu'avec un sous-ensemble d'appareils compatibles ARCore. L'API Depth n'est disponible que sur Android.

Ce que vous allez faire

Dans cet atelier de programmation, vous allez créer une application qui utilise des représentations de profondeur brutes pour chaque image afin d'effectuer une analyse géométrique du monde qui vous entoure. Cette application va:

  1. Vérifiez si l'appareil cible est compatible avec la fonctionnalité Profondeur.
  2. Récupérez la profondeur de champ brute pour chaque image de l'appareil photo.
  3. Reprojetez des représentations de profondeur brutes en points 3D, puis filtrez ces points en fonction du niveau de confiance et de la géométrie.
  4. Utilisez le nuage de points de profondeur brute pour segmenter les objets 3D d'intérêt.

Aperçu de ce que vous allez créer.

Remarque : Si vous rencontrez des problèmes, consultez la dernière section pour obtenir des conseils de dépannage.

2. Prérequis

Vous aurez besoin de matériel et de logiciels spécifiques pour cet atelier de programmation.

Matériel requis

  • Un appareil compatible ARCore sur lequel le débogage USB est activé et connecté à votre ordinateur de développement à l'aide d'un câble USB Cet appareil doit aussi être compatible avec l'API Depth.

Logiciels requis

3. Configurer

Configurer l'ordinateur de développement

Connectez votre appareil ARCore à votre ordinateur avec le câble USB. Assurez-vous que votre appareil autorise le débogage USB. Ouvrez un terminal et exécutez adb devices, comme illustré ci-dessous :

adb devices

List of devices attached
<DEVICE_SERIAL_NUMBER>    device

Le numéro de série <DEVICE_SERIAL_NUMBER> sera une chaîne propre à votre appareil. Assurez-vous qu'un seul appareil s'affiche avant de passer à la suite.

Télécharger et installer le code

Vous pouvez cloner le dépôt :

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

Il est également possible de télécharger un fichier ZIP et de l'extraire :

Télécharger le fichier ZIP

Pour commencer à utiliser le code, procédez comme suit :

  1. Lancez Android Studio, puis sélectionnez Open an existing Android Studio project (Ouvrir un projet Android Studio existant).
  2. Accédez au répertoire local dans lequel vous avez stocké le fichier ZIP Raw Depth.
  3. Double-cliquez sur le répertoire arcore_rawdepthapi_codelab.

Le répertoire arcore_rawdepthapi_codelab est un projet Gradle unique comportant plusieurs modules. Si le volet "Project" (Projet) en haut à gauche d'Android Studio ne s'affiche pas déjà dans le volet "Project" (Projet), cliquez sur Projects (Projets) dans le menu déroulant.

Vous devriez obtenir le résultat suivant :

Ce projet contient les modules suivants :

  • part0_work : application de départ. Vous apporterez des modifications à ce module lors de cet atelier de programmation. Toutes les autres parties contiennent un code de référence.
  • part1 : code de référence pour vérifier vos modifications en fin de 1ʳᵉ partie.
  • part2 : code de référence à consulter en fin de 2ᵉ partie.
  • part3_completed: code de référence à utiliser pour terminer la partie 3, qui est la fin de l'atelier de programmation.

Vous allez travailler dans le module part0_work. Il existe également des solutions complètes pour chaque partie de l'atelier de programmation. Chaque module correspond à une application pouvant être créée.

4. Exécuter l'application de démarrage

Suivez ces étapes pour exécuter l'application de démarrage Raw Depth.

  1. Accédez à Exécuter > Exécuter... > 'part0_work'.
  2. Dans la boîte de dialogue Select Deployment Target (Sélectionner une cible de déploiement), sélectionnez votre appareil dans la liste Connected Devices (Appareils connectés), puis cliquez sur OK.

Android Studio génère l'application de départ, puis l'exécute sur votre appareil.

Lorsque vous exécutez l'application pour la première fois, vous devez l'autoriser à utiliser l'appareil photo. Appuyez sur Allow (Autoriser) pour continuer.

Pour le moment, l'application n'effectue aucune action. Il s'agit de l'application de RA la plus basique, qui affiche une vue de la scène filmée, mais ne fait rien d'autre.Le code existant est semblable à l'exemple Hello AR publié avec le SDK ARCore.

Vous allez maintenant utiliser l'API Raw Depth pour récupérer la géométrie de la scène qui vous entoure.

5. Configurer l'API Raw Depth (partie 1)

Assurez-vous que l'appareil cible est compatible avec la mesure de profondeur.

Tous les appareils compatibles ARCore ne peuvent pas exécuter l'API Depth. Assurez-vous que l'appareil cible est compatible avec la profondeur avant d'ajouter une fonctionnalité à votre application dans la fonction onResume() de RawDepthCodelabActivity.java, où une session est créée.

Recherchez le code existant :

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

Mettez-le à jour pour vous assurer que l'application ne s'exécute que sur les appareils compatibles avec 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.";
}

Activer la profondeur brute

L'API Raw Depth fournit une image de profondeur non lissée et une image de confiance correspondante contenant l'indice de confiance de chaque pixel de l'image de profondeur brute. Activez la profondeur brute en mettant à jour le code suivant sous l'instruction try-catch que vous venez de modifier.

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

La session de RA est maintenant configurée correctement et l'application peut utiliser des fonctionnalités basées sur la profondeur.

Appeler l'API Depth

Appelez ensuite l'API Depth pour récupérer les représentations de profondeur pour chaque image. Encapsulez les données de profondeur dans une nouvelle classe en créant un fichier. Effectuez un clic droit sur le dossier rawdepth, puis sélectionnez New > Java Class. Cela crée un fichier vierge. Ajoutez les éléments suivants à cette classe:

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.

}

Cette classe permet de convertir des représentations de profondeur en nuages de points. Les nuages de points représentent la géométrie de la scène avec une liste de points ayant chacun des coordonnées 3D (x, y, z) et un indice de confiance compris entre 0 et 1.

Ajoutez des appels pour renseigner ces valeurs à l'aide de l'API Raw Depth en ajoutant une méthode create() en bas de la classe. Cette méthode interroge les dernières images de profondeur et de confiance, et stocke le cloud de points ainsi obtenu. Les images de profondeur et de confiance auront des données correspondantes.

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

À ce stade, le code stocke également l'ancre de la caméra, afin que les informations de profondeur puissent être transformées en coordonnées mondiales en appelant une méthode d'assistance convertRawDepthImagesTo3dPointBuffer(). Cette méthode d'assistance prend chaque pixel de la représentation de profondeur et utilise les fonctionnalités intrinsèques de l'appareil photo pour annuler la projection de la profondeur dans un point 3D par rapport à l'appareil photo. Ensuite, l'ancre de la caméra est utilisée pour convertir la position du point en coordonnées mondiales. Chaque pixel existant est converti en point 3D (en mètres) et stocké avec le niveau de confiance.

Ajoutez la méthode d'assistance suivante au fichier 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;
    }

Obtenez les données brutes les plus récentes sur la profondeur pour chaque image

Modifiez l'application pour récupérer les informations de profondeur et les aligner sur les coordonnées mondiales de chaque posture.

Dans RawDepthCodelabActivity.java, recherchez les lignes existantes dans la méthode onDrawFrame():

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

Ajoutez les lignes suivantes juste en dessous:

// 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. Effectuer le rendu des données de profondeur (partie 2)

Maintenant que vous disposez d'un nuage de points de profondeur, il est temps de voir à quoi ressemblent les données affichées à l'écran.

Ajouter un moteur de rendu pour visualiser les points de profondeur

Ajoutez un moteur de rendu pour visualiser les points de profondeur.

Tout d'abord, ajoutez une classe contenant la logique de rendu. Cette classe effectue les opérations OpenGL pour initialiser les nuanceurs afin de visualiser le nuage de points de profondeur.

Ajouter la classe DepthRenderer

  1. Effectuez un clic droit sur le répertoire source rendering.
  2. Sélectionnez New > Java Class.
  3. Nommez la classe DepthRenderer.

Renseignez cette classe avec le code suivant:

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

Afficher les données de profondeur

Indiquez ensuite la source des nuanceurs de rendu. Ajoutez la méthode update() suivante en bas de la classe DepthRenderer. Cette méthode prend en entrée les dernières informations de profondeur et copie les données du cloud de points sur le 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");
    }

Pour afficher les dernières données à l'écran, ajoutez une méthode draw() en bas de la classe DepthRenderer. Cette méthode récupère les informations du nuage de points 3D et les projette dans la vue de la caméra pour les afficher à l'écran.

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

Vous pouvez définir la taille du point sur différentes tailles,en pixels, à l'aide de la variable pointSizeUniform. pointSizeUniform est défini sur 5 pixels dans l'application exemple.

Ajouter des nuanceurs

Il existe de nombreuses façons de consulter et d'afficher les données de profondeur dans votre application. Ici, vous allez ajouter quelques nuanceurs et créer une visualisation simple avec mappage de couleurs.

Ajoutez les nuanceurs .vert et .frag dans le répertoire src/main/assets/shaders/.

Ajouter un nouveau nuanceur ".vert"

Dans Android Studio :

  1. Effectuez un clic droit sur le répertoire des nuanceurs.
  2. Sélectionnez New > File (Nouveau > Fichier)
  3. Appelez-le depth_point_cloud.vert
  4. Définissez-le en tant que fichier texte.

Dans le nouveau fichier .vert, ajoutez le code suivant:

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

Ce nuanceur utilise la carte de couleurs Turbo pour une visualisation optimisée. Il effectue les étapes suivantes:

  1. Récupère l'élévation de chaque point (axe des ordonnées dans les coordonnées mondiales).
  2. Calcule une couleur associée à cette altitude (rouge=faible, bleu=élevé).
  3. Calcule la position à l'écran de chaque point.
  4. Définit la taille (en pixels) de chaque point, telle que définie dans la méthode DepthRenderer.update().

Créez un nuanceur de fragments dans le même répertoire et nommez-le depth_point_cloud.frag, en répétant les étapes de cette section.

Ajoutez ensuite le code suivant à ce nouveau fichier pour afficher chaque point sous la forme d'un sommet unique de couleur uniforme, tel que défini dans le nuanceur de sommets.

src/main/assets/shaders/depth_point_cloud.frag

precision mediump float;
varying vec4 v_Color;

void main() {
    gl_FragColor = v_Color;
}

Pour appliquer ce rendu, ajoutez des appels à la classe DepthRenderer dans votre RawDepthCodelabActivity.

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

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

En haut du cours, ajoutez un membre privé à côté de backgroundRenderer.

private final DepthRenderer depthRenderer = new DepthRenderer();

depthRenderer doit être initialisé dans RawDepthCodelabActivity.onSurfaceCreated(), tout comme le backgroundRenderer existant.

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

Ajoutez le code suivant à la fin du bloc try-catch dans onDrawFrame pour afficher la dernière profondeur de l'image actuelle.

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

Avec ces modifications, l'application devrait maintenant se compiler correctement et afficher la profondeur dans le cloud de points.

Exemple de visualisation d'un nuage de points avec la profondeur brute

  • Chaque échantillon de point est coloré en fonction de sa profondeur.
  • Les points rouges sont proches, les points verts/bleus sont plus éloignés
  • Certaines données manquantes ou « trous » est visible dans les zones où les caractéristiques d'image sont insuffisantes, par exemple dans les murs ou les plafonds blancs.
  • Vous pouvez tester la taille du point affiché en ajustant la ligne GLES20.glUniform1f(pointSizeUniform, 5.0f); dans DepthRenderer.draw(). Les tailles de point 5 et 10 sont visibles à gauche.

7. Analyser des nuages de points en 3D (partie 3)

Vous pouvez analyser les données de profondeur après avoir vérifié qu'elles existent dans une session de RA. La valeur confidence de chaque pixel est un outil important pour analyser la profondeur. Utilisez des valeurs de confiance pour analyser des nuages de points 3D.

Invalider les pixels ayant un faible niveau de confiance

Vous avez récupéré la valeur de confiance pour chaque pixel de profondeur et l'avez enregistrée avec chaque point dans DepthData, mais vous ne l'avez pas encore utilisée.

Les valeurs de confidenceNormalized sont comprises entre 0 et 1. 0 indique un indice de confiance faible et 1 indique un indice de confiance total. Modifiez la méthode convertRawDepthImagesTo3dPointBuffer() dans la classe DepthData pour éviter d'enregistrer des pixels dont l'indice de confiance est trop faible pour être 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 *********

Essayez différents seuils pour le niveau de confiance afin de voir combien de points de profondeur sont conservés à chaque niveau.

Confiance >= 0,1

Confiance >= 0,3

Confiance >= 0,5

Confiance >= 0,7

Confiance >= 0,9

Filtrer les pixels par distance

Vous pouvez également filtrer la profondeur en pixels par distance. Les étapes suivantes concernent les géométries proches de la caméra. Pour optimiser les performances, vous pouvez ignorer les points qui sont trop éloignés.

Modifiez le code de vérification de la confiance que vous venez d'ajouter comme suit:

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

Désormais, vous ne verrez que les points de confiance élevés et les points de conclusion.

Filtrage des distances

Limite le nuage de points à 1,5 mètre de la caméra.

Comparer des points et des plans 3D

Vous pouvez comparer les points et les plans 3D de la géométrie et les utiliser pour les filtrer (par exemple, en supprimant des points proches des plans en RA observés).

Cette étape ne conservera que la valeur "non planaire" points qui ont tendance à représenter les surfaces des objets dans l’environnement. Ajoutez la méthode filterUsingPlanes() en bas de la classe DepthData. Cette méthode parcourt les points existants, vérifie chaque point par rapport à chaque plan et invalide tout point trop proche d'un plan de RA, laissant les zones non planes qui mettent en évidence les objets dans la scène.

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

Vous pouvez ajouter cette méthode à RawDepthCodelabActivity dans la méthode 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'exécution de l'atelier de programmation affiche maintenant un sous-ensemble de points. Ces points représentent les objets de la scène, ignorant les surfaces planes sur lesquelles ils reposent. Vous pouvez utiliser ces données pour estimer la taille et la position des objets en combinant des points de clustering.

Tasse de thé

Micro

Écouteurs

Pillow

Points de cluster

Cet atelier de programmation contient un algorithme de clustering Pointcloud très simpliste. Mettez à jour l'atelier de programmation pour regrouper les nuages de points récupérés dans des clusters définis par des cadres de délimitation alignés sur les axes.

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;

Ajoutez un BoxRenderer à cette classe en haut du fichier, avec les autres moteurs de rendu.

private final BoxRenderer boxRenderer = new BoxRenderer();

Dans la méthode onSurfaceCreated(), ajoutez le code suivant avec les autres moteurs de rendu:

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

Enfin, ajoutez les lignes suivantes à onDrawFrame() dans RawDepthCodelabActivity pour regrouper les nuages de points récupérés en clusters et afficher les résultats sous forme de cadres de délimitation alignés sur les axes.

      // 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 de thé

Micro

Écouteurs

Pillow

Vous pouvez désormais récupérer la profondeur brute via une session ARCore, convertir les informations de profondeur en nuages de points 3D, et effectuer des opérations de filtrage et d'affichage de base sur ces points.

8. Test de génération et d'exécution

Créez, exécutez et testez votre application.

Compiler et exécuter votre application

Procédez comme suit pour générer et exécuter votre application :

  1. Branchez un appareil compatible ARCore à l'aide d'un câble USB.
  2. Exécutez votre projet à l'aide du bouton ► dans la barre de menu.
  3. Attendez que l'application soit générée et se déploie sur votre appareil.

La première fois que vous tentez de déployer l'application sur votre appareil, vous devez :

Autoriser le débogage USB

sur l'appareil. Sélectionnez "OK" pour continuer.

Lorsque vous exécutez votre application pour la première fois sur votre appareil, il vous est demandé si elle est autorisée à utiliser l'appareil photo de celui-ci. Vous devez accorder cette autorisation pour continuer à utiliser la fonctionnalité de RA.

Tester votre application

Lorsque vous exécutez votre application, vous pouvez tester son comportement de base en manipulant votre appareil, en vous déplaçant dans l'espace où vous vous trouvez et en scannant lentement une zone. Essayez de collecter au moins 10 secondes de données et de scanner cette zone selon plusieurs axes avant de passer à l'étape suivante.

9. Félicitations

Félicitations, vous venez de créer et d'exécuter votre première application de réalité augmentée basée sur la profondeur à l'aide de l'API ARCore Raw Depth de Google. Nous avons hâte de voir ce que vous allez créer !

10. Dépannage

Configurer votre appareil Android pour le développement

  1. Connectez votre appareil à l'ordinateur de développement à l'aide d'un câble USB. Si vous développez avec Windows, vous devrez peut-être installer le pilote USB correspondant à votre appareil.
  2. Pour activer le débogage USB dans la fenêtre Options pour les développeurs, procédez comme suit :
  • Ouvrez l'application Paramètres.
  • Si votre appareil est équipé d'Android 8.0 ou version ultérieure, sélectionnez Système.
  • Faites défiler l'écran jusqu'en bas, puis sélectionnez À propos du téléphone.
  • Faites défiler l'écran jusqu'en bas, puis appuyez sept fois sur le numéro de version.
  • Revenez à l'écran précédent, faites défiler la page jusqu'en bas, puis appuyez sur Options pour les développeurs.
  • Dans la fenêtre Options pour les développeurs, faites défiler l'écran vers le bas pour trouver et activer l'option Débogage USB.

Pour en savoir plus sur cette procédure, consultez le site Web des développeurs Android de Google.

Si vous rencontrez un échec de compilation lié aux licences (Failed to install the following Android SDK packages as some licences have not been accepted), vous pouvez utiliser les commandes suivantes pour examiner et accepter ces licences:

cd <path to Android SDK>

tools/bin/sdkmanager --licenses

Questions fréquentes