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

1. Введение

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

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

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

API Depth и Raw Depth поддерживаются только на ограниченном количестве устройств с поддержкой ARCore . API Depth доступен только на Android.

Что вы построите

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

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

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

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

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

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

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

  • Устройство с поддержкой ARCore и включенной отладкой по USB, подключенное к вашей машине для разработки через USB-кабель. Это устройство также должно поддерживать 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-файл с исходными данными о глубине.
  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, опубликованный вместе с SDK ARCore.

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

5. Настройка API для работы с исходными данными о глубине (Часть 1)

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

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

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

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

Обновите его, чтобы приложение работало только на устройствах, поддерживающих 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;
}

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

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

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

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.

}

Этот класс используется для преобразования изображений глубины в облака точек. Облака точек представляют геометрию сцены в виде списка точек, каждая из которых имеет 3D-координаты (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 . Этот метод принимает информацию из 3D-облака точек и проецирует её обратно в поле зрения камеры, чтобы её можно было отобразить на экране.

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

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

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

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

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

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

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

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

src/main/assets/shaders/depth_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. Анализ трехмерных облаков точек (Часть 3)

Анализ данных о глубине возможен после подтверждения их наличия в сеансе дополненной реальности. Важным инструментом для анализа глубины является значение достоверности для каждого пикселя. Используйте значения достоверности для анализа 3D-облаков точек.

Аннулировать пиксели с низкой степенью достоверности

Вы получили значение достоверности для каждого пикселя глубины и сохранили его вместе с каждой точкой в 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/rawdepth/DepthData.java

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

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

Фильтрация по расстоянию

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

Сравните трехмерные точки и плоскости.

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

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

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

Этот метод можно добавить в класс 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/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;

Добавьте в начало файла класс 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

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

При первом запуске приложения на устройстве вам будет предложено разрешить приложению использовать камеру вашего устройства. Для дальнейшего использования функций дополненной реальности необходимо разрешить доступ.

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

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

9. Поздравляем!

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

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

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