Необработанная глубина ARCore

1. Введение

ARCore — это платформа для создания приложений дополненной реальности (AR) на мобильных устройствах. API глубины ARCore от Google предоставляет доступ к изображению глубины для каждого кадра в сеансе ARCore . Каждый пиксель изображения глубины обеспечивает измерение расстояния от камеры до окружающей среды.

API Raw Depth предоставляет изображения глубины, которые не проходят через операции фильтрации экранного пространства, предназначенные для сглаживания и интерполяции результатов. Эти значения более геометрически точны, но могут содержать недостающие данные и менее согласованы с соответствующим изображением камеры.

В этой кодовой лаборатории показано, как использовать API Raw Depth для выполнения трехмерного анализа геометрии сцены. Вы создадите простое приложение с поддержкой дополненной реальности, которое использует необработанные данные о глубине для обнаружения и визуализации геометрии мира.

API-интерфейсы Depth и Raw Depth поддерживаются только на некоторых устройствах с поддержкой ARCore . API глубины доступен только на Android.

Что ты построишь

В этой лаборатории вы создадите приложение, которое использует необработанные изображения глубины для каждого кадра для выполнения геометрического анализа мира вокруг вас. Это приложение будет:

  1. Проверьте, поддерживает ли целевое устройство глубину.
  2. Получите необработанное изображение глубины для каждого кадра камеры.
  3. Перепроецируйте необработанные изображения глубины в 3D-точки и фильтруйте эти точки на основе достоверности и геометрии.
  4. Используйте необработанное облако точек глубины для сегментации интересующих трехмерных объектов.

Предварительный просмотр того, что вы будете строить.

Примечание. Если в ходе работы у вас возникнут проблемы, перейдите к последнему разделу, где вы найдете советы по устранению неполадок.

2. Предварительные условия

Для выполнения этой лаборатории кода вам понадобится специальное оборудование и программное обеспечение.

Требования к оборудованию

  • Устройство с поддержкой ARCore и включенной отладкой по USB, подключенное через USB-кабель к вашей машине разработки. Это устройство также должно поддерживать Depth API.

Требования к программному обеспечению

3. Настройка

Настройте машину разработки

Подключите устройство ARCore к компьютеру через USB-кабель. Убедитесь, что ваше устройство поддерживает отладку по USB . Откройте терминал и запустите adb devices , как показано ниже:

adb devices

List of devices attached
<DEVICE_SERIAL_NUMBER>    device

<DEVICE_SERIAL_NUMBER> будет строкой, уникальной для вашего устройства. Прежде чем продолжить, убедитесь, что вы видите ровно одно устройство .

Загрузите и установите код

Вы можете клонировать репозиторий:

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

Или загрузите ZIP-файл и распакуйте его:

Выполните следующие действия, чтобы начать работу с кодом.

  1. Запустите Android Studio и выберите «Открыть существующий проект Android Studio» .
  2. Перейдите в локальный каталог, в котором вы сохранили ZIP-файл Raw Depth.
  3. Дважды щелкните каталог arcore_rawdepthapi_codelab .

Каталог arcore_rawdepthapi_codelab — это один проект Gradle с несколькими модулями. Если панель «Проект» в левом верхнем углу Android Studio еще не отображается на панели «Проект», нажмите «Проекты» в раскрывающемся меню.

Результат должен выглядеть так:

Этот проект содержит следующие модули:

  • part0_work : стартовое приложение. Вам следует внести изменения в этот модуль при выполнении этой лаборатории кода. Все остальные части содержат ссылочный код.
  • part1 : Справочный код того, как должны выглядеть ваши изменения после завершения части 1.
  • part2 : Справочный код после завершения части 2.
  • part3_completed : ссылочный код после завершения части 3, которая является завершением лаборатории кода.

Вы будете работать в модуле part0_work . Существуют также полные решения для каждой части лаборатории кода. Каждый модуль представляет собой сборное приложение.

4. Запустите стартовое приложение.

Выполните следующие действия, чтобы запустить стартовое приложение Raw Depth.

  1. Перейдите в «Выполнить» > «Выполнить...» > «part0_work» .
  2. В диалоговом окне «Выбор цели развертывания» выберите свое устройство из списка «Подключенные устройства» и нажмите «ОК» .

Android Studio создаст исходное приложение и запустит его на вашем устройстве.

Когда вы запускаете приложение в первый раз, оно запросит разрешение КАМЕРЫ. Нажмите «Разрешить» , чтобы продолжить.

В настоящее время приложение ничего не делает . Это самое простое AR-приложение, которое показывает вид вашей сцены с камеры, но больше ничего не делает. Существующий код аналогичен образцу Hello AR, опубликованному с помощью ARCore SDK.

Далее вы будете использовать API Raw Depth для получения геометрии сцены вокруг вас.

5. Настройте API Raw Depth (часть 1).

Убедитесь, что целевое устройство поддерживает глубину.

Не все устройства с поддержкой ARCore могут использовать Depth API. Убедитесь, что целевое устройство поддерживает Depth, прежде чем добавлять функциональные возможности в ваше приложение внутри функции onResume() RawDepthCodelabActivity.java , где создается новый сеанс.

Найдите существующий код:

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

Обновите его, чтобы приложение работало только на устройствах, поддерживающих Depth API.

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

Включить необработанную глубину

API Raw Depth предоставляет несглаженное изображение глубины и соответствующее достоверное изображение, содержащее достоверность глубины для каждого пикселя необработанного изображения глубины. Включите Raw Depth, обновив следующий код в только что измененном операторе try-catch.

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

Теперь сеанс AR настроен соответствующим образом, и приложение может использовать функции, основанные на глубине.

Вызов API глубины

Затем вызовите Depth API, чтобы получить изображения глубины для каждого кадра. Инкапсулируйте данные глубины в новый класс, создав новый файл. Щелкните правой кнопкой мыши папку rawdepth и выберите New > Java Class . Это создает пустой файл. Добавьте в этот класс следующее:

src/main/java/com/google/ar/core/codelab/rawlength/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.

}

Этот класс используется для преобразования изображений глубины в облака точек. Облака точек представляют геометрию сцены списком точек, каждая из которых имеет трехмерную координату (x, y, z) и значение достоверности в диапазоне от 0 до 1.

Добавьте вызовы для заполнения этих значений с помощью API Raw Depth, добавив метод create() в нижней части класса. Этот метод запрашивает последние изображения глубины и достоверности, сохраняя полученное облако точек. Изображения глубины и достоверности будут иметь совпадающие данные.

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

В это время код также сохраняет привязку камеры, так что информацию о глубине можно преобразовать в мировые координаты, вызвав вспомогательный метод convertRawDepthImagesTo3dPointBuffer() . Этот вспомогательный метод берет каждый пиксель изображения глубины и использует встроенные функции камеры для отмены проецирования глубины в трехмерную точку относительно камеры. Затем привязка камеры используется для преобразования положения точки в мировые координаты. Каждый существующий пиксель преобразуется в трехмерную точку (в метрах) и сохраняется вместе с ее достоверностью.

Добавьте в 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;
    }

Получите последние необработанные данные о глубине для каждого кадра.

Измените приложение так, чтобы оно получало информацию о глубине и согласовывало ее с мировыми координатами для каждой позы.

В RawDepthCodelabActivity.java в методе 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);

Добавьте следующие строки чуть ниже:

// 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. Рендеринг данных глубины (Часть 2)

Теперь, когда у вас есть облако точек глубины, пришло время посмотреть, как выглядят данные, отображаемые на экране.

Добавьте средство визуализации для визуализации точек глубины.

Добавьте средство визуализации для визуализации точек глубины.

Сначала добавьте новый класс, содержащий логику рендеринга. Этот класс выполняет операции OpenGL для инициализации шейдеров для визуализации облака точек глубины.

Добавить класс DepthRenderer

  1. Щелкните правой кнопкой мыши каталог источника rendering
  2. Выберите New > Java Class .
  3. Назовите класс DepthRenderer .

Заполните этот класс следующим кодом:

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

Отображение данных о глубине

Затем укажите источник для шейдеров рендеринга. Добавьте следующий метод update() в нижнюю часть класса DepthRenderer . Этот метод принимает на вход самую последнюю информацию о глубине и копирует данные облака точек в графический процессор.

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

Выведите на экран последние данные, добавив метод draw() в конец класса DepthRenderer . Этот метод принимает информацию трехмерного облака точек и проецирует ее обратно в поле зрения камеры, чтобы ее можно было отобразить на экране.

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

Вы можете установить размер точек разных размеров в пикселях, используя переменную pointSizeUniform . В примере приложения для pointSizeUniform установлено значение 5 пикселей.

Добавить новые шейдеры

Существует множество способов просмотра глубины и отображения данных о глубине в вашем приложении. Здесь вы добавите несколько шейдеров и создадите простую визуализацию цветового сопоставления.

Добавьте новые шейдеры .vert и .frag в каталог src/main/assets/shaders/ .

Добавление нового шейдера .vert

В Android-студии:

  1. Щелкните правой кнопкой мыши каталог шейдеров.
  2. Выберите Создать -> Файл.
  3. Назовите его depth_point_cloud.vert .
  4. Установите его как текстовый файл.

В новый файл .vert добавьте следующий код:

src/main/assets/shaders/length_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;
}

Этот шейдер использует цветовую карту Turbo для улучшения визуализации. Он выполняет следующие шаги:

  1. Получает высоту каждой точки (ось Y в мировых координатах).
  2. Вычисляет цвет, связанный с этой высотой (красный = низкий, синий = высокий).
  3. Вычисляет положение каждой точки на экране.
  4. Устанавливает размер (в пикселях) для каждой точки, как определено в методе DepthRenderer.update() .

Создайте фрагментный шейдер в том же каталоге и назовите его depth_point_cloud.frag , повторяя те же шаги, что и в этом разделе.

Затем добавьте в этот новый файл следующий код, чтобы отобразить каждую точку как одну вершину однородного цвета, как определено в вершинном шейдере.

src/main/assets/shaders/length_point_cloud.frag

precision mediump float;
varying vec4 v_Color;

void main() {
    gl_FragColor = v_Color;
}

Чтобы применить этот рендеринг, добавьте вызовы класса DepthRenderer внутри вашего RawDepthCodelabActivity .

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

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

В верхней части класса добавьте закрытый член рядом с backgroundRenderer .

private final DepthRenderer depthRenderer = new DepthRenderer();

depthRenderer необходимо инициализировать внутри RawDepthCodelabActivity.onSurfaceCreated() , как и существующий backgroundRenderer .

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

Добавьте следующий код в конец блока try-catch внутри onDrawFrame чтобы показать последнюю глубину текущего кадра.

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

Благодаря этим изменениям приложение теперь должно успешно построиться и показать облако точек глубины.

Пример визуализации необработанного облака точек глубины

  • Каждый точечный образец окрашен в соответствии с его глубиной.
  • Красные точки расположены близко, зеленые/синие — дальше.
  • Некоторые недостающие данные или «дыры» можно увидеть в областях с недостаточными характеристиками изображения, например, на пустых белых стенах или потолках.
  • Вы можете поиграть с отображаемым размером точек, отрегулировав строку GLES20.glUniform1f(pointSizeUniform, 5.0f); внутри DepthRenderer.draw() . Слева показаны размеры точек 5 и 10.

7. Анализ 3D-облаков точек (Часть 3)

Вы можете анализировать данные о глубине, как только убедитесь, что они существуют в сеансе AR. Важным инструментом анализа глубины является значение достоверности для каждого пикселя. Используйте значения достоверности для анализа трехмерных облаков точек.

Недействительные пиксели с низкой достоверностью

Вы получили значение достоверности для каждого пикселя глубины и сохранили его рядом с каждой точкой внутри DepthData , но еще не использовали его.

Значения confidenceNormalized диапазон от 0 до 1, где 0 указывает на низкую достоверность, а 1 указывает на полную достоверность. Измените метод convertRawDepthImagesTo3dPointBuffer() в классе DepthData , чтобы избежать сохранения пикселей, достоверность которых слишком низка, чтобы быть полезными.

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

Попробуйте разные пороговые значения уровня достоверности, чтобы увидеть, сколько точек глубины сохраняется на каждом уровне.

Уверенность >= 0,1

Уверенность >= 0,3

Уверенность >= 0,5

Уверенность >= 0,7

Уверенность >= 0,9

Фильтровать пиксели по расстоянию

Вы также можете фильтровать пиксели глубины по расстоянию. Следующие шаги касаются геометрии вблизи камеры. Для оптимизации производительности вы можете игнорировать точки, которые находятся слишком далеко.

Обновите только что добавленный код проверки достоверности, добавив следующее:

src/main/java/com/google/ar/core/codelab/rawlength/DepthData.java

if (confidenceNormalized < 0.3 || depthMeters > 1.5) {
    // Ignore "low-confidence" pixels or depth that is too far away.
   continue;
 }

Теперь вы увидите только точки высокой достоверности и близкие точки.

Дистанционная фильтрация

Ограничивает облако точек расстоянием до 1,5 метров от камеры.

Сравнение 3D-точек и плоскостей

Вы можете сравнивать геометрические 3D-точки и плоскости и использовать их для фильтрации друг друга, например для удаления точек, близких к наблюдаемым плоскостям AR.

На этом этапе останутся только «неплоские» точки, которые обычно представляют поверхности объектов в окружающей среде. Добавьте метод filterUsingPlanes() в конец класса DepthData . Этот метод перебирает существующие точки, проверяет каждую точку на соответствие каждой плоскости и делает недействительной любую точку, которая находится слишком близко к плоскости AR, оставляя неплоские области, которые выделяют объекты на сцене.

src/main/java/com/google/ar/core/codelab/rawlength/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);
            }
        }
    }

Вы можете добавить этот метод в RawDepthCodelabActivity в методе 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);

Запуск лаборатории кода теперь приводит к рендерингу подмножества точек. Эти точки представляют объекты сцены, игнорируя при этом плоские поверхности, на которых лежат объекты. Вы можете использовать эти данные для оценки размера и положения объектов путем группировки точек.

Чашка чая

Микрофон

Наушники

Подушка

Кластерные точки

Эта кодовая лаборатория содержит очень упрощенный алгоритм кластеризации облаков точек . Обновите кодовую лабораторию, чтобы сгруппировать полученные облака точек в кластеры, определяемые ограничивающими рамками, выровненными по осям.

src/main/java/com/google/ar/core/codelab/rawlength/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;

Добавьте BoxRenderer в этот класс вверху файла вместе с другими средствами визуализации.

private final BoxRenderer boxRenderer = new BoxRenderer();

А внутри метода onSurfaceCreated() добавьте следующее рядом с другими средствами визуализации:

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

Наконец, добавьте следующие строки в onDrawFrame() внутри RawDepthCodelabActivity , чтобы сгруппировать полученные облака точек в кластеры и визуализировать результаты в виде ограничивающих рамок, выровненных по оси.

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

Чашка чая

Микрофон

Наушники

Подушка

Теперь вы можете получить необработанную глубину через сеанс ARCore, преобразовать информацию о глубине в трехмерные облака точек и выполнить базовые операции фильтрации и рендеринга для этих точек.

8. Сборка-запуск-тестирование

Создайте, запустите и протестируйте свое приложение.

Создайте и запустите свое приложение

Выполните следующие действия, чтобы создать и запустить приложение:

  1. Подключите устройство с поддержкой ARCore через USB.
  2. Запустите проект с помощью кнопки ► в строке меню.
  3. Подождите, пока приложение будет создано и развернуто на вашем устройстве.

При первой попытке развернуть приложение на своем устройстве вам потребуется

Разрешить отладку по USB

на устройстве. Выберите ОК, чтобы продолжить.

При первом запуске приложения на устройстве вас спросят, есть ли у приложения разрешение на использование камеры вашего устройства. Вы должны разрешить доступ, чтобы продолжить использование функций AR.

Тестирование вашего приложения

Запустив приложение, вы можете проверить его базовое поведение, удерживая устройство, перемещаясь по пространству и медленно сканируя область. Постарайтесь собрать данные хотя бы за 10 секунд и просканировать область с нескольких направлений, прежде чем переходить к следующему шагу.

9. Поздравления

Поздравляем, вы успешно создали и запустили свое первое приложение дополненной реальности на основе глубины с помощью API Google ARCore Raw Depth. Мы с нетерпением ждем возможности увидеть, что вы построите!

10. Устранение неполадок

Настройка устройства Android для разработки

  1. Подключите устройство к компьютеру разработки с помощью USB-кабеля. Если вы разрабатываете с использованием Windows, вам может потребоваться установить соответствующий драйвер USB для вашего устройства.
  2. Выполните следующие шаги, чтобы включить отладку по USB в окне параметров разработчика :
  • Откройте приложение «Настройки» .
  • Если на вашем устройстве используется Android версии 8.0 или выше, выберите «Система» .
  • Прокрутите страницу вниз и выберите «О телефоне» .
  • Прокрутите вниз и семь раз коснитесь номера сборки .
  • Вернитесь на предыдущий экран, прокрутите страницу вниз и нажмите «Параметры разработчика» .
  • В окне параметров разработчика прокрутите вниз, чтобы найти и включить отладку по USB .

Более подробную информацию об этом процессе можно найти на сайте Google для разработчиков Android .

Если вы столкнулись с ошибкой сборки, связанной с лицензиями ( Failed to install the following Android SDK packages as some licences have not been accepted ), вы можете использовать следующие команды для просмотра и принятия этих лицензий:

cd <path to Android SDK>

tools/bin/sdkmanager --licenses

Часто задаваемые вопросы