Profundidad sin procesar de ARCore

1. Introducción

ARCore es una plataforma que permite crear apps de realidad aumentada (RA) en dispositivos móviles. La API de ARCore Depth de Google proporciona acceso a una imagen de profundidad de cada fotograma en una sesión de ARCore. Cada píxel de la imagen de profundidad proporciona una medición de la distancia entre la cámara y el entorno.

La API de Raw Depth brinda imágenes de profundidad que no se pasan por operaciones de filtrado en el espacio de la pantalla diseñadas para interpolar y suavizar los resultados. Estos valores son más exactos desde el punto de vista geométrico, pero pueden contener datos faltantes y estar menos alineados con la imagen de cámara asociada.

En este codelab, se muestra cómo usar la API de Raw Depth para realizar un análisis de geometría en 3D de la escena. Compilarás una app sencilla habilitada para RA que usa datos de profundidad sin procesar para detectar y visualizar la geometría del mundo.

Las APIs de Depth y Raw Depth solo son compatibles con un subconjunto de dispositivos habilitados para ARCore. La API de Depth solo está disponible en Android.

Qué compilarás

En este codelab, compilarás una app que usa imágenes de profundidad sin procesar para cada fotograma con el objetivo de realizar un análisis geométrico del mundo que te rodea. Esta app hará lo siguiente:

  1. Verifica si el dispositivo de destino admite profundidad.
  2. Recupera la imagen de profundidad sin procesar de cada fotograma de la cámara.
  3. Reproyecta imágenes de profundidad sin procesar en puntos 3D y filtra esos puntos en función de la confianza y la geometría.
  4. Usa la nube de puntos de profundidad sin procesar para segmentar objetos 3D de interés.

Obtén una vista previa de lo que crearás.

Nota: Si tienes algún problema durante el codelab, ve a la última sección para obtener algunas sugerencias.

2. Requisitos previos

Necesitarás hardware y software específicos para completar este codelab.

Requisitos de hardware

  • Un dispositivo compatible con ARCore con depuración por USB habilitada, conectado mediante un cable USB a tu máquina de desarrollo (este dispositivo también debe ser compatible con la API de Depth;

Requisitos de software

3. Configurar

Configura la máquina de desarrollo

Conecta tu dispositivo ARCore a la computadora mediante un cable USB. Asegúrate de habilitar la depuración por USB en el dispositivo. Abre una terminal y ejecuta adb devices, como se muestra a continuación:

adb devices

List of devices attached
<DEVICE_SERIAL_NUMBER>    device

El <DEVICE_SERIAL_NUMBER> será una string única para tu dispositivo. Antes de continuar, asegúrate de ver únicamente un dispositivo.

Descarga e instala el código

Puedes optar por clonar el repositorio de la siguiente manera:

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

O bien, puedes descargar este archivo ZIP y extraerlo:

Sigue estos pasos para comenzar a trabajar con el código.

  1. Inicia Android Studio y elige Open an existing Android Studio project.
  2. Navega al directorio local en el que almacenaste el archivo ZIP de profundidad sin procesar.
  3. Haz doble clic en el directorio arcore_rawdepthapi_codelab.

El directorio arcore_rawdepthapi_codelab es un solo proyecto de Gradle con varios módulos. Si el panel Project de la parte superior izquierda de Android Studio no se muestra en el panel Project, haz clic en Projects en el menú desplegable.

El resultado debería verse como el siguiente:

Este proyecto contiene los siguientes módulos:

  • part0_work: Es la app de inicio. Las modificaciones que se detallan en el codelab deben realizarse en este módulo. Todas las demás partes contienen un código de referencia.
  • part1: Es un código de referencia de cómo se deberían ver tus modificaciones cuando completes la Parte 1.
  • part2: Es un código de referencia que obtienes cuando completas la Parte 2.
  • part3_completed: Es un código de referencia que obtienes cuando completas la Parte 3, que es el final del codelab.

Trabajarás en el módulo part0_work. También te brindamos soluciones completas para cada parte del codelab. Cada módulo es una app que se puede compilar.

4. Ejecuta la app de partida

Sigue estos pasos para ejecutar la app de partida de profundidad sin procesar.

  1. Navega a Ejecutar > Ejecutar... > 'part0_work'.
  2. En el diálogo Select Deployment Target, selecciona tu dispositivo de la lista Connected Devices y haz clic en OK.

Android Studio compilará la app inicial y la ejecutará en tu dispositivo.

Cuando ejecutes la app por primera vez, se te solicitará permiso para acceder a la cámara. Presiona Allow para continuar.

Por el momento, la app no realiza ninguna acción.Esta es la aplicación de RA más básica, que muestra una vista de cámara de tu escena, pero no realiza ninguna otra acción.El código existente es similar al de la muestra de RA de Hello que se publicó con el SDK de ARCore.

A continuación, usarás la API de Raw Depth para recuperar la geometría de la escena que te rodea.

5. Configura la API de Raw Depth (parte 1)

Asegúrate de que el dispositivo de destino admita Depth

No todos los dispositivos compatibles con ARCore pueden ejecutar la API de Depth. Asegúrate de que el dispositivo de destino admita Depth antes de agregar funcionalidad a tu app dentro de la función onResume() de RawDepthCodelabActivity.java, donde se crea una nueva sesión.

Busca el siguiente código existente:

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

Actualízala para asegurarte de que la app solo se ejecute en dispositivos compatibles con la API de 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.";
}

Habilitar profundidad sin procesar

La API de Raw Depth proporciona una imagen de profundidad sin suavizar y una imagen de confianza correspondiente que contiene la confianza de profundidad de cada píxel de la imagen de profundidad sin procesar. Para habilitar la profundidad sin procesar, actualiza el siguiente código en la sentencia try-catch que acabas de modificar.

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

Ahora la sesión de RA se configuró correctamente y la app puede usar funciones basadas en la profundidad.

Llama a la API de Depth

Luego, llama a la API de Depth para recuperar imágenes de profundidad de cada fotograma. Crea un archivo nuevo para encapsular los datos de profundidad en una clase nueva. Haz clic con el botón derecho en la carpeta rawdepth y selecciona New > Java Class. Se creará un archivo en blanco. Agrega lo siguiente a esta clase:

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.

}

Esta clase se usa para convertir imágenes de profundidad en nubes de puntos. Las nubes de puntos representan la geometría de la escena con una lista de puntos que tienen una coordenada 3D (x, y, z) y un valor de confianza en el rango de 0 a 1.

Agrega llamadas para propagar estos valores con la API de Raw Depth y un método create() en la parte inferior de la clase. Este método consulta las últimas imágenes de profundidad y confianza, y almacena la nube de puntos resultante. Las imágenes de profundidad y confianza tendrán datos coincidentes.

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

En este momento, el código también almacena el ancla de la cámara, de modo que la información de profundidad se pueda transformar en coordenadas mundiales llamando a un método auxiliar convertRawDepthImagesTo3dPointBuffer(). Este método auxiliar toma cada píxel de la imagen de profundidad y usa las funciones intrínsecas de la cámara para anular la proyección de la profundidad en un punto 3D relativo a la cámara. Luego, se usa el ancla de la cámara para convertir la posición del punto en coordenadas mundiales. Cada píxel existente se convierte en un punto 3D (en unidades de metros) y se almacena junto con su valor de confianza.

Agrega el siguiente método de ayuda 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;
    }

Obtén los datos de profundidad sin procesar más recientes de cada fotograma

Modifica la app para recuperar información de profundidad y alinearla con las coordenadas mundiales de cada postura.

En RawDepthCodelabActivity.java, en el método onDrawFrame(), busca las líneas existentes:

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

Agrega las siguientes líneas justo debajo:

// 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. Renderiza los datos de profundidad (parte 2)

Ahora que tienes una nube de puntos de profundidad para experimentar, es hora de comprobar cómo se ven los datos renderizados en pantalla.

Cómo agregar un procesador para visualizar los puntos de profundidad

Agrega un renderizador para visualizar los puntos de profundidad.

Primero, agrega una nueva clase que contenga la lógica de renderización. Esta clase realiza las operaciones de OpenGL para inicializar sombreadores y visualizar la profundidad de nube de puntos.

Cómo agregar la clase DepthRenderer

  1. Haz clic con el botón derecho en el directorio del código fuente rendering
  2. Selecciona New > Java Class.
  3. Asígnale el nombre DepthRenderer a la clase.

Propaga esta clase con el siguiente código:

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

Renderiza los datos de profundidad

A continuación, proporciona la fuente para los sombreadores de renderización. Agrega el siguiente método update() al final de la clase DepthRenderer. Este método toma la información de profundidad más reciente como entrada y copia los datos de la nube de puntos en la 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");
    }

Dibuja los datos más recientes en la pantalla agregando un método draw() al final de la clase DepthRenderer. Este método toma la información de la nube de puntos en 3D y la proyecta de nuevo en la vista de la cámara para que se pueda renderizar en la pantalla.

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

Puedes establecer el tamaño de los puntos en diferentes tamaños,en píxeles, con la variable pointSizeUniform. pointSizeUniform se establece en 5 píxeles en la app de ejemplo.

Cómo agregar sombreadores nuevos

Existen muchas formas de ver y mostrar datos de profundidad en tu app. Aquí, agregarás algunos sombreadores y crearás una visualización simple del mapa de colores.

Agrega nuevos sombreadores .vert y .frag al directorio src/main/assets/shaders/.

Cómo agregar un sombreador .vert nuevo

En Android Studio:

  1. Haz clic con el botón derecho en el directorio de los sombreadores.
  2. Selecciona New -> File
  3. Asígnale el nombre depth_point_cloud.vert.
  4. Configúralo como un archivo de texto.

En el archivo .vert nuevo, agrega el siguiente código:

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

Este sombreador usa el mapa de colores Turbo para lograr una mejor visualización. Realiza los siguientes pasos:

  1. Recupera la elevación de cada punto (eje Y en coordenadas mundiales).
  2. Calcula un color asociado con esa elevación (rojo=bajo, azul=alta).
  3. Calcula la posición de la pantalla de cada punto.
  4. Establece el tamaño (en píxeles) de cada punto, como se define en el método DepthRenderer.update().

Crea un sombreador de fragmentos en el mismo directorio y asígnale el nombre depth_point_cloud.frag. Para ello, repite los mismos pasos de esta sección.

Luego, agrega el siguiente código a este archivo nuevo para renderizar cada punto como un solo vértice de color uniforme, como se define en el sombreador de vértices.

src/main/assets/shaders/depth_point_cloud.frag

precision mediump float;
varying vec4 v_Color;

void main() {
    gl_FragColor = v_Color;
}

Para aplicar esta renderización, agrega llamadas a la clase DepthRenderer dentro de tu RawDepthCodelabActivity.

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

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

En la parte superior de la clase, agrega un miembro privado junto a backgroundRenderer.

private final DepthRenderer depthRenderer = new DepthRenderer();

depthRenderer debe inicializarse dentro de RawDepthCodelabActivity.onSurfaceCreated(), al igual que el backgroundRenderer existente.

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

Agrega el siguiente código al final del bloque try-catch dentro de onDrawFrame para mostrar la profundidad más reciente del fotograma actual.

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

Con estos cambios, la app debería compilarse correctamente y mostrar la nube de puntos de profundidad.

Ejemplo de visualización de nube de puntos de profundidad sin procesar

  • Cada muestra puntual se colorea según su profundidad.
  • Los puntos rojos están cerca y los puntos verdes y azules están más lejos.
  • Faltan algunos datos o “agujeros” Las imágenes se pueden ver en áreas con imágenes insuficientes, como paredes o techos blancos.
  • Para jugar con el tamaño del punto renderizado, ajusta la línea GLES20.glUniform1f(pointSizeUniform, 5.0f); dentro de DepthRenderer.draw(). A la izquierda, se muestran los tamaños de puntos 5 y 10.

7. Analiza nubes de puntos en 3D (Parte 3)

Puedes analizar los datos de profundidad una vez que verifiques que existen en una sesión de RA. Una herramienta importante para analizar la profundidad es el valor de confianza de cada píxel. Utiliza valores de confianza para analizar nubes de puntos 3D.

Invalidar píxeles de baja confianza

Recuperaste el valor de confianza para cada píxel de profundidad y lo guardaste junto con cada punto dentro de DepthData, pero aún no lo usaste.

Los valores de confidenceNormalized varían de 0 a 1, donde 0 indica un nivel de confianza bajo y 1 indica un nivel de confianza total. Modifica el método convertRawDepthImagesTo3dPointBuffer() en la clase DepthData para evitar guardar píxeles cuya confianza sea demasiado baja para ser útil.

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

Prueba diferentes umbrales de nivel de confianza para ver cuántos puntos de profundidad se mantienen en cada nivel.

Confianza >= 0.1

Confianza >= 0.3

Confianza >= 0.5

Confianza >= 0.7

Confianza >= 0.9

Filtrar píxeles por distancia

También puedes filtrar los píxeles de profundidad por distancia. En los próximos pasos, se trabaja con geometrías cercanas a la cámara. Para optimizar el rendimiento, puedes ignorar los puntos que están demasiado lejos.

Usa el siguiente código para actualizar el código de verificación de confianza que acabas de agregar:

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

Ahora solo verás confianza alta y puntos cercanos.

Filtrado de distancias

Limita la nube de puntos para que esté a 1.5 metros de la cámara.

Comparar puntos y planos en 3D

Puedes comparar los planos y los puntos geométricos en 3D y usarlos para filtrar los datos entre sí, por ejemplo, quitando los puntos que están cerca de los planos de RA observados.

Este paso dejará solo las palabras "no planas" que suelen representar superficies en objetos del entorno. Agrega el método filterUsingPlanes() al final de la clase DepthData. Este método itera a través de los puntos existentes, compara cada uno con cada plano y invalida cualquier punto que esté demasiado cerca de un plano de RA, lo que deja áreas no planas en las que se destacan objetos en la escena.

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

Puedes agregar este método a RawDepthCodelabActivity en el método 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);

Ahora, cuando se ejecuta el codelab, se renderiza un subconjunto de puntos. Estos puntos representan los objetos de la escena mientras se ignoran las superficies planas sobre las que descansan los objetos. Puedes usar estos datos para estimar el tamaño y la posición de los objetos agrupando puntos en clústeres.

Taza de té

Micrófono

Auriculares

Pillow

Puntos del clúster

Este codelab contiene un algoritmo de agrupamiento en clústeres de Pointcloud muy simplista. Actualiza el codelab para agrupar las nubes de puntos recuperadas en clústeres definidos por cuadros de límite alineados con los ejes.

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;

Agrega un BoxRenderer a esta clase en la parte superior del archivo, con los otros procesadores.

private final BoxRenderer boxRenderer = new BoxRenderer();

Dentro del método onSurfaceCreated(), agrega lo siguiente junto con los otros procesadores:

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

Por último, agrega las siguientes líneas a onDrawFrame() dentro de RawDepthCodelabActivity para agrupar las nubes de puntos recuperadas en clústeres y renderizar los resultados como cuadros de límite alineados con los ejes.

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

Taza de té

Micrófono

Auriculares

Pillow

Ahora puedes recuperar Raw Depth a través de una sesión de ARCore, convertir la información de profundidad en nubes de puntos 3D y realizar operaciones básicas de filtrado y renderización en esos puntos.

8. Crea, ejecuta y realiza pruebas

Compila, ejecuta y prueba tu app.

Compila y ejecuta tu app

Para compilar y ejecutar tu app, sigue estos pasos:

  1. Conecta un dispositivo compatible con ARCore por USB.
  2. Ejecuta tu proyecto con el botón ► de la barra de menú.
  3. Espera a que la app se compile y se implemente en el dispositivo.

La primera vez que intentes implementar la app en tu dispositivo, deberás hacer lo siguiente

Cómo permitir la depuración por USB

en el dispositivo. Selecciona Aceptar para continuar.

La primera vez que ejecutes la app en el dispositivo, se te preguntará si tiene permiso para usar la cámara. Debes permitir ese acceso para seguir usando la funcionalidad de RA.

Cómo probar la app

Si ejecutas la app, puedes probar su comportamiento básico. Para ello, sostén el dispositivo, muévete por el lugar y escanea lentamente el área. Procura obtener al menos 10 segundos de datos y examina el área desde distintas direcciones antes de avanzar al siguiente paso.

9. Felicitaciones

Felicitaciones, compilaste y ejecutaste correctamente tu primera app de realidad aumentada basada en profundidad con la API de Raw Depth de ARCore de Google. ¡Estamos ansiosos por ver tus creaciones!

10. Soluciona problemas

Cómo configurar un dispositivo Android para el desarrollo

  1. Conecta el dispositivo a la máquina de desarrollo con un cable USB. Si desarrollas la app en Windows, es posible que debas instalar el controlador USB adecuado para tu dispositivo.
  2. Completa los siguientes pasos a fin de habilitar la depuración por USB en la ventana Opciones para desarrolladores:
  • Abre la app de Configuración.
  • Si tu dispositivo usa Android 8.0 o una versión posterior, selecciona Sistema.
  • Desplázate hasta la parte inferior y selecciona Acerca del teléfono.
  • Desplázate hasta la parte inferior y presiona Número de compilación siete veces.
  • Regresa a la pantalla anterior, desplázate hasta la parte inferior y presiona Opciones para desarrolladores.
  • En la ventana Opciones para desarrolladores, desplázate hacia abajo para buscar y habilitar la depuración por USB.

Obtén información más detallada sobre este proceso en el sitio web para desarrolladores de Android de Google.

Si encuentras una falla de compilación relacionada con las licencias (Failed to install the following Android SDK packages as some licences have not been accepted), puedes usar los siguientes comandos para revisar y aceptar las licencias:

cd <path to Android SDK>

tools/bin/sdkmanager --licenses

Preguntas frecuentes