Độ sâu thô của ARCore

1. Giới thiệu

ARCore là một nền tảng để tạo các ứng dụng Thực tế tăng cường (AR) trên thiết bị di động. ARCore Depth API của Google cung cấp quyền truy cập vào hình ảnh độ sâu cho mỗi khung hình trong phiên ARCore. Mỗi pixel trong hình ảnh độ sâu cung cấp một phép đo khoảng cách từ camera đến môi trường.

Raw Depth API cung cấp hình ảnh độ sâu không được truyền qua các thao tác lọc không gian màn hình được thiết kế để làm mịn và nội suy kết quả. Những giá trị này chính xác hơn về mặt hình học nhưng có thể chứa dữ liệu bị thiếu và ít phù hợp với hình ảnh camera được liên kết.

Lớp học lập trình này minh hoạ cách sử dụng Raw Depth API để phân tích hình học 3D của cảnh. Bạn sẽ tạo một ứng dụng đơn giản có hỗ trợ thực tế tăng cường, sử dụng dữ liệu thô về độ sâu để phát hiện và trực quan hoá hình học của thế giới.

API Độ sâu và Độ sâu thô chỉ được hỗ trợ trên một số thiết bị có hỗ trợ ARCore. Depth API chỉ có trên Android.

Sản phẩm bạn sẽ tạo ra

Trong lớp học lập trình này, bạn sẽ tạo một ứng dụng sử dụng hình ảnh độ sâu thô cho mỗi khung hình để phân tích hình học của thế giới xung quanh bạn. Ứng dụng này sẽ:

  1. Kiểm tra xem thiết bị mục tiêu có hỗ trợ Độ sâu hay không.
  2. Truy xuất hình ảnh độ sâu thô cho mỗi khung hình camera.
  3. Chiếu lại hình ảnh độ sâu thô thành các điểm 3D và lọc các điểm đó dựa trên độ tin cậy và hình học.
  4. Sử dụng đám mây điểm độ sâu thô để phân đoạn các đối tượng 3D mà bạn quan tâm.

Xem trước những gì bạn sẽ tạo.

Lưu ý: Nếu bạn gặp vấn đề trong quá trình thực hiện, hãy chuyển đến phần cuối cùng để xem một số mẹo khắc phục sự cố.

2. Điều kiện tiên quyết

Bạn sẽ cần phần cứng và phần mềm cụ thể để hoàn tất lớp học lập trình này.

Yêu cầu về phần cứng

  • Một thiết bị có hỗ trợ ARCore đã bật tính năng gỡ lỗi qua USB, được kết nối với máy phát triển qua cáp USB. Thiết bị này cũng phải hỗ trợ Depth API.

Yêu cầu về phần mềm

3. Thiết lập

Thiết lập máy phát triển

Kết nối thiết bị ARCore với máy tính bằng cáp USB. Đảm bảo rằng thiết bị của bạn cho phép gỡ lỗi qua USB. Mở một cửa sổ dòng lệnh rồi chạy adb devices, như minh hoạ dưới đây:

adb devices

List of devices attached
<DEVICE_SERIAL_NUMBER>    device

<DEVICE_SERIAL_NUMBER> sẽ là một chuỗi dành riêng cho thiết bị của bạn. Đảm bảo rằng bạn thấy chỉ một thiết bị trước khi tiếp tục.

Tải xuống và cài đặt mã

Bạn có thể sao chép kho lưu trữ:

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

Hoặc tải tệp ZIP xuống rồi giải nén:

Hãy làm theo các bước sau để bắt đầu làm việc với mã.

  1. Khởi chạy Android Studio rồi chọn Mở một dự án Android Studio hiện có.
  2. Chuyển đến thư mục trên máy tính nơi bạn lưu trữ tệp ZIP Độ sâu thô.
  3. Nhấp đúp vào thư mục arcore_rawdepthapi_codelab.

Thư mục arcore_rawdepthapi_codelab là một dự án Gradle duy nhất có nhiều mô-đun. Nếu ngăn Dự án ở trên cùng bên trái của Android Studio chưa xuất hiện trong ngăn Dự án, hãy nhấp vào Projects (Dự án) trong trình đơn thả xuống.

Kết quả sẽ có dạng như sau:

Dự án này chứa các mô-đun sau:

  • part0_work: Ứng dụng khởi đầu. Bạn nên chỉnh sửa mô-đun này khi thực hiện lớp học lập trình này. Tất cả các phần khác đều chứa mã tham chiếu.
  • part1: Mã tham chiếu cho biết nội dung chỉnh sửa của bạn sẽ trông như thế nào khi bạn hoàn thành Phần 1.
  • part2: Mã tham chiếu khi bạn hoàn tất Phần 2.
  • part3_completed: Mã tham chiếu khi bạn hoàn thành Phần 3, tức là phần cuối của lớp học lập trình.

Bạn sẽ làm việc trong mô-đun part0_work. Ngoài ra, còn có các giải pháp hoàn chỉnh cho từng phần của lớp học lập trình. Mỗi mô-đun là một ứng dụng có thể tạo.

4. Chạy ứng dụng khởi đầu

Hãy làm theo các bước sau để chạy ứng dụng khởi đầu Raw Depth (Độ sâu thô).

  1. Chuyển đến Run > Run... (Chạy > Chạy...) > "part0_work".
  2. Trong hộp thoại Select Deployment Target (Chọn mục tiêu triển khai), hãy chọn thiết bị của bạn trong danh sách Connected Devices (Thiết bị đã kết nối) rồi nhấp vào OK.

Android Studio sẽ tạo ứng dụng ban đầu và chạy ứng dụng đó trên thiết bị của bạn.

Trong lần đầu chạy ứng dụng, ứng dụng sẽ yêu cầu quyền CAMERA. Nhấn vào Cho phép để tiếp tục.

Hiện tại, ứng dụng không làm gì cả.Đây là ứng dụng thực tế tăng cường cơ bản nhất, cho thấy chế độ xem camera của cảnh nhưng không làm gì khác.Mã hiện có tương tự như mẫu Hello AR được xuất bản cùng với ARCore SDK.

Tiếp theo, bạn sẽ dùng Raw Depth API để truy xuất hình học của cảnh xung quanh.

5. Thiết lập Raw Depth API (Phần 1)

Đảm bảo thiết bị mục tiêu hỗ trợ tính năng Độ sâu

Không phải thiết bị nào hỗ trợ ARCore cũng chạy được Depth API. Đảm bảo rằng thiết bị mục tiêu hỗ trợ Độ sâu trước khi thêm chức năng vào ứng dụng của bạn trong hàm onResume() của RawDepthCodelabActivity.java, nơi một Phiên mới được tạo.

Tìm mã hiện có:

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

Cập nhật để đảm bảo rằng ứng dụng chỉ chạy trên những thiết bị có thể hỗ trợ 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.";
}

Bật chế độ Độ sâu thô

Raw Depth API cung cấp một hình ảnh độ sâu chưa được làm mịn và một hình ảnh độ tin cậy tương ứng chứa độ tin cậy về độ sâu cho từng pixel trong hình ảnh độ sâu thô. Bật Độ sâu thô bằng cách cập nhật mã sau trong câu lệnh try-catch mà bạn vừa sửa đổi.

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

Giờ đây, phiên thực tế tăng cường đã được định cấu hình phù hợp và ứng dụng có thể sử dụng các tính năng dựa trên độ sâu.

Gọi Depth API

Tiếp theo, hãy gọi Depth API để truy xuất hình ảnh độ sâu cho từng khung hình. Đóng gói dữ liệu độ sâu vào một lớp mới bằng cách tạo một tệp mới. Nhấp chuột phải vào thư mục rawdepth rồi chọn New > Java Class. Thao tác này sẽ tạo một tệp trống. Thêm nội dung sau vào lớp này:

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.

}

Lớp này dùng để chuyển đổi hình ảnh độ sâu thành đám mây điểm. Đám mây điểm biểu thị hình học cảnh bằng một danh sách các điểm, mỗi điểm có một toạ độ 3D (x, y, z) và một giá trị độ tin cậy trong khoảng từ 0 đến 1.

Thêm các lệnh gọi để điền sẵn các giá trị này bằng Raw Depth API bằng cách thêm một phương thức create() ở cuối lớp. Phương thức này truy vấn độ sâu và hình ảnh độ tin cậy mới nhất, lưu trữ đám mây điểm thu được. Hình ảnh độ sâu và độ tin cậy sẽ có dữ liệu khớp.

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

Vào thời điểm này, mã cũng lưu trữ tâm máy ảnh để thông tin về độ sâu có thể được chuyển đổi thành toạ độ thế giới bằng cách gọi phương thức trợ giúp convertRawDepthImagesTo3dPointBuffer(). Phương thức trợ giúp này lấy từng pixel trong hình ảnh độ sâu và sử dụng các thông số nội tại của camera để chiếu ngược độ sâu thành một điểm 3D so với camera. Sau đó, neo camera được dùng để chuyển đổi vị trí của điểm thành toạ độ thế giới. Mỗi pixel hiện có sẽ được chuyển đổi thành một điểm 3D (tính bằng mét) và được lưu trữ cùng với độ tin cậy của điểm đó.

Thêm phương thức trợ giúp sau vào 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;
    }

Nhận dữ liệu Độ sâu thô mới nhất cho mỗi khung hình

Sửa đổi ứng dụng để truy xuất thông tin về độ sâu và căn chỉnh thông tin đó theo toạ độ thế giới cho từng tư thế.

Trong RawDepthCodelabActivity.java, trong phương thức onDrawFrame(), hãy tìm các dòng hiện có:

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

Thêm các dòng sau ngay bên dưới:

// 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. Kết xuất dữ liệu độ sâu (Phần 2)

Giờ đây, bạn đã có một đám mây điểm độ sâu để thử nghiệm, đã đến lúc xem dữ liệu trông như thế nào khi được kết xuất trên màn hình.

Thêm một trình kết xuất để trực quan hoá các điểm độ sâu

Thêm một trình kết xuất đồ hoạ để trực quan hoá các điểm độ sâu.

Trước tiên, hãy thêm một lớp mới để chứa logic kết xuất. Lớp này thực hiện các thao tác OpenGL để khởi chạy chương trình đổ bóng nhằm trực quan hoá đám mây điểm độ sâu.

Thêm lớp DepthRenderer

  1. Nhấp chuột phải vào thư mục nguồn rendering
  2. Chọn New > Java Class.
  3. Đặt tên cho lớp là DepthRenderer.

Điền mã sau vào lớp này:

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

Kết xuất dữ liệu độ sâu

Tiếp theo, hãy cung cấp nguồn cho các chương trình đổ bóng kết xuất. Thêm phương thức update() sau đây ở cuối lớp DepthRenderer. Phương thức này lấy thông tin chiều sâu mới nhất làm dữ liệu đầu vào và sao chép dữ liệu đám mây điểm vào 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");
    }

Vẽ dữ liệu mới nhất lên màn hình bằng cách thêm phương thức draw() vào cuối lớp DepthRenderer. Phương thức này lấy thông tin đám mây điểm 3D và chiếu thông tin đó trở lại chế độ xem camera để có thể hiển thị trên màn hình.

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

Bạn có thể đặt kích thước điểm thành nhiều kích thước (tính bằng pixel) bằng cách sử dụng biến pointSizeUniform. pointSizeUniform được đặt thành 5 pixel trong ứng dụng mẫu.

Thêm chương trình đổ bóng mới

Có nhiều cách để xem độ sâu và hiển thị dữ liệu độ sâu trong ứng dụng của bạn. Ở đây, bạn sẽ thêm một số chương trình đổ bóng và tạo một hình ảnh trực quan đơn giản về việc ánh xạ màu.

Thêm chương trình đổ bóng .vert.frag mới vào thư mục src/main/assets/shaders/.

Thêm chương trình đổ bóng .vert mới

Trong Android Studio:

  1. Nhấp chuột phải vào thư mục chương trình đổ bóng
  2. Chọn New (Mới) -> File (Tệp)
  3. Đặt tên cho tệp là depth_point_cloud.vert
  4. Đặt tệp này làm tệp văn bản.

Trong tệp .vert mới, hãy thêm mã sau:

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

Chương trình đổ bóng này sử dụng bảng màu Turbo để cải thiện khả năng trực quan hoá. Thao tác này thực hiện các bước sau:

  1. Truy xuất độ cao của từng điểm (trục y trong toạ độ thế giới).
  2. Tính toán màu sắc liên kết với độ cao đó (đỏ=thấp, xanh dương=cao).
  3. Tính toán vị trí trên màn hình của từng điểm.
  4. Đặt kích thước (tính bằng pixel) cho từng điểm, như được xác định trong phương thức DepthRenderer.update().

Tạo một chương trình đổ bóng mảnh trong cùng thư mục và đặt tên là depth_point_cloud.frag, lặp lại các bước tương tự trong phần này.

Sau đó, hãy thêm mã sau vào tệp mới này để kết xuất từng điểm dưới dạng một đỉnh duy nhất có màu đồng nhất, như được xác định trong chương trình đổ bóng đỉnh.

src/main/assets/shaders/depth_point_cloud.frag

precision mediump float;
varying vec4 v_Color;

void main() {
    gl_FragColor = v_Color;
}

Để áp dụng quá trình kết xuất này, hãy thêm các lệnh gọi vào lớp DepthRenderer bên trong RawDepthCodelabActivity.

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

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

Ở đầu lớp, hãy thêm một thành viên riêng tư bên cạnh backgroundRenderer.

private final DepthRenderer depthRenderer = new DepthRenderer();

Bạn cần khởi chạy depthRenderer bên trong RawDepthCodelabActivity.onSurfaceCreated(), giống như backgroundRenderer hiện có.

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

Thêm mã sau vào cuối khối try-catch bên trong onDrawFrame để hiện độ sâu mới nhất cho khung hình hiện tại.

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

Với những thay đổi này, ứng dụng hiện sẽ tạo thành công và hiển thị đám mây điểm độ sâu.

Ví dụ về hình ảnh trực quan của đám mây điểm độ sâu thô

  • Mỗi mẫu điểm được tô màu theo độ sâu.
  • Các điểm màu đỏ ở gần, các điểm màu xanh lục/xanh dương ở xa hơn
  • Bạn có thể thấy một số dữ liệu bị thiếu hoặc "lỗ hổng" ở những khu vực không có đủ đặc điểm hình ảnh, chẳng hạn như tường hoặc trần nhà màu trắng.
  • Bạn có thể điều chỉnh kích thước điểm được kết xuất bằng cách điều chỉnh đường GLES20.glUniform1f(pointSizeUniform, 5.0f); bên trong DepthRenderer.draw(). Bên trái là các kích thước điểm 5 và 10.

7. Phân tích đám mây điểm 3D (Phần 3)

Bạn có thể phân tích dữ liệu về độ sâu sau khi xác minh rằng dữ liệu đó có trong một phiên thực tế tăng cường. Một công cụ quan trọng để phân tích độ sâu là giá trị độ tin cậy cho từng pixel. Sử dụng giá trị độ tin cậy để phân tích đám mây điểm 3D.

Huỷ các pixel có độ tin cậy thấp

Bạn đã truy xuất giá trị độ tin cậy cho từng pixel độ sâu và lưu giá trị đó cùng với từng điểm bên trong DepthData, nhưng bạn chưa sử dụng giá trị này.

Giá trị của confidenceNormalized nằm trong khoảng từ 0 đến 1, trong đó 0 cho biết độ tin cậy thấp và 1 cho biết độ tin cậy cao. Sửa đổi phương thức convertRawDepthImagesTo3dPointBuffer() trong lớp DepthData để tránh lưu những pixel có độ tin cậy quá thấp đến mức không hữu ích.

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

Hãy thử các ngưỡng khác nhau cho mức độ tin cậy để xem có bao nhiêu điểm độ sâu được giữ lại ở mỗi cấp độ.

Độ tin cậy >= 0,1

Độ tin cậy >= 0,3

Độ tin cậy >= 0,5

Độ tin cậy >= 0,7

Độ tin cậy >= 0,9

Lọc các pixel theo khoảng cách

Bạn cũng có thể lọc các pixel độ sâu theo khoảng cách. Các bước tiếp theo này liên quan đến hình học gần camera. Để tối ưu hoá hiệu suất, bạn có thể bỏ qua những điểm quá xa.

Cập nhật mã kiểm tra độ tin cậy mà bạn vừa thêm bằng mã sau:

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

Giờ đây, bạn sẽ chỉ thấy những điểm có độ tin cậy cao và gần.

Lọc theo khoảng cách

Giới hạn đám mây điểm trong phạm vi 1,5 mét tính từ camera.

So sánh các điểm và mặt phẳng 3D

Bạn có thể so sánh các điểm và mặt phẳng 3D hình học rồi dùng chúng để lọc lẫn nhau, chẳng hạn như xoá các điểm gần với mặt phẳng thực tế tăng cường được quan sát.

Bước này sẽ chỉ để lại các điểm "không phẳng" có xu hướng biểu thị các bề mặt trên các đối tượng trong môi trường. Thêm phương thức filterUsingPlanes() vào cuối lớp DepthData. Phương thức này lặp lại các điểm hiện có, kiểm tra từng điểm với từng mặt phẳng và vô hiệu hoá mọi điểm quá gần với một mặt phẳng AR, để lại các vùng không phẳng làm nổi bật các đối tượng trong cảnh.

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

Bạn có thể thêm phương thức này vào RawDepthCodelabActivity trong phương thức 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);

Khi chạy lớp học lập trình này, một nhóm nhỏ các điểm sẽ được kết xuất. Các điểm này biểu thị các đối tượng trong cảnh, đồng thời bỏ qua các bề mặt phẳng mà các đối tượng nằm trên đó. Bạn có thể sử dụng những dữ liệu này để ước tính kích thước và vị trí của các đối tượng bằng cách nhóm các điểm lại với nhau.

Tách trà

Micrô

Tai nghe

Gối

Điểm cụm

Lớp học lập trình này chứa một thuật toán phân cụm đám mây điểm rất đơn giản. Cập nhật lớp học lập trình để nhóm các đám mây điểm đã truy xuất thành các cụm được xác định bằng các hộp giới hạn theo trục.

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;

Thêm một BoxRenderer vào lớp này ở đầu tệp, cùng với các trình kết xuất khác.

private final BoxRenderer boxRenderer = new BoxRenderer();

Trong phương thức onSurfaceCreated(), hãy thêm nội dung sau cùng với các trình kết xuất khác:

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

Cuối cùng, hãy thêm các dòng sau vào onDrawFrame() bên trong RawDepthCodelabActivity để nhóm các đám mây điểm đã truy xuất thành các cụm và hiển thị kết quả dưới dạng các hộp giới hạn được căn chỉnh theo trục.

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

Tách trà

Micrô

Tai nghe

Gối

Giờ đây, bạn có thể truy xuất Độ sâu thô thông qua một phiên ARCore, chuyển đổi thông tin về độ sâu thành đám mây điểm 3D và thực hiện các thao tác lọc và kết xuất cơ bản trên những điểm đó.

8. Xây dựng – Chạy – Kiểm thử

Tạo, chạy và kiểm thử ứng dụng.

Tạo và chạy ứng dụng

Hãy làm theo các bước sau để tạo và chạy ứng dụng:

  1. Cắm thiết bị hỗ trợ ARCore qua USB.
  2. Chạy dự án bằng nút ► trên thanh trình đơn.
  3. Chờ ứng dụng tạo bản dựng và triển khai trên thiết bị của bạn.

Lần đầu tiên cố gắng triển khai ứng dụng cho thiết bị, bạn sẽ cần phải

Cho phép gỡ lỗi qua USB

trên thiết bị. Chọn OK để tiếp tục.

Lần đầu tiên chạy ứng dụng trên thiết bị, bạn sẽ được hỏi xem ứng dụng có quyền sử dụng camera trên thiết bị hay không. Bạn phải cho phép truy cập để tiếp tục sử dụng chức năng thực tế tăng cường.

Kiểm thử ứng dụng

Khi chạy ứng dụng, bạn có thể kiểm thử hành vi cơ bản của ứng dụng bằng cách cầm thiết bị, di chuyển xung quanh không gian và quét một khu vực từ từ. Cố gắng thu thập dữ liệu trong ít nhất 10 giây và quét khu vực từ nhiều hướng trước khi chuyển sang bước tiếp theo.

9. Xin chúc mừng

Xin chúc mừng! Bạn đã tạo và chạy thành công ứng dụng Thực tế tăng cường dựa trên độ sâu đầu tiên bằng cách sử dụng ARCore Raw Depth API của Google. Chúng tôi rất háo hức chờ đón những thành quả của bạn!

10. Khắc phục sự cố

Thiết lập thiết bị Android cho hoạt động phát triển

  1. Kết nối thiết bị với máy phát triển bằng cáp USB. Nếu phát triển bằng Windows, bạn có thể cần cài đặt trình điều khiển USB thích hợp cho thiết bị của mình.
  2. Thực hiện các bước sau để bật tính năng Gỡ lỗi qua USB trong cửa sổ Tùy chọn cho nhà phát triển:
  • Mở ứng dụng Cài đặt.
  • Nếu thiết bị của bạn sử dụng Android phiên bản 8.0 trở lên, hãy chọn Hệ thống.
  • Cuộn xuống dưới cùng rồi chọn Giới thiệu về điện thoại.
  • Di chuyển xuống dưới cùng rồi nhấn 7 lần vào Số bản dựng.
  • Quay lại màn hình trước, cuộn xuống dưới cùng rồi nhấn vào Tùy chọn cho nhà phát triển.
  • Trong cửa sổ Tùy chọn cho nhà phát triển, hãy cuộn xuống để tìm và bật tính năng Gỡ lỗi qua USB.

Bạn có thể tìm thêm thông tin chi tiết về quy trình này trên trang web dành cho nhà phát triển Android của Google.

Nếu gặp phải lỗi bản dựng liên quan đến giấy phép (Failed to install the following Android SDK packages as some licences have not been accepted), bạn có thể dùng các lệnh sau để xem xét và chấp nhận những giấy phép này:

cd <path to Android SDK>

tools/bin/sdkmanager --licenses

Câu hỏi thường gặp