عمق خام ARCore

۱. مقدمه

ARCore پلتفرمی برای ساخت برنامه‌های واقعیت افزوده (AR) در دستگاه‌های تلفن همراه است. API عمق ARCore گوگل، دسترسی به یک تصویر عمقی را برای هر فریم در یک جلسه ARCore فراهم می‌کند. هر پیکسل در تصویر عمقی، اندازه‌گیری فاصله از دوربین تا محیط را ارائه می‌دهد.

API عمق خام، تصاویر عمقی ارائه می‌دهد که از عملیات فیلترینگ فضای صفحه که برای هموارسازی و درون‌یابی نتایج طراحی شده‌اند، عبور داده نشده‌اند. این مقادیر از نظر هندسی دقیق‌تر هستند، اما ممکن است حاوی داده‌های از دست رفته باشند و با تصویر دوربین مرتبط، هماهنگی کمتری داشته باشند.

این آزمایشگاه کد، نحوه استفاده از API عمق خام (Raw Depth API) را برای انجام تحلیل هندسی سه‌بعدی صحنه نشان می‌دهد. شما یک برنامه ساده با قابلیت AR خواهید ساخت که از داده‌های عمق خام برای تشخیص و تجسم هندسه جهان استفاده می‌کند.

APIهای Depth و Raw Depth فقط در زیرمجموعه‌ای از دستگاه‌های دارای ARCore پشتیبانی می‌شوند. API Depth فقط در اندروید موجود است.

آنچه خواهید ساخت

در این آزمایشگاه کد، شما برنامه‌ای خواهید ساخت که از تصاویر عمق خام برای هر فریم استفاده می‌کند تا تحلیل هندسی جهان اطراف شما را انجام دهد. این برنامه:

  1. بررسی کنید که آیا دستگاه هدف از قابلیت Depth پشتیبانی می‌کند یا خیر.
  2. تصویر عمق خام را برای هر فریم دوربین بازیابی کنید.
  3. تصاویر عمق خام را به نقاط سه‌بعدی تبدیل کنید و آن نقاط را بر اساس اطمینان و هندسه فیلتر کنید.
  4. از ابر نقاط عمق خام برای قطعه‌بندی اشیاء سه‌بعدی مورد نظر استفاده کنید.

پیش‌نمایشی از آنچه خواهید ساخت.

توجه: اگر در طول مسیر با مشکلی مواجه شدید، برای نکات عیب‌یابی به بخش آخر بروید.

۲. پیش‌نیازها

برای تکمیل این آزمایشگاه کد، به سخت‌افزار و نرم‌افزار خاصی نیاز دارید.

الزامات سخت‌افزاری

  • یک دستگاه پشتیبانی‌شده از ARCore که اشکال‌زدایی USB در آن فعال باشد و از طریق کابل USB به دستگاه توسعه شما متصل باشد. این دستگاه همچنین باید از Depth API پشتیبانی کند.

نیازمندی‌های نرم‌افزاری

۳. راه‌اندازی

دستگاه توسعه را راه‌اندازی کنید

دستگاه 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

یا یک فایل زیپ دانلود کنید و آن را از حالت فشرده خارج کنید:

برای شروع کار با کد، این مراحل را دنبال کنید.

  1. اندروید استودیو را اجرا کنید و گزینه‌ی «باز کردن یک پروژه‌ی اندروید استودیو موجود» را انتخاب کنید.
  2. به دایرکتوری محلی که فایل زیپ Raw Depth را در آن ذخیره کرده‌اید، بروید.
  3. روی پوشه‌ی arcore_rawdepthapi_codelab دوبار کلیک کنید.

دایرکتوری arcore_rawdepthapi_codelab یک پروژه Gradle واحد با چندین ماژول است. اگر پنجره Project در سمت چپ بالای Android Studio از قبل در پنجره Project نمایش داده نشده است، از منوی کشویی روی Projects کلیک کنید.

نتیجه باید به این شکل باشد:

این پروژه شامل ماژول‌های زیر است:

  • part0_work : برنامه‌ی آغازین. شما باید هنگام انجام این آزمایش کد، این ماژول را ویرایش کنید. سایر بخش‌ها حاوی کد مرجع هستند.
  • part1 : کد مرجعی که نشان می‌دهد ویرایش‌های شما پس از تکمیل بخش ۱ چگونه باید باشند.
  • part2 : کد مرجع هنگام تکمیل بخش ۲.
  • part3_completed : کد مرجع هنگام تکمیل بخش ۳، که پایان آزمایشگاه کد است.

شما در ماژول part0_work کار خواهید کرد. همچنین برای هر بخش از codelab راه‌حل‌های کاملی وجود دارد. هر ماژول یک برنامه قابل ساخت است.

۴. برنامه‌ی آغازین را اجرا کنید

برای اجرای برنامه‌ی Raw Depth starter، این مراحل را دنبال کنید.

  1. به مسیر Run > Run... > 'part0_work' بروید.
  2. در پنجره‌ی «انتخاب هدف استقرار »، دستگاه خود را از فهرست «دستگاه‌های متصل» انتخاب کرده و روی «تأیید» کلیک کنید.

اندروید استودیو برنامه اولیه را می‌سازد و آن را روی دستگاه شما اجرا می‌کند.

وقتی برای اولین بار برنامه را اجرا می‌کنید، از شما اجازه دوربین (CAMERA) را درخواست می‌کند. برای ادامه، روی «مجاز» (Allow) ضربه بزنید.

در حال حاضر، این برنامه هیچ کاری انجام نمی‌دهد . این ابتدایی‌ترین برنامه واقعیت افزوده است که نمای دوربین از صحنه شما را نشان می‌دهد، اما کار دیگری انجام نمی‌دهد. کد موجود مشابه نمونه Hello AR منتشر شده با ARCore SDK است.

در مرحله بعد، از API عمق خام (Raw Depth API) برای بازیابی هندسه صحنه اطراف خود استفاده خواهید کرد.

۵. تنظیم API عمق خام (بخش ۱)

مطمئن شوید که دستگاه هدف از Depth پشتیبانی می‌کند

همه دستگاه‌های پشتیبانی‌شده توسط ARCore نمی‌توانند Depth API را اجرا کنند. قبل از افزودن قابلیت به برنامه خود در تابع onResume() از RawDepthCodelabActivity.java ، جایی که یک Session جدید ایجاد می‌شود، مطمئن شوید که دستگاه هدف از Depth پشتیبانی می‌کند.

کد موجود را پیدا کنید:

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

فعال کردن عمق خام

API مربوط به عمق خام، یک تصویر با عمق هموار نشده و یک تصویر با اطمینان متناظر شامل اطمینان عمق برای هر پیکسل در تصویر با عمق خام ارائه می‌دهد. با به‌روزرسانی کد زیر در زیر عبارت try-catch که به تازگی اصلاح کرده‌اید، عمق خام را فعال کنید.

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

اکنون جلسه واقعیت افزوده (AR Session) به طور مناسب پیکربندی شده است و برنامه می‌تواند از ویژگی‌های مبتنی بر عمق استفاده کند.

فراخوانی 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.

}

این کلاس برای تبدیل تصاویر عمق به ابرنقاط استفاده می‌شود. ابرنقاط هندسه صحنه را با فهرستی از نقاط نشان می‌دهند که هر کدام دارای مختصات سه‌بعدی (x، y، z) و یک مقدار اطمینان در محدوده ۰ تا ۱ هستند.

با اضافه کردن متد create() در انتهای کلاس، فراخوانی‌هایی برای پر کردن این مقادیر با استفاده از API عمق خام اضافه کنید. این متد آخرین تصاویر عمق و اطمینان را جستجو می‌کند و ابر نقطه‌ای حاصل را ذخیره می‌کند. تصاویر عمق و اطمینان دارای داده‌های منطبق خواهند بود.

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

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

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

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

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

acquireCameraImage()

acquireDepthImage16Bits()

acquireRawDepthImage16Bits()

acquireRawDepthConfidenceImage()

این کد همچنین در این زمان لنگر دوربین را ذخیره می‌کند، به طوری که اطلاعات عمق را می‌توان با فراخوانی یک متد کمکی convertRawDepthImagesTo3dPointBuffer() به مختصات جهانی تبدیل کرد. این متد کمکی هر پیکسل را در تصویر عمق می‌گیرد و از ویژگی‌های ذاتی دوربین برای تبدیل عمق به یک نقطه سه‌بعدی نسبت به دوربین استفاده می‌کند. سپس از لنگر دوربین برای تبدیل موقعیت نقطه به مختصات جهانی استفاده می‌شود. هر پیکسل موجود به یک نقطه سه‌بعدی (بر حسب متر) تبدیل شده و در کنار مقدار اطمینان آن ذخیره می‌شود.

متد کمکی زیر را به DepthData.java اضافه کنید:

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

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

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

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

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

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

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

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

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

        points.rewind();
        return points;
    }

آخرین داده‌های عمق خام را برای هر فریم دریافت کنید

برنامه را طوری تغییر دهید که اطلاعات عمق را بازیابی کند و آن را برای هر ژست با مختصات جهانی هم‌تراز کند.

در RawDepthCodelabActivity.java ، در متد onDrawFrame() ، خطوط موجود را پیدا کنید:

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

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

خطوط زیر را درست زیر آن اضافه کنید:

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

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

۶. رندر کردن داده‌های عمق (بخش ۲)

حالا که یک ابر نقطه‌ای عمق برای کار با آن دارید، وقت آن است که ببینید داده‌ها هنگام رندر شدن روی صفحه نمایش چگونه به نظر می‌رسند.

برای تجسم نقاط عمق، یک رندرکننده اضافه کنید

یک رندرکننده برای نمایش نقاط عمق اضافه کنید.

ابتدا، یک کلاس جدید برای شامل کردن منطق رندر اضافه کنید. این کلاس عملیات OpenGL را برای مقداردهی اولیه سایه‌زن‌ها جهت تجسم ابر نقطه‌ای عمق انجام می‌دهد.

اضافه کردن کلاس DepthRenderer

  1. روی دایرکتوری منبع rendering کلیک راست کنید
  2. New > Java Class را انتخاب کنید.
  3. نام کلاس را DepthRenderer بگذارید.

این کلاس را با کد زیر پر کنید:

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

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

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

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

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

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

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

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

    private int arrayBuffer;
    private int arrayBufferSize;

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

    private int numPoints = 0;

    public DepthRenderer() {}

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

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

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

        ShaderUtil.checkGLError(TAG, "Create");

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

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

        ShaderUtil.checkGLError(TAG, "Program");

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

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

رندر کردن داده‌های عمق

در مرحله بعد، منبع مربوط به رندر شیدرها را ارائه دهید. متد update() زیر را در انتهای کلاس DepthRenderer اضافه کنید. این متد آخرین اطلاعات عمق را به عنوان ورودی دریافت کرده و داده‌های 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 آخرین داده‌ها را روی صفحه نمایش دهید. این متد اطلاعات ابر نقاط سه‌بعدی را دریافت کرده و آن را به نمای دوربین برمی‌گرداند تا بتوان آن را روی صفحه نمایش داد.

    /** 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 در برنامه نمونه روی ۵ پیکسل تنظیم شده است.

اضافه کردن شیدرهای جدید

روش‌های زیادی برای مشاهده عمق و نمایش داده‌های عمق در برنامه شما وجود دارد. در اینجا، چند سایه‌زن اضافه خواهید کرد و یک تجسم ساده نگاشت رنگ ایجاد خواهید کرد.

شیدرهای جدید .vert و .frag را به دایرکتوری src/main/assets/shaders/ اضافه کنید.

اضافه کردن شیدر جدید .vert

در اندروید استودیو:

  1. روی پوشه shaders کلیک راست کنید
  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;
}

این سایه‌زن از نقشه رنگی توربو برای بهبود تجسم استفاده می‌کند. این سایه‌زن مراحل زیر را انجام می‌دهد:

  1. ارتفاع هر نقطه (محور y در مختصات جهانی) را بازیابی می‌کند.
  2. رنگی مرتبط با آن ارتفاع را محاسبه می‌کند (قرمز=پایین، آبی=بالا).
  3. موقعیت هر نقطه را روی صفحه نمایش محاسبه می‌کند.
  4. اندازه (برحسب پیکسل) را برای هر نقطه، همانطور که در متد DepthRenderer.update() تعریف شده است، تنظیم می‌کند.

یک سایه‌زن قطعه‌ای در همان دایرکتوری ایجاد کنید و نام آن را depth_point_cloud.frag بگذارید و مراحل مشابه در این بخش را تکرار کنید.

سپس کد زیر را به این فایل جدید اضافه کنید تا هر نقطه به عنوان یک رأس واحد با رنگ یکنواخت، همانطور که در سایه‌زن رأس تعریف شده است، رندر شود.

src/main/assets/shaders/depth_point_cloud.frag

precision mediump float;
varying vec4 v_Color;

void main() {
    gl_FragColor = v_Color;
}

برای اعمال این رندر، کلاس DepthRenderer را درون RawDepthCodelabActivity خود فراخوانی کنید.

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

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

در بالای کلاس، یک عضو خصوصی در کنار backgroundRenderer اضافه کنید.

private final DepthRenderer depthRenderer = new DepthRenderer();

depthRenderer باید درست مانند backgroundRenderer موجود، درون RawDepthCodelabActivity.onSurfaceCreated() مقداردهی اولیه شود.

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

کد زیر را در انتهای بلوک try-catch درون onDrawFrame اضافه کنید تا آخرین عمق فریم فعلی نمایش داده شود.

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

با این تغییرات، اکنون برنامه باید با موفقیت ساخته شود و ابر نقطه‌ای عمق را نشان دهد.

مثال تجسم ابر نقطه‌ای با عمق خام

  • هر نمونه نقطه‌ای بر اساس عمق آن رنگ‌آمیزی می‌شود.
  • نقاط قرمز نزدیک هستند، نقاط سبز/آبی دورتر هستند
  • برخی از داده‌های از دست رفته یا «حفره‌ها» را می‌توان در مناطقی با ویژگی‌های تصویر ناکافی، مانند دیوارها یا سقف‌های سفید خالی، مشاهده کرد.
  • شما می‌توانید با تنظیم خط GLES20.glUniform1f(pointSizeUniform, 5.0f); درون DepthRenderer.draw() با اندازه نقاط رندر شده بازی کنید. در سمت چپ، اندازه نقاط ۵ و ۱۰ نشان داده شده است.

۷. تحلیل ابرهای نقطه‌ای سه‌بعدی (بخش ۳)

شما می‌توانید داده‌های عمق را پس از تأیید وجود آنها در یک جلسه AR تجزیه و تحلیل کنید. یک ابزار مهم برای تجزیه و تحلیل عمق، مقدار اطمینان برای هر پیکسل است. از مقادیر اطمینان برای تجزیه و تحلیل ابرهای نقطه‌ای سه‌بعدی استفاده کنید.

پیکسل‌های با اطمینان پایین را نامعتبر کنید

شما مقدار اطمینان را برای هر پیکسل عمق بازیابی کرده و آن را در کنار هر نقطه درون DepthData ذخیره کرده‌اید، اما هنوز از آن استفاده نکرده‌اید.

مقادیر confidenceNormalized نرمال‌شده از ۰ تا ۱ متغیر است، که ۰ نشان‌دهنده‌ی اعتماد کم و ۱ نشان‌دهنده‌ی اعتماد کامل است. متد 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;
 }

حالا فقط نقاط با اعتماد به نفس بالا و نزدیک را خواهید دید.

فیلتر کردن فاصله

ابر نقاط را به فاصله ۱.۵ متری دوربین محدود می‌کند.

مقایسه نقاط و صفحات سه‌بعدی

شما می‌توانید نقاط و صفحات سه‌بعدی هندسی را با هم مقایسه کنید و از آنها برای فیلتر کردن یکدیگر استفاده کنید، مانند حذف نقاطی که نزدیک به صفحات 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 اکنون منجر به رندر شدن زیرمجموعه‌ای از نقاط می‌شود. این نقاط نمایانگر اشیاء موجود در صحنه هستند، در حالی که سطوح صافی که اشیاء روی آنها قرار دارند را نادیده می‌گیرند. می‌توانید از این داده‌ها برای تخمین اندازه و موقعیت اشیاء با خوشه‌بندی نقاط با هم استفاده کنید.

فنجان چای

میکروفون

هدفون

بالش

نقاط خوشه

این آزمایشگاه کد شامل یک الگوریتم خوشه‌بندی ابر نقاط بسیار ساده است. آزمایشگاه کد را به‌روزرسانی کنید تا ابر نقاط بازیابی شده را در خوشه‌هایی که توسط جعبه‌های محصورکننده هم‌تراز با محور تعریف شده‌اند، گروه‌بندی کند.

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

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

یک BoxRenderer به این کلاس در بالای فایل، به همراه رندرکننده‌های دیگر اضافه کنید.

private final BoxRenderer boxRenderer = new BoxRenderer();

و درون متد onSurfaceCreated() ، موارد زیر را در کنار سایر رندرکننده‌ها اضافه کنید:

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

در نهایت، خطوط زیر را به onDrawFrame() درون RawDepthCodelabActivity اضافه کنید تا ابرهای نقطه‌ای بازیابی شده را در خوشه‌هایی گروه‌بندی کرده و نتایج را به صورت جعبه‌های محصورکننده‌ی هم‌تراز با محور رندر کنید.

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

// ************ New code to add ***************

      // Draw boxes around clusters of points.
      PointClusteringHelper clusteringHelper = new PointClusteringHelper(points);
      List<AABB> clusters = clusteringHelper.findClusters();
      for (AABB aabb : clusters) {
        boxRenderer.draw(aabb, camera);
      }

// ************ End new code to add ***************

فنجان چای

میکروفون

هدفون

بالش

اکنون می‌توانید عمق خام را از طریق یک جلسه ARCore بازیابی کنید، اطلاعات عمق را به ابرهای نقطه‌ای سه‌بعدی تبدیل کنید و عملیات فیلترینگ و رندرینگ اولیه را روی آن نقاط انجام دهید.

۸. ساخت-اجرا-تست

برنامه خود را بسازید، اجرا کنید و آزمایش کنید.

برنامه خود را بسازید و اجرا کنید

برای ساخت و اجرای برنامه خود، این مراحل را دنبال کنید:

  1. یک دستگاه پشتیبانی‌شده با ARCore را از طریق USB وصل کنید.
  2. پروژه خود را با دکمه ► در نوار منو اجرا کنید.
  3. منتظر بمانید تا برنامه ساخته و روی دستگاه شما نصب شود.

اولین باری که سعی می‌کنید برنامه را روی دستگاه خود نصب کنید، باید

اشکال‌زدایی USB را مجاز کنید

روی دستگاه. برای ادامه، تأیید را انتخاب کنید.

اولین باری که برنامه خود را روی دستگاه اجرا می‌کنید، از شما پرسیده می‌شود که آیا برنامه اجازه استفاده از دوربین دستگاه شما را دارد یا خیر. برای ادامه استفاده از قابلیت واقعیت افزوده، باید به آن اجازه دسترسی بدهید.

تست برنامه شما

وقتی برنامه خود را اجرا می‌کنید، می‌توانید رفتار اولیه آن را با نگه داشتن دستگاه، حرکت در فضای اطراف و اسکن آهسته یک منطقه آزمایش کنید. سعی کنید حداقل 10 ثانیه داده جمع‌آوری کنید و قبل از رفتن به مرحله بعدی، منطقه را از چندین جهت اسکن کنید.

۹. تبریک

تبریک می‌گوییم، شما با موفقیت اولین برنامه واقعیت افزوده مبتنی بر عمق خود را با استفاده از API عمق خام ARCore گوگل ساخته و اجرا کردید. ما مشتاقیم ببینیم چه چیزی خواهید ساخت!

۱۰. عیب‌یابی

آماده‌سازی دستگاه اندروید برای توسعه

  1. دستگاه خود را با کابل USB به دستگاه توسعه خود وصل کنید. اگر با استفاده از ویندوز توسعه می‌دهید، ممکن است لازم باشد درایور USB مناسب دستگاه خود را نصب کنید.
  2. برای فعال کردن اشکال‌زدایی USB در پنجره گزینه‌های توسعه‌دهندگان ، مراحل زیر را انجام دهید:
  • برنامه تنظیمات را باز کنید.
  • اگر دستگاه شما از اندروید نسخه ۸.۰ یا بالاتر استفاده می‌کند، سیستم را انتخاب کنید.
  • به پایین صفحه بروید و درباره تلفن (About phone) را انتخاب کنید.
  • به پایین صفحه بروید و هفت بار روی شماره ساخت (Build number) ضربه بزنید.
  • به صفحه قبلی برگردید، به پایین بروید و روی گزینه‌های توسعه‌دهندگان (Developer options) ضربه بزنید.
  • در پنجره گزینه‌های توسعه‌دهندگان ، به پایین اسکرول کنید تا گزینه اشکال‌زدایی USB را پیدا کرده و آن را فعال کنید.

می‌توانید اطلاعات دقیق‌تر در مورد این فرآیند را در وب‌سایت توسعه‌دهندگان اندروید گوگل پیدا کنید.

اگر با خطای ساخت مربوط به مجوزها مواجه شدید ( Failed to install the following Android SDK packages as some licences have not been accepted )، می‌توانید از دستورات زیر برای بررسی و پذیرش این مجوزها استفاده کنید:

cd <path to Android SDK>

tools/bin/sdkmanager --licenses

سوالات متداول