Độ 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. API Độ sâu ARCore của Google cho phép truy cập vào hình ảnh chiều sâu cho mỗi khung hình trong một phiên ARCore. Mỗi pixel trong hình ảnh chiều sâu cung cấp số đo khoảng cách từ máy ảnh tới môi trường.

API Chiều sâu thô cung cấp hình ảnh chiều 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ượt và nội suy kết quả. Các 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 được căn chỉnh hơn cho phù hợp với hình ảnh camera được liên kết.

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

Các API depth và Raw depth chỉ được hỗ trợ trên một số thiết bị hỗ trợ ARCore. Depth API (API Chiều sâu) 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ẽ xây dựng một ứng dụng sử dụng hình ảnh có chiều sâu thô cho mỗi khung hình để phân tích hình học thế giới xung quanh. Ứng dụng này sẽ:

  1. Kiểm tra xem thiết bị mục tiêu có hỗ trợ tính năng Chiều sâu hay không.
  2. Truy xuất hình ảnh chiều sâu thô cho từng khung hình máy ảnh.
  3. Chiếu lại hình ảnh chiều sâu thô vào điểm 3D và lọc những đ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 quan tâm.

Xem trước ẩn danh thành phần bạn sẽ tạo ra.

Lưu ý: Nếu bạn gặp sự cố trong quá trình này, hãy chuyển đến phần cuối để biết một số mẹo khắc phục sự cố.

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

Bạn cần có 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ị hỗ trợ ARCore đã bật tính năng gỡ lỗi qua USB. Thiết bị này được kết nối với máy phát triển của bạn qua cáp USB. Thiết bị này cũng phải hỗ trợ depth API (API Độ sâu).

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 của bạn qua cáp USB. Đảm bảo rằng thiết bị của bạn cho phép gỡ lỗi qua USB. Mở 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. Hãy đảm bảo bạn nhìn thấy chính xác 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:

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

  1. Chạy Android Studio rồi chọn Open an existing Android Studio project (Mở một dự án Android Studio hiện có).
  2. Chuyển đến thư mục cục bộ nơi bạn lưu trữ tệp ZIP Raw depth.
  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 Project (Dự án) ở trên cùng bên trái của Android Studio chưa hiển thị trong ngăn Project (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. Tất cả các phần khác đều chứa mã tham chiếu.
  • part1: Mã tham chiếu thể hiện nội dung chỉnh sửa của bạn khi hoàn thành Phần 1.
  • part2: Mã tham chiếu khi bạn hoàn thành Phần 2.
  • part3_completed: Mã tham chiếu khi bạn hoàn thành Phần 3, tức là kết thúc 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 này. Mỗi mô-đun là một ứng dụng có thể xây dựng.

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

Làm theo các bước sau để chạy ứng dụng khởi đầu Độ sâu thô.

  1. Chuyển đến Run > (Chạy >) Chạy... &gt; "part0_work".
  2. Trong hộp thoại Select Deployment Target (Chọn đối tượng 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 sẽ yêu cầu quyền sử dụng CAMERA. Hãy nhấn vào Cho phép để tiếp tục.

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

Tiếp theo, bạn sẽ sử dụng API Chiều sâu thô để truy xuất hình dạng của cảnh xung quanh.

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

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

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

Tìm mã hiện có:

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

Hãy cập nhật API này để đảm bảo ứng dụng chỉ chạy trên các 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 Độ sâu thô

API Chiều sâu thô cung cấp hình ảnh chiều sâu chưa được làm mịn và hình ảnh tin cậy tương ứng có độ tin cậy về độ tin cậy cho mỗi pixel trong hình ảnh chiều 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ể dùng các tính năng theo chiều sâu.

Gọi depth API (API Chiều sâu)

Tiếp theo, hãy gọi depth API (API Độ sâu) để truy xuất hình ảnh chiều sâu cho từng khung hình. Đóng gói dữ liệu chiề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. Hãy thêm nội dung sau đây 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 chiều sâu thành đám mây điểm (pointcloud). Đám mây điểm biểu thị hình học cảnh với một danh sách các điểm mà mỗi điểm có một toạ độ 3D (x, y, z) và giá trị tin cậy trong phạm vi từ 0 đến 1.

Thêm lệnh gọi để điền sẵn các giá trị này thông qua API Chiều sâu thô bằng cách thêm phương thức create() ở cuối lớp. Phương thức này truy vấn hình ảnh chiều sâu và độ tin cậy mới nhất, lưu trữ Pointcloud thu được. Hình ảnh chiều sâu và độ tin cậy sẽ có dữ liệu trùng 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()

Tại thời điểm này, đoạn mã này cũng lưu trữ điểm neo của máy ảnh để thông tin về độ sâu có thể chuyển đổi thành toạ độ tổng thể 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 điểm ảnh trong hình ảnh chiều sâu và sử dụng hàm nội tại của máy ảnh để bỏ chiếu chiều sâu vào một điểm 3D so với máy ảnh. Sau đó, neo máy ảnh được sử dụng để chuyển đổi vị trí của điểm thành toạ độ thế giới. Mỗi pixel tồn tại được chuyển đổi sang một điểm 3D (tính bằng đơn vị mét) và được lưu trữ cùng với độ tin cậy của nó.

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 mới nhất về Chiều sâu thô cho từng 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 mỗi 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 về độ sâu (Phần 2)

Giờ đây, khi bạn đã có một đám mây điểm sâu để khám phá, đã đến lúc xem dữ liệu hiển thị trên màn hình.

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

Thêm trình kết xuất đồ hoạ để trực quan hoá các điểm chiều 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á depth Pointcloud.

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 này là DepthRenderer.

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

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 về độ sâu

Tiếp theo, hãy cung cấp nguồn cho chương trình đổ bóng kết xuất hình ảnh. 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 chuyên sâu mới nhất làm dữ liệu đầu vào rồi sao chép dữ liệu Pointcloud 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 3D rồi chiếu thông tin đó trở lại chế độ xem camera để có thể kết xuất thông tin đó 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 các kích thước khác nhau (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 dữ liệu chiều sâu và hiện dữ liệu chiều sâu trong ứng dụng. Tại đây, bạn sẽ thêm một số chương trình đổ bóng và tạo hình ảnh trực quan hoá bản đồ màu đơn giản.

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) -> Tệp
  3. Đặt tên tệp này là depth_point_cloud.vert
  4. Đặt thành 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ản đồ màu Turbo để cải thiện hình ảnh. Công 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ột màu được liên kết với độ cao đó (đỏ=thấp, xanh dương=cao).
  3. Tính toán vị trí 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ư đã xác định trong phương thức DepthRenderer.update().

Tạo chương trình đổ bóng mảnh trong cùng thư mục rồi đặ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 đó, thêm mã sau vào tệp mới này để kết xuất mỗi điểm dưới dạng một đỉnh duy nhất có màu đồng nhất, như 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 quy 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();

depthRenderer cần được khởi chạy 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 chiều sâu mới nhất của khung hình hiện tại.

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

Với những thay đổi này, giờ đây ứng dụng sẽ tạo thành công và hiển thị depth Pointcloud.

Ví dụ về hình ảnh đám mây điểm theo chiều sâu thô

  • Mỗi mẫu điểm được tô màu theo độ sâu.
  • Các điểm màu đỏ ở gần nhau, các điểm xanh lục/xanh lam ở xa hơn
  • Một số dữ liệu bị thiếu hoặc "lỗ hổng" có thể nhìn thấy được ở 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 trắng.
  • Bạn có thể xem kích thước điểm được kết xuất bằng cách điều chỉnh dòng GLES20.glUniform1f(pointSizeUniform, 5.0f); bên trong DepthRenderer.draw(). Hiển thị ở 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 chiều sâu sau khi xác minh rằng dữ liệu đó tồn tại trong một phiên thực tế tăng cường. Một công cụ quan trọng để phân tích chiều sâu là giá trị độ tin cậy cho mỗi pixel. Sử dụng giá trị tin cậy để phân tích đám mây điểm 3D.

Vô hiệu hoá các pixel có độ tin cậy thấp

Bạn đã truy xuất giá trị tin cậy cho mỗi 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 biểu thị độ tin cậy thấp và 1 biểu thị độ tin cậy hoàn toàn. 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ên 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 của 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 pixel theo khoảng cách

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

Cập nhật mã kiểm tra độ tin cậy mà bạn vừa thêm như 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;
 }

Bây giờ, bạn sẽ chỉ thấy điểm đóng và độ tin cậy cao.

Lọc khoảng cách

Giới hạn Pointcloud trong phạm vi 1,5 mét so với máy ảnh.

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

Bạn có thể so sánh các điểm và mặt phẳng 3D dạng hình học, đồng thời sử dụng chúng để lọc lẫn nhau, chẳng hạn như loại bỏ các điểm gần mặt phẳng AR được quan sát.

Bước này sẽ chỉ để lại trạng thái "không phẳng" các điểm có xu hướng biểu thị 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 qua các điểm hiện có, kiểm tra từng điểm so với từng mặt phẳng và vô hiệu hoá mọi điểm quá gần với 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);

Việc chạy lớp học lập trình giờ đây sẽ dẫn đến một tập hợp con các điểm được kết xuất. Những điểm này đại diện cho 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 đố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

Các điểm cụm

Lớp học lập trình này chứa thuật toán phân cụm điểm đám mây 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 căn chỉnh 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 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();

Và bên trong phương thức onSurfaceCreated(), hãy thêm nội dung sau đây 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 nhiều cụm và kết xuất kết quả dưới dạng các hộp giới hạn 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. Build-Run-Test

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 đây để tạo và chạy ứng dụng:

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

Lần đầu tiên triển khai ứng dụng cho thiết bị của mình, bạn sẽ cần

Cho phép gỡ lỗi qua USB

trên thiết bị. Hãy 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 máy ảnh của 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 giữ thiết bị, di chuyển xung quanh và từ từ quét một khu vực. Cố gắng thu thập ít nhất 10 giây dữ liệu 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 chiều sâu đầu tiên bằng API ARCore Raw depth của Google. Chúng tôi rất háo hức chờ đón thành quả mà bạn phát triể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 phải 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ổ Tuỳ 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 Tuỳ chọn cho nhà phát triển.
  • Trong cửa sổ Tuỳ 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 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ể sử dụng các lệnh sau để xem xét và chấp nhận các giấy phép này:

cd <path to Android SDK>

tools/bin/sdkmanager --licenses

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