عمق خام ARCore

1. مقدمه

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

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

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

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

چیزی که خواهی ساخت

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

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

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

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

2. پیش نیازها

برای تکمیل این کد لبه به سخت افزار و نرم افزار خاصی نیاز دارید.

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

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

الزامات نرم افزاری

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 را راه اندازی کنید و Open an Android Studio موجود پروژه را انتخاب کنید.
  2. به دایرکتوری محلی که فایل ZIP عمق خام را در آن ذخیره کرده اید بروید.
  3. روی دایرکتوری arcore_rawdepthapi_codelab دوبار کلیک کنید.

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

نتیجه باید به این صورت باشد:

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

  • part0_work : برنامه شروع. هنگام انجام این کد لبه، باید این ماژول را ویرایش کنید. تمام قسمت های دیگر حاوی کد مرجع هستند.
  • part1 : کد مرجع مربوط به اینکه ویرایش‌های شما پس از تکمیل قسمت 1 چگونه باید باشد.
  • part2 : کد مرجع پس از تکمیل قسمت 2.
  • part3_completed : زمانی که قسمت 3 را تکمیل می‌کنید، کد مرجع، که انتهای Codelab است.

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

4. برنامه استارتر را اجرا کنید

برای اجرای برنامه استارتر Raw Depth این مراحل را دنبال کنید.

  1. به Run > Run... > 'part0_work' بروید.
  2. در گفتگوی Select Deployment Target ، دستگاه خود را از لیست دستگاه های متصل انتخاب کنید و روی OK کلیک کنید.

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

هنگامی که برنامه را برای اولین بار اجرا می کنید، مجوز CAMERA را درخواست می کند. برای ادامه روی Allow ضربه بزنید.

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

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

5. Raw Depth API را تنظیم کنید (قسمت 1)

مطمئن شوید که دستگاه مورد نظر از 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.";
}

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

Raw Depth API یک تصویر با عمق صاف و یک تصویر اطمینان متناظر حاوی اطمینان عمق برای هر پیکسل در تصویر عمق خام ارائه می دهد. Raw Depth را با به‌روزرسانی کد زیر در دستور 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;
}

اکنون Session 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.

}

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

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

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

6. داده های عمق را رندر کنید (قسمت 2)

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

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

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

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

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

سایه بان های جدید اضافه کنید

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

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

افزودن سایه‌زن Vert جدید

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

  1. روی فهرست سایه بان ها کلیک راست کنید
  2. New -> File را انتخاب کنید
  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() تعریف شده است، تنظیم می کند.

یک قطعه سایه زن در همان دایرکتوری ایجاد کنید و نام آن را 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 باید در داخل RawDepthCodelabActivity.onSurfaceCreated() مقداردهی اولیه شود، درست مانند backgroundRenderer موجود.

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

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

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

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

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

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

7. تحلیل ابرهای نقطه سه بعدی (قسمت 3)

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

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

شما مقدار اطمینان هر پیکسل عمق را بازیابی کرده اید و آن را در کنار هر نقطه در 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;
 }

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

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

نقطه ابری را در 1.5 متری دوربین محدود می کند.

نقاط سه بعدی و سطوح را مقایسه کنید

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

می توانید این متد را در متد onDrawFrame به RawDepthCodelabActivity اضافه کنید:

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

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

فنجان چای

میکروفون

هدفون

بالش

نقاط خوشه ای

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

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

8. Build-Run-Test

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

اپلیکیشن خود را بسازید و اجرا کنید

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

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

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

به اشکال زدایی USB اجازه دهید

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

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

در حال آزمایش برنامه شما

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

9. تبریک می گویم

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

10. عیب یابی

راه اندازی دستگاه اندرویدی خود برای توسعه

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

می‌توانید اطلاعات دقیق‌تری درباره این فرآیند در وب‌سایت توسعه‌دهنده اندروید 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

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