Nieprzetworzona głębia ARCore

1. Wprowadzenie

ARCore to platforma do tworzenia aplikacji rzeczywistości rozszerzonej (AR) na urządzenia mobilne. Interfejs Google ARCore Depth API zapewnia dostęp do obrazu głębi każdej klatki podczas sesji ARCore. Każdy piksel na obrazie głębi to pomiar odległości między aparatem a otoczeniem.

Interfejs Raw Depth API udostępnia obrazy głębi, które nie są przekazywane do operacji filtrowania przestrzeni ekranu w celu wygładzania i interpolowania wyników. Te wartości są bardziej dokładne geometrycznie, ale mogą zawierać brakujące dane i być mniej dopasowane do powiązanego zdjęcia z aparatu.

Dzięki temu ćwiczeniu w Codelabs dowiesz się, jak za pomocą interfejsu API Raw Depth przeprowadzić analizę geometrii 3D sceny. Tworzysz prostą aplikację obsługującą AR, która używa nieprzetworzonych danych o głębi do wykrywania i wizualizacji geometrii świata.

Interfejsy API Depth i Rw Depth są obsługiwane tylko na podzbiorze urządzeń obsługujących ARCore. Depth API jest dostępny tylko na Androidzie.

Co utworzysz

W ramach tego ćwiczenia w programie utworzysz aplikację, która w każdej klatce będzie używać nieprzetworzonych obrazów głębi, aby przeprowadzać analizę geometryczną otaczającego Cię świata. Ta aplikacja będzie:

  1. Sprawdź, czy urządzenie docelowe obsługuje głębokość.
  2. Pobierz obraz o nieprzetworzonej głębi dla każdej klatki aparatu.
  3. Ponownie rzutuj obrazy z nieprzetworzoną głębią w punkty 3D i filtruj te punkty na podstawie poziomu ufności i geometrii.
  4. Użyj nieprzetworzonej głębi chmury punktów do podziału interesujących obiektów 3D na segmenty.

Zobacz podgląd tego, co zbudujesz.

Uwaga: jeśli napotkasz jakieś problemy, przejdź do ostatniej sekcji, by uzyskać wskazówki dotyczące ich rozwiązywania.

2. Wymagania wstępne

Do wykonania tego ćwiczenia z programowania potrzebne jest odpowiedni sprzęt i oprogramowanie.

Wymagania sprzętowe

  • Urządzenie obsługujące ARCore z włączonym debugowaniem USB, podłączone kablem USB do maszyny wirtualnej. To urządzenie musi też obsługiwać interfejs Depth API.

Wymagania dotyczące oprogramowania

3. Skonfiguruj

Konfigurowanie maszyny do programowania

Podłącz urządzenie ARCore do komputera kablem USB. Upewnij się, że Twoje urządzenie umożliwia debugowanie USB. Otwórz terminal i uruchom polecenie adb devices, jak pokazano poniżej:

adb devices

List of devices attached
<DEVICE_SERIAL_NUMBER>    device

<DEVICE_SERIAL_NUMBER> to ciąg znaków unikalny dla Twojego urządzenia. Zanim przejdziesz dalej, upewnij się, że widzisz dokładnie jedno urządzenie.

Pobieranie i instalowanie kodu

Możesz sklonować repozytorium:

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

Możesz też pobrać plik ZIP i wyodrębnić go:

Aby rozpocząć pracę z kodem, wykonaj te czynności.

  1. Uruchom Android Studio i wybierz Otwórz istniejący projekt Android Studio.
  2. Przejdź do lokalnego katalogu, w którym został zapisany plik ZIP z nieprzetworzoną głębią obrazu.
  3. Kliknij dwukrotnie katalog arcore_rawdepthapi_codelab.

Katalog arcore_rawdepthapi_codelab to pojedynczy projekt Gradle z wieloma modułami. Jeśli panelu Projekt w lewym górnym rogu Android Studio nie jest jeszcze wyświetlany w panelu Projekt, kliknij Projekty w menu.

Wynik powinien wyglądać tak:

Ten projekt zawiera następujące moduły:

  • part0_work: aplikacja startowa. W tym module musisz wprowadzić zmiany w tym module. Pozostałe części zawierają kod referencyjny.
  • part1: kod referencyjny określający, jak powinny wyglądać wprowadzone zmiany po ukończeniu części 1.
  • part2: kod referencyjny po ukończeniu części 2.
  • part3_completed: użyj kodu referencyjnego po ukończeniu części 3, która jest końcem ćwiczeń z programowania.

Będziesz pracować w module part0_work. Dostępne są też kompletne rozwiązania dotyczące każdej części ćwiczenia. Każdy moduł jest aplikacją do kompilacji.

4. Uruchom aplikację startową

Aby uruchomić aplikację startową Raw Depth, wykonaj te czynności.

  1. Wybierz Uruchom > Uruchom... &gt; „part0_work”.
  2. W oknie Wybierz cel wdrożenia wybierz swoje urządzenie z listy Połączone urządzenia i kliknij OK.

Android Studio skompiluje początkową aplikację i uruchomi ją na Twoim urządzeniu.

Przy pierwszym uruchomieniu aplikacja poprosi o uprawnienia do CAMERA. Aby kontynuować, kliknij Zezwól.

Obecnie aplikacja nic nie robi.To najbardziej podstawowa aplikacja AR, która obrazuje scenę z kamery, ale nie robi nic więcej.Istniejący kod jest podobny do przykładowego kodu Hello AR opublikowanego za pomocą pakietu ARCore SDK.

Następnie użyj interfejsu API Raw Depth, aby pobrać geometrię sceny wokół Ciebie.

5. Konfigurowanie interfejsu Raw Depth API (część 1)

Upewnij się, że urządzenie docelowe obsługuje głębokość

Nie wszystkie urządzenia obsługujące ARCore obsługują interfejs Depth API. Przed dodaniem funkcji do aplikacji w funkcji onResume() w RawDepthCodelabActivity.java, gdzie jest tworzona nowa sesja, urządzenie docelowe musi obsługiwać Głębia.

Znajdź istniejący kod:

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

Zaktualizuj go, aby aplikacja działała tylko na urządzeniach, które obsługują interfejs 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.";
}

Włącz nieprzetworzoną głębię

Interfejs Raw Depth API zapewnia nieostry obraz głębi i odpowiedni obraz ufności, który pokazuje poziom ufności dla każdego piksela na obrazie w nieprzetworzonej głębi. Włącz nieprzetworzoną głębię, aktualizując poniższy kod pod zmodyfikowaną właśnie instrukcją 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;
}

Teraz sesja AR jest odpowiednio skonfigurowana, a aplikacja może używać funkcji opartych na głębi.

Wywoływanie interfejsu Depth API

Następnie wywołaj interfejs Depth API, aby pobrać obrazy głębi dla każdej klatki. Aby dołączyć dane głębi do nowej klasy, utwórz nowy plik. Kliknij prawym przyciskiem myszy folder rawdepth i wybierz New > Java Class. Spowoduje to utworzenie pustego pliku. Dodaj do tych zajęć te elementy:

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.

}

Ta klasa służy do konwertowania obrazów głębi na chmury punktowe. Chmury punktowe przedstawiają geometrię sceny z listą punktów, z których każdy ma współrzędne 3D (x, y, z) i wartość ufności z zakresu od 0 do 1.

Dodaj wywołania, aby wypełniać te wartości za pomocą interfejsu Raw Depth API. Aby to zrobić, dodaj metodę create() na dole klasy. Ta metoda wysyła zapytania do najnowszych obrazów głębi i ufności, zapisując powstałą w ten sposób chmurę punktów. Obrazy zawierające głębię i pewność będą mieć pasujące dane.

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

Kod przechowuje w tym czasie również element zakotwiczony kamery, dzięki czemu informacje o głębi mogą zostać przekształcone w współrzędne świata przez wywołanie metody pomocniczej convertRawDepthImagesTo3dPointBuffer(). Ta metoda pomocnicza obejmuje każdy piksel na obrazie głębi i wykorzystuje funkcje aparatu, aby odwzorować głębię w punkt 3D względem kamery. Następnie element zakotwiczony kamery jest używany do przekształcania położenia punktu na współrzędne świata. Każdy piksel jest konwertowany do punktu 3D (w jednostkach metra) i zapisywany wraz z jego poziomem ufności.

Dodaj do pliku DepthData.java tę metodę pomocniczą:

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

Uzyskaj najnowsze nieprzetworzone dane o głębi w przypadku każdej klatki

Zmodyfikuj aplikację, aby uzyskać informacje o głębi i dopasować je do współrzędnych świata dla każdej pozycji.

W metodzie RawDepthCodelabActivity.java w metodzie onDrawFrame() odszukaj istniejące wiersze:

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

Dodaj te wiersze tuż pod nim:

// 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. Renderowanie danych o głębi (część 2)

Skoro masz już do dyspozycji głęboką chmurę punktową, nadszedł czas, aby zobaczyć, jak dane wyglądają po wyświetleniu na ekranie.

Dodaj mechanizm renderowania, aby zwizualizować punkty głębi

Dodaj mechanizm renderowania, aby zwizualizować punkty głębi.

Najpierw dodaj nową klasę zawierającą logikę renderowania. Ta klasa wykonuje operacje OpenGL w celu inicjowania funkcji cieniowania w celu wizualizacji głębi chmury punktów.

Dodaj klasę DepthRenderer

  1. Kliknij prawym przyciskiem myszy katalog źródłowy rendering
  2. Wybierz New > Java Class.
  3. Nazwij zajęcia DepthRenderer.

Wypełnij te zajęcia tym kodem:

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

Renderuj dane o głębi

Następnie podaj źródło dla modułów do cieniowania renderowania. Na dole klasy DepthRenderer dodaj następującą metodę update(). Ta metoda pobiera najnowsze informacje o głębi i kopiuje dane chmury punktów do 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");
    }

Narysuj na ekranie najnowsze dane, dodając metodę draw() u dołu klasy DepthRenderer. Ta metoda pobiera informacje z chmury punktowej 3D i umieszcza je z powrotem w widoku kamery, aby mogły zostać wyświetlone na ekranie.

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

Możesz ustawić różne rozmiary w pikselach za pomocą zmiennej pointSizeUniform. Zasób pointSizeUniform jest ustawiony na 5 pikseli w przykładowej aplikacji.

Dodaj nowe cieniowanie

Istnieje wiele sposobów wyświetlania i wyświetlania danych dotyczących głębi w aplikacji. Dodasz tu kilka cieniowania i utworzysz prostą wizualizację mapowania kolorów.

Dodaj nowe cieniowanie .vert i .frag do katalogu src/main/assets/shaders/.

Dodaję nowy program do cieniowania plików .vert

W Android Studio:

  1. Kliknij prawym przyciskiem myszy katalog cieni
  2. Wybierz Nowy -> Plik
  3. Nazwij go depth_point_cloud.vert
  4. Ustaw go jako plik tekstowy.

W nowym pliku .vert dodaj ten kod:

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

Ten cieniowanie poprawia turbomapę kolorów, aby poprawić wizualizację. Wykonuje te czynności:

  1. Pobiera informacje o wysokości każdego punktu (oś Y we współrzędnych świata).
  2. Oblicza kolor powiązany z tą wysokością (czerwony=niska, niebieski=wysoka).
  3. Oblicza pozycję każdego punktu na ekranie.
  4. Ustawia rozmiar (w pikselach) każdego punktu, zgodnie z definicją w metodzie DepthRenderer.update().

Utwórz w tym samym katalogu program do cieniowania fragmentów i nadaj mu nazwę depth_point_cloud.frag, powtarzając te same czynności co w tej sekcji.

Następnie dodaj do tego nowego pliku poniższy kod, by renderować każdy punkt jako pojedynczy wierzchołek o jednolitym kolorze, zgodnie z definicją w cieniowaniu wierzchołków.

src/main/assets/shaders/depth_point_cloud.frag

precision mediump float;
varying vec4 v_Color;

void main() {
    gl_FragColor = v_Color;
}

Aby zastosować to renderowanie, dodaj wywołania do klasy DepthRenderer w elemencie RawDepthCodelabActivity.

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

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

U góry zajęć dodaj członka prywatnego obok zajęć backgroundRenderer.

private final DepthRenderer depthRenderer = new DepthRenderer();

Pole depthRenderer musi zostać zainicjowane w obrębie RawDepthCodelabActivity.onSurfaceCreated(), tak jak istniejący backgroundRenderer.

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

Aby pokazać najnowszą głębokość bieżącej klatki, dodaj ten kod na końcu bloku try-catch w onDrawFrame.

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

Po wprowadzeniu tych zmian aplikacja powinna się teraz pomyślnie kompilować i wyświetlać głęboką chmurę punktową.

Przykładowa wizualizacja chmury punktowej z nieprzetworzoną głębią

  • Każda próbka punktowa jest kolorowana głębokością.
  • Punkty czerwone znajdują się blisko siebie, a zielone i niebieskie są dalej
  • Brakujące dane lub luki będzie widoczny na obszarach z niewystarczającymi funkcjami zdjęcia, na przykład na białych ścianach lub suficie.
  • Możesz zmienić wielkość wyrenderowanego punktu, dostosowując linię GLES20.glUniform1f(pointSizeUniform, 5.0f); wewnątrz DepthRenderer.draw(). Po lewej stronie widać rozmiary punktowe 5 i 10.

7. Analizowanie chmur punktów w 3D (część 3)

Dane głębi możesz przeanalizować, gdy potwierdzisz, że istnieją w ramach sesji AR. Ważnym narzędziem do analizy głębi jest poziom ufności każdego piksela. Używaj wartości ufności do analizowania chmur punktów 3D.

Unieważniaj piksele o niskim poziomie ufności

Udało Ci się pobrać wartość ufności dla każdego piksela głębi i zapisać ją obok każdego punktu w obrębie DepthData, ale nie korzystano jeszcze z niej.

Wartości confidenceNormalized mieszczą się w zakresie od 0 do 1, gdzie 0 oznacza niski poziom ufności, a 1 oznacza pełną pewność. Zmodyfikuj metodę convertRawDepthImagesTo3dPointBuffer() w klasie DepthData, aby uniknąć zapisywania pikseli, których poziom ufności jest zbyt niski, aby być przydatny.

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

Wypróbuj różne progi poziomu ufności, aby zobaczyć, ile punktów głębi jest utrzymywane na poszczególnych poziomach.

Poziom ufności >= 0,1

Poziom ufności >= 0,3

Poziom ufności >= 0,5

Poziom ufności >= 0,7

Poziom ufności >= 0,9

Filtruj piksele według odległości

Możesz też filtrować piksele głębi według odległości. Następne czynności dotyczą geometrii w pobliżu aparatu. Aby zoptymalizować skuteczność, możesz zignorować zbyt odległe punkty.

Zaktualizuj dodany przed chwilą kod sprawdzania ufności, korzystając z tego kodu:

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

Teraz zobaczysz tylko punkty o wysokim stopniu pewności co do wiarygodności i zamknięcia.

Filtrowanie odległości

Ogranicza chmurę punktową do odległości nieprzekraczającej 1,5 metra od kamery.

Porównywanie punktów i samolotów w 3D

Możesz porównywać geometryczne punkty i płaszczyzny 3D oraz używać ich do filtrowania siebie, np. usuwania punktów znajdujących się blisko obserwowanych płaszczyzn AR.

Ten krok pozostawi tylko wymiar „inny niż planarny”. punkty, które mają tendencję do reprezentowania powierzchni na obiektach w otoczeniu. Dodaj metodę filterUsingPlanes() na końcu klasy DepthData. Ta metoda powtarza iterację istniejących punktów, sprawdza każdy z nich względem każdej płaszczyzny i unieważnia każdy punkt, który jest zbyt blisko płaszczyzny AR, pozostawiając obszary inne niż płaskie, które wyróżniają obiekty na scenie.

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

Tę metodę możesz dodać do metody RawDepthCodelabActivity w metodzie 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);

Uruchomienie ćwiczenia z programowania powoduje teraz wygenerowanie podzbioru punktów. Punkty te reprezentują obiekty w scenie, nie uwzględniając płaskich powierzchni, na których spoczywają. Na podstawie tych danych możesz oszacować rozmiar i pozycję obiektów przez pogrupowanie punktów.

Filiżanka herbaty

Mikrofon

Słuchawki

Poduszka

Punkty klastra

To ćwiczenie w Codelabs zawiera bardzo uproszczony algorytm grupowania chmur punktów. Zaktualizuj ćwiczenie w Codelabs, aby zgrupować pobrane chmury punktowe w klastry zdefiniowane przez ramki ograniczające wyrównane do osi.

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;

Dodaj do tej klasy obiekt BoxRenderer (wraz z innymi mechanizmami renderowania) na początku pliku.

private final BoxRenderer boxRenderer = new BoxRenderer();

W metodzie onSurfaceCreated() obok innych mechanizmów renderowania dodaj:

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

Na koniec dodaj następujące wiersze do obiektu onDrawFrame() w obrębie RawDepthCodelabActivity, aby pogrupować pobrane chmury punktowe w klastry i wyrenderować wyniki jako ramki ograniczające wyrównane do osi.

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

Filiżanka herbaty

Mikrofon

Słuchawki

Poduszka

Teraz możesz pobierać nieprzetworzoną głębię za pomocą sesji ARCore, konwertować informacje o głębi na chmury punktowe 3D oraz wykonywać podstawowe operacje filtrowania i renderowania na tych punktach.

8. Build-Run-Test

Tworzenie, uruchamianie i testowanie aplikacji.

Tworzenie i uruchamianie aplikacji

Aby skompilować i uruchomić aplikację, wykonaj te czynności:

  1. Podłącz urządzenie obsługujące ARCore przez USB.
  2. Uruchom projekt, naciskając przycisk ► na pasku menu.
  3. Poczekaj, aż aplikacja skompiluje się i wdroży na urządzeniu.

Przy pierwszej próbie wdrożenia aplikacji na urządzeniu

Zezwalaj na debugowanie USB

na urządzeniu. Aby kontynuować, kliknij OK.

Przy pierwszym uruchomieniu aplikacji na urządzeniu pojawi się pytanie, czy ma ona uprawnienia do korzystania z aparatu urządzenia. Aby dalej korzystać z funkcji AR, musisz zezwolić na dostęp.

Testowanie aplikacji

Po uruchomieniu aplikacji możesz przetestować jej podstawowe działanie, przytrzymując urządzenie, poruszając się po obszarze i powolnie skanując obszar. Postaraj się zebrać co najmniej 10 sekund danych i przeskanować obszar z kilku stron, zanim przejdziesz do kolejnego kroku.

9. Gratulacje

Gratulujemy! Udało Ci się utworzyć i uruchomić pierwszą aplikację opartą na głębi rzeczywistości i zastosowaną przez Google interfejs ARCore Raw Depth API. Nie możemy się doczekać, żeby zobaczyć, co stworzysz!

10. Rozwiązywanie problemów

Konfigurowanie urządzenia z Androidem na potrzeby programowania

  1. Podłącz urządzenie do programisty kablem USB. Jeśli programujesz w systemie Windows, konieczne może być zainstalowanie odpowiedniego sterownika USB dla Twojego urządzenia.
  2. Aby włączyć Debugowanie USB w oknie Opcje programisty, wykonaj te czynności:
  • Otwórz aplikację Ustawienia.
  • Jeśli masz urządzenie z Androidem w wersji 8.0 lub nowszej, kliknij System.
  • Przewiń w dół i wybierz Informacje o telefonie.
  • Przewiń w dół i kliknij Numer kompilacji 7 razy.
  • Wróć na poprzedni ekran, przewiń w dół i kliknij Opcje programisty.
  • W oknie Opcje programisty przewiń w dół, aby znaleźć i włączyć Debugowanie USB.

Bardziej szczegółowe informacje o tym procesie znajdziesz na stronie Google dla deweloperów aplikacji na Androida.

Jeśli napotkasz błąd kompilacji związany z licencjami (Failed to install the following Android SDK packages as some licences have not been accepted), możesz użyć tych poleceń, aby sprawdzić i zaakceptować te licencje:

cd <path to Android SDK>

tools/bin/sdkmanager --licenses

Najczęstsze pytania