ความลึกของข้อมูลดิบ ARCore

1. บทนำ

ARCore เป็นแพลตฟอร์มสำหรับสร้างแอป Augmented Reality (AR) ในอุปกรณ์เคลื่อนที่ ARCore Depth API ของ Google ให้สิทธิ์เข้าถึงรูปภาพความลึกแต่ละเฟรมในเซสชัน ARCore ภาพแต่ละพิกเซลในระดับลึกจะแสดงการวัดระยะทางจากกล้องไปยังสภาพแวดล้อม

Raw Depth API จะให้รูปภาพที่มีความลึกที่ไม่ผ่านการดำเนินการกรองพื้นที่หน้าจอซึ่งออกแบบมาให้ใช้งานได้อย่างลื่นไหลและสอดแทรกผลลัพธ์ ค่าเหล่านี้มีความถูกต้องทางเรขาคณิตมากกว่า แต่ก็อาจมีข้อมูลที่ขาดหายไปและไม่สอดคล้องกับรูปภาพจากกล้องที่เกี่ยวข้องน้อยลง

Codelab นี้แสดงวิธีใช้ Raw Depth API เพื่อทำการวิเคราะห์เรขาคณิต 3 มิติของฉาก คุณจะได้สร้างแอปที่เปิดใช้ AR แบบง่ายๆ โดยใช้ข้อมูลดิบที่มีความลึกในการตรวจจับและแสดงภาพเรขาคณิตของโลก

Depth และ Raw Depth API รองรับเฉพาะในอุปกรณ์ที่เปิดใช้ ARCore บางส่วนเท่านั้น Depth API ใช้งานได้ใน Android เท่านั้น

สิ่งที่คุณจะสร้าง

ใน Codelab นี้ คุณจะได้สร้างแอปที่ใช้ภาพความลึกแบบดิบสำหรับแต่ละเฟรมเพื่อทำการวิเคราะห์ทางเรขาคณิตของโลกรอบตัวคุณ แอปนี้จะ:

  1. ตรวจสอบว่าอุปกรณ์เป้าหมายรองรับความลึกหรือไม่
  2. ดึงรูปภาพความลึกที่เป็นข้อมูลดิบของเฟรมกล้องแต่ละเฟรม
  3. ฉายภาพความลึกดิบอีกครั้งเป็นจุด 3 มิติ และกรองจุดเหล่านั้นตามความเชื่อมั่นและเรขาคณิต
  4. ใช้ระบบคลาวด์ที่มีจุดความลึกแบบข้อมูลดิบเพื่อแบ่งกลุ่มวัตถุ 3 มิติที่สนใจ

ดูตัวอย่างสิ่งที่คุณจะสร้าง

หมายเหตุ: หากพบปัญหาระหว่างการใช้งาน ให้ข้ามไปยังส่วนสุดท้ายเพื่อดูเคล็ดลับในการแก้ปัญหาบางอย่าง

2. ข้อกำหนดเบื้องต้น

คุณจะต้องใช้ฮาร์ดแวร์และซอฟต์แวร์เฉพาะเพื่อทำงาน Codelab นี้ให้เสร็จสมบูรณ์

ข้อกำหนดเกี่ยวกับฮาร์ดแวร์

  • อุปกรณ์ที่รองรับ ARCore ที่เปิดใช้การแก้ไขข้อบกพร่องผ่าน USB และเชื่อมต่อกับเครื่องพัฒนาผ่านสาย USB อุปกรณ์นี้ต้องรองรับ Depth API ด้วย

ข้อกำหนดของซอฟต์แวร์

  • ARCore SDK 1.31.0 ขึ้นไป
  • เครื่องพัฒนาซอฟต์แวร์ที่ติดตั้ง Android Studio (เวอร์ชัน 4.0.1 ขึ้นไป)

3. ตั้งค่า

ตั้งค่าเครื่องสำหรับการพัฒนา

เชื่อมต่ออุปกรณ์ ARCore กับคอมพิวเตอร์ผ่านสาย USB ตรวจสอบว่าอุปกรณ์ของคุณอนุญาตให้แก้ไขข้อบกพร่อง USB เปิดเทอร์มินัลและเรียกใช้ adb devices ตามที่แสดงด้านล่าง

adb devices

List of devices attached
<DEVICE_SERIAL_NUMBER>    device

<DEVICE_SERIAL_NUMBER> จะเป็นสตริงเฉพาะสำหรับอุปกรณ์ของคุณ โปรดตรวจสอบว่าคุณเห็นอุปกรณ์รุ่นเดียวเท่านั้นก่อนดำเนินการต่อ

ดาวน์โหลดและติดตั้งโค้ด

คุณจะโคลนที่เก็บได้ด้วยวิธีการต่อไปนี้

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

หรือดาวน์โหลดไฟล์ ZIP และแตกข้อมูลออกมา

ทำตามขั้นตอนต่อไปนี้เพื่อเริ่มต้นใช้งานโค้ด

  1. เปิด Android Studio แล้วเลือกเปิดโปรเจ็กต์ Android Studio ที่มีอยู่
  2. ไปที่ไดเรกทอรีในเครื่องที่คุณจัดเก็บไฟล์ ZIP แบบความลึกเป็นไฟล์ข้อมูล RAW
  3. ดับเบิลคลิกไดเรกทอรี arcore_rawdepthapi_codelab

ไดเรกทอรี arcore_rawdepthapi_codelab เป็นโปรเจ็กต์ Gradle เดียวที่มีหลายโมดูล หากแผงโครงการที่ด้านซ้ายบนของ Android Studio ยังไม่ปรากฏในแผงโครงการ ให้คลิกโครงการจากเมนูแบบเลื่อนลง

ผลลัพธ์ที่ได้ควรมีลักษณะดังนี้

โครงการนี้มีโมดูลต่อไปนี้:

  • part0_work: แอปเริ่มต้น คุณควรแก้ไขโมดูลนี้เมื่อทํา Codelab นี้ ส่วนอื่นๆ ทั้งหมดจะมีรหัสอ้างอิง
  • part1: รหัสอ้างอิงแสดงลักษณะของการแก้ไขเมื่อคุณทำส่วนที่ 1 เสร็จ
  • part2: รหัสอ้างอิงเมื่อทำส่วนที่ 2 เสร็จ
  • part3_completed: ใส่รหัสอ้างอิงเมื่อคุณทำส่วนที่ 3 ซึ่งเป็นตอนท้ายของ Codelab

คุณจะได้ทำงานในโมดูล part0_work นอกจากนี้ยังมีโซลูชันที่สมบูรณ์สำหรับแต่ละส่วนของ Codelab แต่ละโมดูลคือแอปที่บิลด์ได้

4. เรียกใช้แอปเริ่มต้น

ทำตามขั้นตอนต่อไปนี้เพื่อเรียกใช้แอปเริ่มต้นสำหรับขอบเขตข้อมูลดิบ

  1. ไปที่เรียกใช้ > เรียกใช้... "part0_work"
  2. ในกล่องโต้ตอบเลือกเป้าหมายการทำให้ใช้งานได้ ให้เลือกอุปกรณ์ของคุณจากรายการอุปกรณ์ที่เชื่อมต่อ แล้วคลิกตกลง

Android Studio จะสร้างแอปเริ่มต้นและเรียกใช้บนอุปกรณ์ของคุณ

เมื่อเรียกใช้แอปเป็นครั้งแรก แอปจะขอสิทธิ์ใช้กล้อง แตะอนุญาตเพื่อดำเนินการต่อ

ขณะนี้แอปไม่ได้ดำเนินการใดๆ ซึ่งเป็นแอปพลิเคชัน AR ขั้นพื้นฐานที่สุดที่แสดงมุมมองกล้องของฉาก แต่ไม่ได้ดำเนินการใดๆ โค้ดที่มีอยู่จะคล้ายกับตัวอย่าง Hello AR ที่เผยแพร่ด้วย ARCore SDK

ถัดไป คุณจะใช้ API ความลึกแบบ Raw เพื่อดึงข้อมูลเรขาคณิตของฉากรอบตัวคุณ

5. ตั้งค่า Raw Depth API (ส่วนที่ 1)

ตรวจสอบว่าอุปกรณ์เป้าหมายรองรับความลึก

อุปกรณ์ที่รองรับ ARCore บางอย่างอาจเรียกใช้ Depth API ได้ ตรวจสอบว่าอุปกรณ์เป้าหมายรองรับความลึกก่อนที่จะเพิ่มฟังก์ชันลงในแอปภายในฟังก์ชัน onResume() ของ RawDepthCodelabActivity.java ที่มีการสร้างเซสชันใหม่

ค้นหารหัสที่มีอยู่

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

โปรดอัปเดตเพื่อให้แอปพลิเคชันทำงานบนอุปกรณ์ที่รองรับ Depth API ได้เท่านั้น

// Create the ARCore session.
session = new Session(/* context= */ this);
if (!session.isDepthModeSupported(Config.DepthMode.RAW_DEPTH_ONLY)) {
  message =
     "This device does not support the ARCore Raw Depth API. See" +
     "https://developers.google.com/ar/devices for 
     a list of devices that do.";
}

เปิดใช้ความลึกของไฟล์ข้อมูล RAW

Raw Depth API ให้รูปภาพความลึกที่ไม่เรียบเนียน และรูปภาพระดับความเชื่อมั่นที่สอดคล้องกันซึ่งมีความเชื่อมั่นด้านความลึกสำหรับแต่ละพิกเซลในรูปภาพที่มีความลึกแบบ RAW เปิดใช้ความลึกของไฟล์ข้อมูล RAW โดยการอัปเดตโค้ดต่อไปนี้ใต้ข้อความลองจับที่คุณเพิ่งแก้ไข

try {
  // ************ New code to add ***************
  // Enable raw depth estimation and auto focus mode while ARCore is running.
  Config config = session.getConfig();
  config.setDepthMode(Config.DepthMode.RAW_DEPTH_ONLY);
  config.setFocusMode(Config.FocusMode.AUTO);
  session.configure(config);
  // ************ End new code to add ***************
  session.resume();
} catch (CameraNotAvailableException e) {
  messageSnackbarHelper.showError(this, "Camera not available. Try restarting the app.");
  session = null;
  return;
}

ตอนนี้เซสชัน AR ได้รับการกำหนดค่าอย่างเหมาะสมแล้ว และแอปก็จะใช้ฟีเจอร์ที่อิงตามความลึกได้

เรียกใช้ Depth API

ถัดไป ให้เรียก Depth API เพื่อเรียกข้อมูลรูปภาพความลึกของแต่ละเฟรม สรุปข้อมูลความลึกไว้ในคลาสใหม่โดยการสร้างไฟล์ใหม่ คลิกขวาที่โฟลเดอร์ rawdepth แล้วเลือก New > Java Class การดำเนินการนี้จะสร้างไฟล์เปล่า เพิ่มข้อมูลต่อไปนี้ในชั้นเรียนนี้

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

package com.google.ar.core.codelab.rawdepth;

import android.media.Image;
import android.opengl.Matrix;

import com.google.ar.core.Anchor;
import com.google.ar.core.CameraIntrinsics;
import com.google.ar.core.Frame;
import com.google.ar.core.exceptions.NotYetAvailableException;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.ShortBuffer;

/**
 * Convert depth data from ARCore depth images to 3D pointclouds. Points are added by calling the
 * Raw Depth API, and reprojected into 3D space.
 */
public class DepthData {
    public static final int FLOATS_PER_POINT = 4; // X,Y,Z,confidence.

}

คลาสนี้ใช้เพื่อแปลงรูปภาพความลึกเป็น Pointcloud Pointcloud แสดงเรขาคณิตของฉากพร้อมด้วยรายการจุดที่แต่ละจุดมีพิกัด 3 มิติ (x, y, z) และค่าความเชื่อมั่นในช่วง 0 ถึง 1

เพิ่มการเรียกใช้เพื่อป้อนข้อมูลค่าเหล่านี้โดยใช้ Raw Depth API โดยเพิ่มเมธอด create() ที่ด้านล่างของคลาส วิธีนี้จะค้นหารูปภาพความลึกและความเชื่อมั่นล่าสุด ซึ่งจัดเก็บ Pointcloud ที่ได้ รูปภาพความลึกและความเชื่อมั่นจะมีข้อมูลที่ตรงกัน

public static FloatBuffer create(Frame frame, Anchor cameraPoseAnchor) {
    try {
        Image depthImage = frame.acquireRawDepthImage16Bits();
        Image confidenceImage = frame.acquireRawDepthConfidenceImage();

        // Retrieve the intrinsic camera parameters corresponding to the depth image to
        // transform 2D depth pixels into 3D points. See more information about the depth values
        // at
        // https://developers.google.com/ar/develop/java/depth/overview#understand-depth-values.

        final CameraIntrinsics intrinsics = frame.getCamera().getTextureIntrinsics();
        float[] modelMatrix = new float[16];
        cameraPoseAnchor.getPose().toMatrix(modelMatrix, 0);
        final FloatBuffer points = convertRawDepthImagesTo3dPointBuffer(
                depthImage, confidenceImage, intrinsics, modelMatrix);

        depthImage.close();
        confidenceImage.close();

        return points;
    } catch (NotYetAvailableException e) {
        // This normally means that depth data is not available yet.
        // This is normal, so you don't have to spam the logcat with this.
    }
    return null;
}

acquireCameraImage()

acquireDepthImage16Bits()

acquireRawDepthImage16Bits()

acquireRawDepthConfidenceImage()

โค้ดยังจัดเก็บจุดยึดของกล้องในตอนนี้ เพื่อให้สามารถเปลี่ยนข้อมูลความลึกเป็นพิกัดโลกได้โดยการเรียกใช้เมธอดช่วย convertRawDepthImagesTo3dPointBuffer() เมธอดตัวช่วยนี้จะนำแต่ละพิกเซลในรูปภาพลึกและใช้ภายในกล้องเพื่อยกเลิกการฉายภาพความลึกไปยังจุด 3 มิติที่สัมพันธ์กับกล้อง จากนั้นจะใช้จุดยึดกล้องเพื่อแปลงตำแหน่งของจุดให้เป็นพิกัดโลก แต่ละพิกเซลที่มีอยู่จะถูกแปลงเป็นจุด 3 มิติ (เป็นหน่วยเมตร) และจัดเก็บไว้ควบคู่กับความเชื่อมั่นของจุด 3 มิติ

เพิ่มเมธอด Helper ต่อไปนี้ไปยัง DepthData.java

/** Apply camera intrinsics to convert depth image into a 3D pointcloud. */
    private static FloatBuffer convertRawDepthImagesTo3dPointBuffer(
            Image depth, Image confidence, CameraIntrinsics cameraTextureIntrinsics, float[] modelMatrix) {
        // Java uses big endian so change the endianness to ensure
        // that the depth data is in the correct byte order.
        final Image.Plane depthImagePlane = depth.getPlanes()[0];
        ByteBuffer depthByteBufferOriginal = depthImagePlane.getBuffer();
        ByteBuffer depthByteBuffer = ByteBuffer.allocate(depthByteBufferOriginal.capacity());
        depthByteBuffer.order(ByteOrder.LITTLE_ENDIAN);
        while (depthByteBufferOriginal.hasRemaining()) {
            depthByteBuffer.put(depthByteBufferOriginal.get());
        }
        depthByteBuffer.rewind();
        ShortBuffer depthBuffer = depthByteBuffer.asShortBuffer();

        final Image.Plane confidenceImagePlane = confidence.getPlanes()[0];
        ByteBuffer confidenceBufferOriginal = confidenceImagePlane.getBuffer();
        ByteBuffer confidenceBuffer = ByteBuffer.allocate(confidenceBufferOriginal.capacity());
        confidenceBuffer.order(ByteOrder.LITTLE_ENDIAN);
        while (confidenceBufferOriginal.hasRemaining()) {
            confidenceBuffer.put(confidenceBufferOriginal.get());
        }
        confidenceBuffer.rewind();

        // To transform 2D depth pixels into 3D points, retrieve the intrinsic camera parameters
        // corresponding to the depth image. See more information about the depth values at
        // https://developers.google.com/ar/develop/java/depth/overview#understand-depth-values.
        final int[] intrinsicsDimensions = cameraTextureIntrinsics.getImageDimensions();
        final int depthWidth = depth.getWidth();
        final int depthHeight = depth.getHeight();
        final float fx =
                cameraTextureIntrinsics.getFocalLength()[0] * depthWidth / intrinsicsDimensions[0];
        final float fy =
                cameraTextureIntrinsics.getFocalLength()[1] * depthHeight / intrinsicsDimensions[1];
        final float cx =
                cameraTextureIntrinsics.getPrincipalPoint()[0] * depthWidth / intrinsicsDimensions[0];
        final float cy =
                cameraTextureIntrinsics.getPrincipalPoint()[1] * depthHeight / intrinsicsDimensions[1];

        // Allocate the destination point buffer. If the number of depth pixels is larger than
        // `maxNumberOfPointsToRender` we uniformly subsample. The raw depth image may have
        // different resolutions on different devices.
        final float maxNumberOfPointsToRender = 20000;
        int step = (int) Math.ceil(Math.sqrt(depthWidth * depthHeight / maxNumberOfPointsToRender));

        FloatBuffer points = FloatBuffer.allocate(depthWidth / step * depthHeight / step * FLOATS_PER_POINT);
        float[] pointCamera = new float[4];
        float[] pointWorld = new float[4];

        for (int y = 0; y < depthHeight; y += step) {
            for (int x = 0; x < depthWidth; x += step) {
                // Depth images are tightly packed, so it's OK to not use row and pixel strides.
                int depthMillimeters = depthBuffer.get(y * depthWidth + x); // Depth image pixels are in mm.
                if (depthMillimeters == 0) {
                    // Pixels with value zero are invalid, meaning depth estimates are missing from
                    // this location.
                    continue;
                }
                final float depthMeters = depthMillimeters / 1000.0f; // Depth image pixels are in mm.

                // Retrieve the confidence value for this pixel.
                final byte confidencePixelValue =
                        confidenceBuffer.get(
                                y * confidenceImagePlane.getRowStride()
                                        + x * confidenceImagePlane.getPixelStride());
                final float confidenceNormalized = ((float) (confidencePixelValue & 0xff)) / 255.0f;

                // Unproject the depth into a 3D point in camera coordinates.
                pointCamera[0] = depthMeters * (x - cx) / fx;
                pointCamera[1] = depthMeters * (cy - y) / fy;
                pointCamera[2] = -depthMeters;
                pointCamera[3] = 1;

                // Apply model matrix to transform point into world coordinates.
                Matrix.multiplyMV(pointWorld, 0, modelMatrix, 0, pointCamera, 0);
                points.put(pointWorld[0]); // X.
                points.put(pointWorld[1]); // Y.
                points.put(pointWorld[2]); // Z.
                points.put(confidenceNormalized);
            }
        }

        points.rewind();
        return points;
    }

รับข้อมูลความลึกไฟล์ดิบล่าสุดของแต่ละเฟรม

แก้ไขแอปเพื่อดึงข้อมูลความลึกและปรับแนวให้ตรงกับพิกัดโลกสำหรับท่าทางแต่ละท่า

ใน RawDepthCodelabActivity.java ให้ค้นหาบรรทัดที่มีอยู่ในเมธอด onDrawFrame() ดังต่อไปนี้

Frame frame = session.update();
Camera camera = frame.getCamera();

// If the frame is ready, render the camera preview image to the GL surface.
backgroundRenderer.draw(frame);

เพิ่มบรรทัดต่อไปนี้ที่ด้านล่าง

// Retrieve the depth data for this frame.
FloatBuffer points = DepthData.create(frame, session.createAnchor(camera.getPose()));
if (points == null) {
  return;
}

if (messageSnackbarHelper.isShowing() && points != null) {
  messageSnackbarHelper.hide(this);
}

6. แสดงข้อมูลความลึก (ส่วนที่ 2)

เมื่อคุณมี Pointcloud สำหรับดูระดับความลึกให้เล่นแล้ว ก็ถึงเวลาดูว่าข้อมูลมีลักษณะอย่างไรบนหน้าจอ

เพิ่มโหมดแสดงภาพเพื่อแสดงภาพจุดความลึก

เพิ่มโหมดแสดงภาพเพื่อแสดงภาพจุดความลึก

ขั้นแรก ให้เพิ่มคลาสใหม่เพื่อให้มีตรรกะการแสดงผล คลาสนี้จะดำเนินการ OpenGL เพื่อเริ่มต้นตัวปรับแสงเงาเพื่อแสดงภาพ Pointcloud ของความลึก

เพิ่มคลาส DepthRenderer

  1. คลิกขวาที่ไดเรกทอรีต้นทาง rendering
  2. เลือก New > Java Class
  3. ตั้งชื่อชั้นเรียนว่า DepthRenderer

สร้างชั้นเรียนนี้ด้วยรหัสต่อไปนี้

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

package com.google.ar.core.codelab.common.rendering;

import android.content.Context;
import android.opengl.GLES20;
import android.opengl.Matrix;

import com.google.ar.core.Camera;
import com.google.ar.core.codelab.rawdepth.DepthData;

import java.io.IOException;
import java.nio.FloatBuffer;

public class DepthRenderer {
    private static final String TAG = DepthRenderer.class.getSimpleName();

    // Shader names.
    private static final String VERTEX_SHADER_NAME = "shaders/depth_point_cloud.vert";
    private static final String FRAGMENT_SHADER_NAME = "shaders/depth_point_cloud.frag";

    public static final int BYTES_PER_FLOAT = Float.SIZE / 8;
    private static final int BYTES_PER_POINT = BYTES_PER_FLOAT * DepthData.FLOATS_PER_POINT;
    private static final int INITIAL_BUFFER_POINTS = 1000;

    private int arrayBuffer;
    private int arrayBufferSize;

    private int programName;
    private int positionAttribute;
    private int modelViewProjectionUniform;
    private int pointSizeUniform;

    private int numPoints = 0;

    public DepthRenderer() {}

    public void createOnGlThread(Context context) throws IOException {
        ShaderUtil.checkGLError(TAG, "Bind");

        int[] buffers = new int[1];
        GLES20.glGenBuffers(1, buffers, 0);
        arrayBuffer = buffers[0];
        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, arrayBuffer);

        arrayBufferSize = INITIAL_BUFFER_POINTS * BYTES_PER_POINT;
        GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, arrayBufferSize, null, GLES20.GL_DYNAMIC_DRAW);
        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);

        ShaderUtil.checkGLError(TAG, "Create");

        int vertexShader =
                ShaderUtil.loadGLShader(TAG, context, GLES20.GL_VERTEX_SHADER, VERTEX_SHADER_NAME);
        int fragmentShader =
                ShaderUtil.loadGLShader(TAG, context, GLES20.GL_FRAGMENT_SHADER, FRAGMENT_SHADER_NAME);

        programName = GLES20.glCreateProgram();
        GLES20.glAttachShader(programName, vertexShader);
        GLES20.glAttachShader(programName, fragmentShader);
        GLES20.glLinkProgram(programName);
        GLES20.glUseProgram(programName);

        ShaderUtil.checkGLError(TAG, "Program");

        positionAttribute = GLES20.glGetAttribLocation(programName, "a_Position");
        modelViewProjectionUniform = GLES20.glGetUniformLocation(programName, "u_ModelViewProjection");
        // Sets the point size, in pixels.
        pointSizeUniform = GLES20.glGetUniformLocation(programName, "u_PointSize");

        ShaderUtil.checkGLError(TAG, "Init complete");
    }
}

แสดงผลข้อมูลความลึก

ถัดไป ให้ระบุแหล่งที่มาของตัวปรับแสงเงา เพิ่มเมธอด update() ต่อไปนี้ที่ด้านล่างของคลาส DepthRenderer วิธีนี้ใช้ข้อมูลความลึกล่าสุดเป็นอินพุตและคัดลอกข้อมูล Pointcloud ไปยัง 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");
    }

วาดข้อมูลล่าสุดลงในหน้าจอโดยเพิ่มเมธอด draw() ไว้ที่ด้านล่างของชั้นเรียน DepthRenderer วิธีนี้จะใช้ข้อมูล Pointcloud 3 มิติและฉายกลับไปยังมุมมองกล้องเพื่อให้แสดงผลบนหน้าจอได้

    /** Render the point cloud. The ARCore point cloud is given in world space. */
    public void draw(Camera camera) {
        float[] projectionMatrix = new float[16];
        camera.getProjectionMatrix(projectionMatrix, 0, 0.1f, 100.0f);
        float[] viewMatrix = new float[16];
        camera.getViewMatrix(viewMatrix, 0);
        float[] viewProjection = new float[16];
        Matrix.multiplyMM(viewProjection, 0, projectionMatrix, 0, viewMatrix, 0);

        ShaderUtil.checkGLError(TAG, "Draw");

        GLES20.glUseProgram(programName);
        GLES20.glEnableVertexAttribArray(positionAttribute);
        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, arrayBuffer);
        GLES20.glVertexAttribPointer(positionAttribute, 4, GLES20.GL_FLOAT, false, BYTES_PER_POINT, 0);
        GLES20.glUniformMatrix4fv(modelViewProjectionUniform, 1, false, viewProjection, 0);
        // Set point size to 5 pixels.
        GLES20.glUniform1f(pointSizeUniform, 5.0f);

        GLES20.glDrawArrays(GLES20.GL_POINTS, 0, numPoints);
        GLES20.glDisableVertexAttribArray(positionAttribute);
        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);

        ShaderUtil.checkGLError(TAG, "Draw complete");
    }

คุณกำหนดขนาดจุดเป็นขนาดต่างๆ ในหน่วยพิกเซลได้โดยใช้ตัวแปร pointSizeUniform มีการตั้งค่า pointSizeUniform เป็น 5 พิกเซลในแอปตัวอย่าง

เพิ่มตัวปรับแสงเงาใหม่

คุณดูความลึกและแสดงข้อมูลความลึกในแอปได้หลายวิธี ตรงนี้ให้เพิ่มตัวปรับแสงเงา 2-3 ตัว และสร้างภาพการแมปสีแบบง่ายๆ

เพิ่มตัวปรับแสงเงา .vert และ .frag ใหม่ในไดเรกทอรี src/main/assets/shaders/

การเพิ่มตัวปรับแสงเงา .vert ใหม่

ใน Android Studio ให้ทำดังนี้

  1. คลิกขวาที่ไดเรกทอรีตัวปรับแสงเงา
  2. เลือกใหม่ -> ไฟล์
  3. ตั้งชื่อว่า depth_point_cloud.vert
  4. ตั้งค่าเป็นไฟล์ข้อความ

เพิ่มโค้ดต่อไปนี้ในไฟล์ .vert ใหม่

src/main/assets/shaders/depth_point_cloud.vert

uniform mat4 u_ModelViewProjection;
uniform float u_PointSize;

attribute vec4 a_Position;

varying vec4 v_Color;

// Return an interpolated color in a 6 degree polynomial interpolation.
vec3 GetPolynomialColor(in float x,
  in vec4 kRedVec4, in vec4 kGreenVec4, in vec4 kBlueVec4,
  in vec2 kRedVec2, in vec2 kGreenVec2, in vec2 kBlueVec2) {
  // Moves the color space a little bit to avoid pure red.
  // Removes this line for more contrast.
  x = clamp(x * 0.9 + 0.03, 0.0, 1.0);
  vec4 v4 = vec4(1.0, x, x * x, x * x * x);
  vec2 v2 = v4.zw * v4.z;
  return vec3(
    dot(v4, kRedVec4) + dot(v2, kRedVec2),
    dot(v4, kGreenVec4) + dot(v2, kGreenVec2),
    dot(v4, kBlueVec4) + dot(v2, kBlueVec2)
  );
}

// Return a smooth Percept colormap based upon the Turbo colormap.
vec3 PerceptColormap(in float x) {
  const vec4 kRedVec4 = vec4(0.55305649, 3.00913185, -5.46192616, -11.11819092);
  const vec4 kGreenVec4 = vec4(0.16207513, 0.17712472, 15.24091500, -36.50657960);
  const vec4 kBlueVec4 = vec4(-0.05195877, 5.18000081, -30.94853351, 81.96403246);
  const vec2 kRedVec2 = vec2(27.81927491, -14.87899417);
  const vec2 kGreenVec2 = vec2(25.95549545, -5.02738237);
  const vec2 kBlueVec2 = vec2(-86.53476570, 30.23299484);
  const float kInvalidDepthThreshold = 0.01;
  return step(kInvalidDepthThreshold, x) *
         GetPolynomialColor(x, kRedVec4, kGreenVec4, kBlueVec4,
                            kRedVec2, kGreenVec2, kBlueVec2);
}

void main() {
   // Color the pointcloud by height.
   float kMinHeightMeters = -2.0f;
   float kMaxHeightMeters = 2.0f;
   float normalizedHeight = clamp((a_Position.y - kMinHeightMeters) / (kMaxHeightMeters - kMinHeightMeters), 0.0, 1.0);
   v_Color = vec4(PerceptColormap(normalizedHeight), 1.0);
   gl_Position = u_ModelViewProjection * vec4(a_Position.xyz, 1.0);
   gl_PointSize = u_PointSize;
}

ตัวปรับแสงเงานี้ใช้แผนที่สี Turbo สำหรับการแสดงภาพที่ดีขึ้น โดยมีขั้นตอนดังนี้

  1. ดึงข้อมูลระดับความสูงของแต่ละจุด (แกน y ในพิกัดโลก)
  2. คำนวณสีที่เชื่อมโยงกับระดับความสูงนั้น (แดง=ต่ำ, น้ำเงิน=สูง)
  3. คำนวณตำแหน่งหน้าจอของแต่ละจุด
  4. ตั้งค่าขนาด (เป็นพิกเซล) สำหรับแต่ละจุด ตามที่ระบุไว้ในเมธอด DepthRenderer.update()

สร้างตัวปรับแสงเงา Fragment ในไดเรกทอรีเดียวกันและตั้งชื่อเป็น depth_point_cloud.frag โดยทำขั้นตอนเดียวกันซ้ำในส่วนนี้

จากนั้นเพิ่มโค้ดต่อไปนี้ลงในไฟล์ใหม่นี้เพื่อแสดงจุดแต่ละจุดเป็นจุดยอดมุมเดี่ยวที่มีสีเดียวกันตามที่กำหนดไว้ในตัวปรับแสงเงา Vertex

src/main/assets/shaders/depth_point_cloud.frag

precision mediump float;
varying vec4 v_Color;

void main() {
    gl_FragColor = v_Color;
}

หากต้องการใช้การแสดงผลนี้ ให้เพิ่มการโทรไปยังคลาส DepthRenderer ใน RawDepthCodelabActivity

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

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

เพิ่มสมาชิกส่วนตัวถัดจาก backgroundRenderer ที่ด้านบนของชั้นเรียน

private final DepthRenderer depthRenderer = new DepthRenderer();

depthRenderer ต้องเริ่มต้นภายใน RawDepthCodelabActivity.onSurfaceCreated() เช่นเดียวกับ backgroundRenderer ที่มีอยู่

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

เพิ่มโค้ดต่อไปนี้ไว้ที่ส่วนท้ายของบล็อกลองใช้ภายใน onDrawFrame เพื่อแสดงความลึกล่าสุดสำหรับเฟรมปัจจุบัน

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

การเปลี่ยนแปลงเหล่านี้ทำให้แอปควรจะสร้างได้สำเร็จและแสดง Pointcloud ของความลึก

ตัวอย่างการแสดงภาพ Pointcloud ของความลึกดิบ

  • ตัวอย่างจุดแต่ละจุดจะใส่สีตามความลึก
  • จุดสีแดงอยู่ใกล้ จุดสีเขียว/น้ำเงินอยู่ไกลกว่า
  • ข้อมูลบางส่วนหายไปหรือ "หลุม" สามารถมองเห็นได้ในพื้นที่ที่มีคุณลักษณะของรูปภาพไม่เพียงพอ เช่น ผนังหรือเพดานสีขาวที่ว่างเปล่า
  • คุณสามารถทดลองกับขนาดจุดที่แสดงผลได้โดยการปรับเส้น GLES20.glUniform1f(pointSizeUniform, 5.0f); ภายใน DepthRenderer.draw() แสดงทางด้านซ้ายเป็นจุดขนาด 5 และ 10

7. วิเคราะห์เมฆแบบจุด 3 มิติ (ส่วนที่ 3)

คุณจะวิเคราะห์ข้อมูลความลึกได้เมื่อยืนยันได้ว่าข้อมูลดังกล่าวอยู่ในเซสชัน AR เครื่องมือสำคัญในการวิเคราะห์ความลึกคือค่าความเชื่อมั่นของแต่ละพิกเซล ใช้ค่าความเชื่อมั่นเพื่อวิเคราะห์เมฆแบบจุด 3 มิติ

เอาพิกเซลที่มีความเชื่อมั่นต่ำให้เป็นโมฆะ

คุณดึงข้อมูลค่าความเชื่อมั่นสำหรับพิกเซลความลึกแต่ละพิกเซลและบันทึกไว้ข้างๆ แต่ละจุดภายใน DepthData แล้ว แต่คุณยังไม่ได้ใช้พิกเซลดังกล่าว

ค่าของ confidenceNormalized อยู่ในช่วงตั้งแต่ 0 ถึง 1 โดย 0 หมายถึงความเชื่อมั่นต่ำ และ 1 หมายถึงความเชื่อมั่นเต็มที่ แก้ไขเมธอด convertRawDepthImagesTo3dPointBuffer() ในคลาส DepthData เพื่อหลีกเลี่ยงการบันทึกพิกเซลที่มีความเชื่อมั่นต่ำมากจนไม่มีประโยชน์

final float confidenceNormalized = ((float) (confidencePixelValue & 0xff)) / 255.0f;

// ******** New code to add ************
if (confidenceNormalized < 0.3) {
   // Ignores "low-confidence" pixels.
   continue;
}
// ******** End of new code to add *********

ลองใช้เกณฑ์ต่างๆ สำหรับระดับความเชื่อมั่นเพื่อดูจำนวนจุดความลึกที่เก็บไว้ในแต่ละระดับ

ความมั่นใจ >= 0.1

ความมั่นใจ >= 0.3

ความมั่นใจ >= 0.5

ความมั่นใจ >= 0.7

ความมั่นใจ >= 0.9

กรองพิกเซลตามระยะทาง

นอกจากนี้ยังกรองพิกเซลความลึกตามระยะทางได้ด้วย ขั้นตอนถัดไปเหล่านี้จะจัดการกับเรขาคณิตที่อยู่ใกล้กับกล้อง เพื่อการเพิ่มประสิทธิภาพการทำงาน คุณสามารถละเว้นจุดที่อยู่ไกลเกินไปได้

อัปเดตรหัสการตรวจสอบความเชื่อมั่นที่คุณเพิ่งเพิ่มด้วยข้อมูลต่อไปนี้

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

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

ตอนนี้คุณจะเห็นเฉพาะจุดความเชื่อมั่นสูงและช่วงปิด

การกรองระยะทาง

จำกัด Pointcloud ให้อยู่ห่างจากกล้องไม่เกิน 1.5 เมตร

เปรียบเทียบจุด 3 มิติกับเครื่องบิน

คุณสามารถเปรียบเทียบจุด 3 มิติของเรขาคณิตกับระนาบต่างๆ และใช้จุดเหล่านั้นเพื่อกรองกัน เช่น นำจุดที่อยู่ใกล้กับระนาบ AR ที่สังเกตการณ์ออก

ขั้นตอนนี้จะเหลือเฉพาะ "ไม่ใช่ระนาบ" จุดที่มีแนวโน้มว่าจะแสดงพื้นผิวบนวัตถุในสภาพแวดล้อม เพิ่มเมธอด filterUsingPlanes() ที่ด้านล่างของชั้นเรียน DepthData เมธอดนี้จะทำซ้ำผ่านจุดที่มีอยู่ ตรวจสอบแต่ละจุดเทียบกับระนาบแต่ละระนาบ และทำให้จุดที่อยู่ใกล้กับระนาบ AR กลายเป็นโมฆะ ทำให้เหลือพื้นที่ที่ไม่ใช่ระนาบซึ่งไฮไลต์วัตถุในฉากนั้น

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

    public static void filterUsingPlanes(FloatBuffer points, Collection<Plane> allPlanes) {
        float[] planeNormal = new float[3];

        // Allocate the output buffer.
        int numPoints = points.remaining() / DepthData.FLOATS_PER_POINT;

        // Check each plane against each point.
        for (Plane plane : allPlanes) {
            if (plane.getTrackingState() != TrackingState.TRACKING || plane.getSubsumedBy() != null) {
                continue;
            }

            // Compute the normal vector of the plane.
            Pose planePose = plane.getCenterPose();
            planePose.getTransformedAxis(1, 1.0f, planeNormal, 0);

            // Filter points that are too close to the plane.
            for (int index = 0; index < numPoints; ++index) {
                // Retrieves the next point.
                final float x = points.get(FLOATS_PER_POINT * index);
                final float y = points.get(FLOATS_PER_POINT * index + 1);
                final float z = points.get(FLOATS_PER_POINT * index + 2);

                // Transform point to be in world coordinates, to match plane info.
                float distance = (x - planePose.tx()) * planeNormal[0]
                        + (y - planePose.ty()) * planeNormal[1]
                        + (z - planePose.tz()) * planeNormal[2];
                // Controls the size of objects detected.
                // Smaller values mean smaller objects will be kept.
                // Larger values will only allow detection of larger objects, but also helps reduce noise.
                if (Math.abs(distance) > 0.03) {
                    continue;  // Keep this point, since it's far enough away from the plane.
                }

                // Invalidate points that are too close to planar surfaces.
                points.put(FLOATS_PER_POINT * index, 0);
                points.put(FLOATS_PER_POINT * index + 1, 0);
                points.put(FLOATS_PER_POINT * index + 2, 0);
                points.put(FLOATS_PER_POINT * index + 3, 0);
            }
        }
    }

คุณเพิ่มเมธอดนี้ลงใน RawDepthCodelabActivity ในเมธอด onDrawFrame ได้ ดังนี้

//  ********** New code to add ************
  // Filter the depth data.
  DepthData.filterUsingPlanes(points, session.getAllTrackables(Plane.class));
//  ********** End new code to add *******

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

ตอนนี้การเรียกใช้ Codelab จะทำให้ระบบแสดงผลจุดบางส่วน จุดเหล่านี้แสดงถึงวัตถุในฉาก โดยไม่สนใจพื้นผิวราบที่วัตถุปรากฏอยู่ คุณใช้ข้อมูลเหล่านี้เพื่อประมาณขนาดและตำแหน่งของวัตถุได้โดยจัดกลุ่มจุดไว้ด้วยกัน

ถ้วยชา

ไมโครโฟน

หูฟัง

หมอน

จุดคลัสเตอร์

Codelab นี้มีอัลกอริทึมคลัสเตอร์ Pointcloud ที่ง่ายมาก อัปเดต Codelab เพื่อจัดกลุ่ม Pointcloud ที่ดึงมาลงในคลัสเตอร์ที่กำหนดโดยกรอบล้อมรอบแบบแกน

src/main/java/com/google/ar/core/codelab/rawdepth/RawDepthCodelabActivity.java

import com.google.ar.core.codelab.common.helpers.AABB;
import com.google.ar.core.codelab.common.helpers.PointClusteringHelper;
import com.google.ar.core.codelab.common.rendering.BoxRenderer;
import java.util.List;

เพิ่ม BoxRenderer ลงในคลาสนี้ที่ด้านบนของไฟล์ พร้อมด้วยโหมดแสดงภาพอื่นๆ

private final BoxRenderer boxRenderer = new BoxRenderer();

ภายในเมธอด onSurfaceCreated() ให้เพิ่มค่าต่อไปนี้ควบคู่ไปกับโหมดแสดงภาพอื่นๆ

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

สุดท้าย ให้เพิ่มบรรทัดต่อไปนี้ไปยัง onDrawFrame() ภายใน RawDepthCodelabActivity เพื่อจัดกลุ่ม Pointcloud ที่ดึงมาไว้เป็นคลัสเตอร์และแสดงผลผลลัพธ์เป็นกรอบล้อมรอบแบบแกน

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

ถ้วยชา

ไมโครโฟน

หูฟัง

หมอน

ตอนนี้คุณสามารถดึงข้อมูลความลึกแบบ Raw ผ่านเซสชัน ARCore, แปลงข้อมูลเชิงลึกเป็น Pointcloud 3 มิติ ตลอดจนดำเนินการกรองและการแสดงผลขั้นพื้นฐานในจุดเหล่านั้น

8. Build-Run-Test

สร้าง เรียกใช้ และทดสอบแอป

สร้างและเรียกใช้แอป

ทำตามขั้นตอนต่อไปนี้เพื่อสร้างและเรียกใช้แอป

  1. เสียบอุปกรณ์ที่รองรับ ARCore ผ่าน USB
  2. เรียกใช้โปรเจ็กต์ด้วยปุ่ม ► ในแถบเมนู
  3. รอให้แอปสร้างและทำให้ใช้งานได้ในอุปกรณ์ของคุณ

ครั้งแรกที่คุณพยายามทำให้แอปใช้งานได้ในอุปกรณ์ของคุณ คุณจะต้องดำเนินการต่อไปนี้

อนุญาตให้แก้ไขข้อบกพร่อง USB

ในอุปกรณ์ เลือก "ตกลง" เพื่อดำเนินการต่อ

ครั้งแรกที่เรียกใช้แอปในอุปกรณ์ ระบบจะถามว่าแอปมีสิทธิ์ใช้กล้องของอุปกรณ์หรือไม่ คุณต้องอนุญาตให้เข้าถึงเพื่อใช้ฟังก์ชัน AR ต่อไป

การทดสอบแอปของคุณ

เมื่อเรียกใช้แอป คุณทดสอบลักษณะการทำงานเบื้องต้นของแอปได้โดยถืออุปกรณ์ไว้ เคลื่อนไปรอบๆ พื้นที่ของคุณ และสแกนพื้นที่ช้าๆ พยายามรวบรวมข้อมูลอย่างน้อย 10 วินาทีและสแกนพื้นที่จากหลายๆ เส้นทางก่อนไปยังขั้นตอนถัดไป

9. ขอแสดงความยินดี

ขอแสดงความยินดี คุณได้สร้างและเรียกใช้แอป Augmented Reality แบบเจาะลึกแอปแรกโดยใช้ ARCore Raw Depth API ของ Google เรียบร้อยแล้ว เราตื่นเต้นที่จะได้เห็นสิ่งที่คุณจะสร้าง

10. การแก้ปัญหา

ตั้งค่าอุปกรณ์ Android เพื่อการพัฒนา

  1. เชื่อมต่ออุปกรณ์กับเครื่องพัฒนาซอฟต์แวร์ด้วยสาย USB หากคุณพัฒนาโดยใช้ Windows คุณอาจต้องติดตั้งไดรเวอร์ USB ที่เหมาะสมสำหรับอุปกรณ์ของคุณ
  2. ทำตามขั้นตอนต่อไปนี้เพื่อเปิดใช้การแก้ไขข้อบกพร่อง USB ในหน้าต่างตัวเลือกสำหรับนักพัฒนาแอป
  • เปิดแอปการตั้งค่า
  • หากอุปกรณ์ใช้ Android เวอร์ชัน 8.0 ขึ้นไป ให้เลือกระบบ
  • เลื่อนไปด้านล่างแล้วเลือกเกี่ยวกับโทรศัพท์
  • เลื่อนไปด้านล่างและแตะหมายเลขบิลด์ 7 ครั้ง
  • กลับไปที่หน้าจอก่อนหน้า เลื่อนไปด้านล่าง แล้วแตะตัวเลือกสำหรับนักพัฒนาซอฟต์แวร์
  • ในหน้าต่างตัวเลือกสำหรับนักพัฒนาซอฟต์แวร์ ให้เลื่อนลงเพื่อหาและเปิดใช้การแก้ไขข้อบกพร่อง USB

คุณสามารถดูข้อมูลโดยละเอียดเพิ่มเติมเกี่ยวกับกระบวนการนี้ได้ที่เว็บไซต์นักพัฒนาซอฟต์แวร์ Android ของ Google

หากเกิดความล้มเหลวในรุ่นที่เกี่ยวข้องกับใบอนุญาต (Failed to install the following Android SDK packages as some licences have not been accepted) คุณสามารถใช้คำสั่งต่อไปนี้เพื่อตรวจสอบและยอมรับใบอนุญาตเหล่านี้ได้

cd <path to Android SDK>

tools/bin/sdkmanager --licenses

คำถามที่พบบ่อย