Profundidade bruta do ARCore

1. Introdução

O ARCore é uma plataforma para criar apps de realidade aumentada (RA) para dispositivos móveis. A API Depth do ARCore do Google oferece acesso a uma imagem de profundidade para cada frame em uma sessão do ARCore. Cada pixel na imagem de profundidade fornece uma medida da distância da câmera ao ambiente.

A API Raw Depth fornece imagens de profundidade que não são transmitidas por operações de filtragem de espaço na tela, projetadas para suavizar e interpolar os resultados. Esses valores são mais precisamente geométricos, mas podem conter dados ausentes e estar menos alinhados com a imagem da câmera associada.

Este codelab mostra como usar a API Raw Depth para fazer análises da geometria 3D da cena. Você vai criar um app simples compatível com RA que usa dados brutos de profundidade para detectar e visualizar a geometria do mundo.

As APIs Depth e Raw Depth são compatíveis apenas com um subconjunto de dispositivos compatíveis com ARCore. A API Depth está disponível apenas no Android.

O que você vai criar

Neste codelab, você vai criar um app que usa imagens de profundidade bruta para cada frame para realizar análises geométricas do mundo ao seu redor. Este app vai:

  1. Confira se o dispositivo de destino é compatível com o recurso "profundidade".
  2. Extraia a imagem de profundidade bruta de cada frame da câmera.
  3. Reprojetar imagens de profundidade bruta em pontos 3D e filtrá-los com base na confiança e na geometria.
  4. Use a nuvem de pontos de profundidade bruta para segmentar objetos de interesse em 3D.

uma prévia do que você vai criar.

Observação: se você tiver problemas durante o processo, vá para a última seção e veja algumas dicas de solução de problemas.

2. Pré-requisitos

Você precisará de hardware e software específicos para concluir este codelab.

Requisitos de hardware

  • Um dispositivo compatível com o ARCore com depuração USB ativada, conectado por um cabo USB à máquina de desenvolvimento. Esse dispositivo também precisa ser compatível com a API Depth.

Requisitos de software

3. Configurar

Configurar a máquina de desenvolvimento

Conecte o dispositivo ARCore ao computador usando o cabo USB. Verifique se a depuração USB está ativada no dispositivo. Abra um terminal e execute adb devices, como mostrado abaixo:

adb devices

List of devices attached
<DEVICE_SERIAL_NUMBER>    device

O <DEVICE_SERIAL_NUMBER> será uma string exclusiva do dispositivo. Antes de continuar, verifique se apenas um dispositivo é exibido.

Fazer o download e instalar o código

Você pode clonar o repositório:

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

Ou fazer o download de um arquivo ZIP e extraí-lo:

Siga estas etapas para começar a trabalhar com o código.

  1. Inicie o Android Studio e escolha Open an existing Android Studio project.
  2. Navegue até o diretório local em que você armazenou o arquivo ZIP com profundidade bruta.
  3. Clique duas vezes no diretório arcore_rawdepthapi_codelab.

O diretório arcore_rawdepthapi_codelab é um único projeto do Gradle com vários módulos. Se o painel Project no canto superior esquerdo do Android Studio ainda não aparecer no painel Project, clique em Projects no menu suspenso.

O resultado ficará assim:

Este projeto contém os seguintes módulos:

  • part0_work: o app inicial. Ao fazer este codelab, você fará editará esse arquivo. Todas as outras partes contêm códigos de referência.
  • part1: o código de referência para saber como o arquivo editado ficará após concluir a Parte 1.
  • part2: o código de referência após concluir a Parte 2.
  • part3_completed: o código de referência após concluir a Parte 3, que é o fim do codelab.

Você trabalhará com o módulo part0_work. Também fornecemos soluções completas para cada parte do codelab. Cada módulo é um app que pode ser compilado.

4. Executar o app inicial

Siga estas etapas para executar o app inicial Raw Depth.

  1. Navegue até Executar > Executar... > ‘part0_work'.
  2. Na caixa de diálogo Select Deployment Target, selecione seu dispositivo na lista Connected Devices e clique em OK.

O Android Studio criará o app inicial e o executará no dispositivo.

Quando você executar o app pela primeira vez, ele solicitará a permissão para usar a CÂMERA. Toque em Permitir para continuar.

No momento, o app não faz nada.Esse é o aplicativo de RA mais básico, mostrando uma visualização da câmera da sua cena, mas sem fazer mais nada.O código atual é semelhante ao exemplo Hello AR publicado com o SDK do ARCore.

Em seguida, você usará a API Raw Depth para recuperar a geometria da cena ao seu redor.

5. Configurar a API Raw Depth (Parte 1)

Verifique se o dispositivo de destino é compatível com o recurso "Profundidade"

Nem todos os dispositivos com suporte ao ARCore podem executar a API Depth. Confira se o dispositivo de destino oferece suporte ao recurso Depth antes de adicionar funcionalidade ao app na função onResume() da RawDepthCodelabActivity.java, em que uma nova sessão é criada.

Veja o código existente:

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

Atualize para garantir que o app seja executado apenas em dispositivos com suporte à 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.";
}

Ativar profundidade bruta

A API Raw Depth fornece uma imagem de profundidade não suave e uma imagem de confiança correspondente contendo confiança de profundidade para cada pixel na imagem de profundidade bruta. Ative a opção "Profundidade bruta" atualizando o código a seguir na instrução try-catch que você acabou 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;
}

Agora a sessão de RA está configurada corretamente e o app pode usar recursos com base em profundidade.

Chamar a API Depth

Em seguida, chame a API Depth para recuperar imagens de profundidade de cada frame. Consolide os dados de profundidade em uma nova classe criando um novo arquivo. Clique com o botão direito do mouse na pasta rawdepth e selecione New > Java Class. Isso cria um arquivo em branco. Adicione o seguinte a essa 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.

}

Essa classe é usada para converter imagens de profundidade em nuvens de pontos. As nuvens de pontos representam a geometria da cena com uma lista de pontos, cada um com uma coordenada 3D (x, y, z) e um valor de confiança no intervalo de 0 a 1.

Adicione chamadas para preencher esses valores usando a API Raw Depth adicionando um método create() na parte inferior da classe. Esse método consulta as imagens de profundidade e confiança mais recentes, armazenando a nuvem de pontos resultante. As imagens de profundidade e confiança terão dados correspondentes.

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

Nesse momento, o código também armazena a âncora da câmera para que as informações de profundidade possam ser transformadas em coordenadas mundiais chamando um método auxiliar convertRawDepthImagesTo3dPointBuffer(). Esse método auxiliar usa cada pixel na imagem de profundidade e usa os intrínsecos da câmera para desprojetar a profundidade em um ponto 3D relativo à câmera. Em seguida, a âncora da câmera é usada para converter a posição do ponto em coordenadas mundiais. Cada pixel que existe é convertido em um ponto 3D (em unidades de metros) e armazenado junto com sua confiança.

Adicione o seguinte método auxiliar 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;
    }

Acessar os dados mais recentes de profundidade bruta de cada frame

Modifique o app para recuperar informações de profundidade e alinhá-las às coordenadas mundiais de cada pose.

Em RawDepthCodelabActivity.java, no método onDrawFrame(), encontre as linhas 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);

Adicione as seguintes linhas logo abaixo:

// 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. Renderizar os dados de profundidade (Parte 2)

Agora que você tem uma nuvem de pontos de profundidade para testar, é hora de conferir como os dados ficam renderizados na tela.

Adicione um renderizador para visualizar pontos de profundidade

Adicione um renderizador para mostrar os pontos de profundidade.

Primeiro, adicione uma nova classe para conter a lógica de renderização. Essa classe executa as operações do OpenGL para inicializar sombreadores para visualizar a profundidade da nuvem de pontos.

Adicionar a classe DepthRenderer

  1. Clique com o botão direito do mouse no diretório de origem rendering.
  2. Selecione New > Java Class.
  3. Nomeie a classe como DepthRenderer.

Preencha essa classe com o seguinte 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");
    }
}

Renderizar os dados de profundidade

Em seguida, forneça a origem dos sombreadores de renderização. Adicione o método update() abaixo na parte de baixo da classe DepthRenderer. Esse método usa as informações de profundidade mais recentes como entrada e copia os dados da nuvem de pontos para a 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");
    }

Exiba os dados mais recentes na tela adicionando um método draw() à parte de baixo da classe DepthRenderer. Esse método usa as informações da nuvem de pontos 3D e as projeta de volta na visualização da câmera para que possam ser renderizadas na tela.

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

É possível definir o tamanho do ponto em diferentes tamanhos,em pixels, usando a variável pointSizeUniform. pointSizeUniform está definido como 5 pixels no app de exemplo.

Adicionar novos sombreadores

Há muitas maneiras de conferir os dados de profundidade e de profundidade no app. Aqui, você vai adicionar alguns sombreadores e criar uma visualização simples do mapeamento de cores.

Adicione novos sombreadores .vert e .frag ao diretório src/main/assets/shaders/.

Como adicionar um novo sombreador .vert

No Android Studio:

  1. Clique com o botão direito no diretório de sombreadores.
  2. Selecione New -> File.
  3. Nomeie o arquivo como depth_point_cloud.vert.
  4. Defina-o como um arquivo de texto.

No novo arquivo .vert, adicione o seguinte 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;
}

Esse sombreador usa o Turbo colormap (link em inglês) para melhorar a visualização. Ele executa as seguintes etapas:

  1. Recupera a elevação de cada ponto (eixo Y em coordenadas mundiais).
  2. Calcula uma cor associada a essa elevação (vermelho=baixo, azul=alto).
  3. Calcula a posição da tela de cada ponto.
  4. Define o tamanho (em pixels) de cada ponto, conforme definido no método DepthRenderer.update().

Crie um sombreador de fragmento no mesmo diretório e nomeie-o como depth_point_cloud.frag, repetindo as mesmas etapas desta seção.

Em seguida, adicione o código a seguir a esse novo arquivo para renderizar cada ponto como um único vértice de cor uniforme, conforme definido no sombreador de vértice.

src/main/assets/shaders/depth_point_cloud.frag

precision mediump float;
varying vec4 v_Color;

void main() {
    gl_FragColor = v_Color;
}

Para aplicar essa renderização, adicione chamadas à classe DepthRenderer dentro do RawDepthCodelabActivity.

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

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

Na parte superior da turma, adicione um participante particular ao lado de backgroundRenderer.

private final DepthRenderer depthRenderer = new DepthRenderer();

O depthRenderer precisa ser inicializado dentro de RawDepthCodelabActivity.onSurfaceCreated(), assim como o backgroundRenderer existente.

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

Adicione o código abaixo ao final do bloco try-catch dentro de onDrawFrame para mostrar a profundidade mais recente do frame atual.

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

Com essas mudanças, o app vai ser criado corretamente e mostrar a profundidade da nuvem de pontos.

Exemplo de visualização de nuvem de profundidade bruta

  • Cada amostra de ponto é colorida pela profundidade.
  • Os pontos vermelhos estão próximos, e os pontos verdes/azuis estão mais distantes
  • Alguns dados ausentes ou "buracos" pode ser visto em áreas com características de imagem insuficientes, como paredes ou tetos brancos.
  • É possível testar o tamanho do ponto renderizado ajustando a linha GLES20.glUniform1f(pointSizeUniform, 5.0f); dentro de DepthRenderer.draw(). À esquerda, estão os tamanhos de ponto 5 e 10.

7. Analisar nuvens de pontos em 3D (parte 3)

Para analisar os dados de profundidade, verifique se eles existem em uma sessão de RA. Uma ferramenta importante para analisar a profundidade é o valor de confidence para cada pixel. Use valores de confiança para analisar nuvens de pontos em 3D.

Invalidar pixels de baixa confiança

Você recuperou o valor de confiança de cada pixel de profundidade e o salvou ao lado de cada ponto em DepthData, mas ainda não o usou.

Os valores de confidenceNormalized variam de 0 a 1, sendo que 0 indica baixa confiança e 1 indica confiança total. Modifique o método convertRawDepthImagesTo3dPointBuffer() na classe DepthData para evitar salvar pixels cuja confiança seja muito baixa 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 *********

Teste diferentes limites de confiança e veja quantos pontos de profundidade são mantidos em cada nível.

Confiança >= 0,1

Confiança >= 0,3

Confiança >= 0,5

Confiança >= 0,7

Confiança >= 0,9

Filtrar pixels por distância

Também é possível filtrar pixels de profundidade por distância. As próximas etapas abordam a geometria próxima à câmera. Para otimizar o desempenho, ignore os pontos que estão muito distantes.

Atualize o código de verificação de confiança que você acabou de adicionar com o seguinte código:

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

Agora você verá apenas pontos de alta confiança e de fechamento.

Filtragem de distância

Limita a nuvem de pontos a uma distância de até 1,5 metro da câmera.

Comparar pontos e planos em 3D

Você pode comparar os planos e pontos geométricos em 3D e usá-los para filtrar uns aos outros, como remover pontos próximos a planos de RA observados.

Esta etapa deixará apenas "não planos" que tendem a representar superfícies em objetos no ambiente. Adicione o método filterUsingPlanes() à parte de baixo da classe DepthData. Esse método itera pelos pontos atuais, verifica cada ponto em relação a cada plano e invalida qualquer ponto que esteja muito próximo de um plano de RA, deixando áreas não planas que destacam objetos na cena.

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

Você pode adicionar esse método ao RawDepthCodelabActivity no 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);

A execução do codelab agora resulta na renderização de um subconjunto de pontos. Esses pontos representam os objetos na cena e ignoram as superfícies planas sobre as quais eles estão. Use esses dados para estimar o tamanho e a posição dos objetos agrupando os pontos.

Xícara de chá

Microfone

Fones de ouvido

Pillow

Pontos do cluster

Este codelab contém um algoritmo de clustering de ponto de nuvem muito simplificado. Atualize o codelab para agrupar as nuvens de pontos recuperadas em clusters definidos por caixas delimitadoras alinhadas ao eixo.

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;

Adicione um BoxRenderer a essa classe na parte de cima do arquivo, com os outros renderizadores.

private final BoxRenderer boxRenderer = new BoxRenderer();

Dentro do método onSurfaceCreated(), adicione o código abaixo junto com os outros renderizadores:

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

Por fim, adicione as linhas abaixo a onDrawFrame() dentro de RawDepthCodelabActivity para agrupar as nuvens de pontos recuperadas em clusters e renderizar os resultados como caixas delimitadoras alinhadas ao eixo.

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

Xícara de chá

Microfone

Fones de ouvido

Pillow

Agora é possível recuperar a profundidade bruta com uma sessão do ARCore, converter as informações de profundidade em nuvens de pontos em 3D e realizar operações básicas de filtragem e renderização nesses pontos.

8. Build-Run-Test

Crie, execute e teste seu app.

Criar e executar o app

Siga estas etapas para criar e executar o app:

  1. Conecte um dispositivo compatível com o ARCore via USB.
  2. Execute o projeto com o botão ► na barra de menus.
  3. Espere o app ser criado e implantado no dispositivo.

Na primeira vez que você tentar implantar o app no seu dispositivo, será preciso

Permitir a depuração USB

no dispositivo. Selecione "OK" para continuar.

Na primeira vez que você executar o app no dispositivo, ele pedirá permissão para usar a câmera. É necessário autorizar o acesso para continuar usando a funcionalidade de RA.

Como testar seu app

Ao executar o app, você pode testar o comportamento básico dele segurando o dispositivo, movendo-o pelo espaço e digitalizando lentamente uma área. Tente coletar pelo menos 10 segundos de dados e digitalizar a área de várias direções antes de continuar para a próxima etapa.

9. Parabéns

Parabéns! Você criou e executou seu primeiro app de realidade aumentada com base em profundidade usando a API Raw Depth do ARCore do Google. Estamos ansiosos para ver o que você criará!

10. Solução de problemas

Como configurar o dispositivo Android para desenvolvimento

  1. Conecte o dispositivo à máquina de desenvolvimento usando um cabo USB. Se você usa o Windows para desenvolver, pode ser necessário instalar o driver USB adequado para o dispositivo.
  2. Siga estas etapas para ativar a depuração USB na janela Opções do desenvolvedor:
  • Abra o app Configurações.
  • Se o dispositivo usa o Android v8.0 ou versão posterior, selecione System.
  • Navegue até a parte inferior da tela e selecione About phone.
  • Role até a parte de baixo da tela e toque em Número da versão sete vezes.
  • Volte à tela anterior, navegue até a parte inferior e toque em Opções do desenvolvedor.
  • Na janela Opções do desenvolvedor, role para baixo para encontrar e ativar a depuração USB.

Veja informações mais detalhadas sobre esse processo no site para desenvolvedores Android do Google.

Se você encontrar uma falha de compilação relacionada às licenças (Failed to install the following Android SDK packages as some licences have not been accepted), use os seguintes comandos para analisar e aceitar essas licenças:

cd <path to Android SDK>

tools/bin/sdkmanager --licenses

Perguntas frequentes