عمق المعالجة في ARCore Raw Depth

1. مقدمة

ARCore هي منصة لإنشاء تطبيقات الواقع المعزّز (AR) على الأجهزة الجوّالة. تتيح واجهة برمجة التطبيقات ARCore Depth API من Google الوصول إلى صورة بعمق كل إطار في جلسة ARCore. يوفر كل بكسل في الصورة بالعمق قياس مسافة من الكاميرا إلى البيئة.

توفّر واجهة برمجة التطبيقات Raw Depth API صورًا متعمقة لا يتم تمريرها من خلال عمليات فلترة مساحات الشاشة المصمّمة لتسهيل النتائج وإقحامها. هذه القيم أكثر دقة من الناحية الهندسية ولكن قد تحتوي على بيانات مفقودة وقد تكون أقل توافقًا مع صورة الكاميرا المرتبطة.

يعرض هذا الدرس التطبيقي حول الترميز كيفية استخدام Raw Depth API لإجراء تحليل هندسي ثلاثي الأبعاد للمشهد. ستتمكن من إنشاء تطبيق بسيط يدعم الواقع المعزّز يستخدم بيانات العمق الأولية لاكتشاف هندسة العالم وتصورها.

لا تتوافق واجهات برمجة التطبيقات Depth وRaw Depth API إلا مع مجموعة فرعية من الأجهزة التي تستخدم ARCore. لا تتوفّر واجهة Depth API إلا على أجهزة Android.

ما الذي ستقوم ببنائه

في هذا الدرس التطبيقي حول الترميز، ستنشئ تطبيقًا يستخدم صورًا بعمق أولي لكل إطار لإجراء تحليل هندسي للعالم من حولك. سينفّذ هذا التطبيق ما يلي:

  1. تحقَّق مما إذا كان الجهاز المستهدَف يتوافق مع ميزة "العمق".
  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" واختَر فتح مشروع حالي على "استوديو Android".
  2. انتقِل إلى الدليل المحلي الذي تم فيه تخزين ملف Raw Depth ZIP.
  3. انقر مرّتين على الدليل arcore_rawdepthapi_codelab.

دليل arcore_rawdepthapi_codelab هو مشروع واحد من مشروع Gradle الذي يتضمّن وحدات متعددة. إذا لم يكن جزء "المشروع" في أعلى يمين "استوديو Android" معروضًا حاليًا في لوحة "المشروع"، انقر على المشاريع من القائمة المنسدلة.

يُفترض أن تبدو النتيجة على النحو التالي:

يحتوي هذا المشروع على الوحدات التالية:

  • part0_work: تطبيق إجراء التفعيل ويجب إجراء تعديلات على هذه الوحدة عند القيام بهذا الدرس التطبيقي حول الترميز. وتحتوي جميع الأجزاء الأخرى على رمز مرجعي.
  • part1: رمز مرجعي لما يجب أن تبدو عليه تعديلاتك عند إكمال الجزء الأول.
  • part2: الرمز المرجعي عند إكمال الجزء الثاني.
  • part3_completed: يمكنك إضافة رمز مرجعي عند إكمال الجزء الثالث، وهو نهاية الدرس التطبيقي حول الترميز.

يمكنك استخدام وحدة "part0_work". هناك أيضًا حلول كاملة لكل جزء من الدرس التطبيقي حول الترميز. كل وحدة هي تطبيق قابل للإنشاء.

4. تشغيل تطبيق إجراء التفعيل

اتّبِع الخطوات التالية لتشغيل تطبيق Raw Depth للمبتدئين.

  1. انتقل إلى تشغيل > تشغيل... &gt; "part0_work" (العمل_القطع).
  2. في مربّع الحوار اختيار هدف النشر، اختَر جهازك من قائمة الأجهزة المتصلة وانقر على حسنًا.

سينشئ Android Studio التطبيق الأولي ويشغِّله على جهازك.

عند تشغيل التطبيق لأول مرة، سيطلب إذن الكاميرا. انقر على سماح للمتابعة.

لا ينفّذ التطبيق أي إجراء في الوقت الحالي.هذا هو التطبيق الأساسي للواقع المعزّز، إذ يعرض لقطة للمشهد بدون فعل أي شيء آخر.الرمز الحالي مشابه لعيّنة Hello AR التي تم نشرها باستخدام ARCore SDK.

بعد ذلك، ستستخدم واجهة برمجة تطبيقات Raw Depth API لاسترداد الشكل الهندسي للمشهد من حولك.

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 Depth API صورة بعمق غير متجانس وصورة مقابلة لها ثقة تحتوي على درجة ثقة العمق لكل بكسل في صورة العمق الأولي. قم بتمكين Raw Depth من خلال تحديث التعليمة البرمجية التالية ضمن عبارة تجربة الالتقاط التي عدّلتها للتو.

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

تم الآن ضبط "جلسة الواقع المعزّز" بشكل مناسب، ويمكن للتطبيق استخدام الميزات المستنِدة إلى العمق.

استدعاء واجهة برمجة التطبيقات 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.

}

تُستخدم هذه الفئة لتحويل الصور ذات العمق إلى سحب نقطية. تمثل السحب النقطية هندسة المشهد بقائمة نقاط يحتوي كل منها على إحداثي ثلاثي الأبعاد (س، ص، ع) وقيمة ثقة في النطاق من 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(). تأخذ هذه الطريقة المساعِدة كل وحدة بكسل في صورة العمق وتستخدم العناصر الجوهرية للكاميرا لإلغاء عرض العمق في نقطة ثلاثية الأبعاد مرتبطة بالكاميرا. ويتم بعد ذلك استخدام ارتساء الكاميرا لتحويل موضع النقطة إلى إحداثيات للعالم. يتم تحويل كل وحدة بكسل موجودة إلى نقطة ثلاثية الأبعاد (بوحدات القياس) وتخزينها إلى جانب الثقة.

أضِف طريقة المساعدة التالية إلى 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 إلى وحدة معالجة الرسومات.

    /**
     * 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 على 5 بكسل في نموذج التطبيق.

إضافة أدوات تظليل جديدة

هناك العديد من الطرق لعرض بيانات العمق في تطبيقك. هنا، ستضيف بعض التظليل وإنشاء تصور بسيط لتعيين الألوان.

أضِف أدوات تظليل .vert و.frag جديدة إلى دليل src/main/assets/shaders/.

جارٍ إضافة أداة تظليل .فرت

في "استوديو Android"، اتّبِع الخطوات التالية:

  1. انقر بزر الماوس الأيمن على دليل أدوات التظليل.
  2. اختَر "جديد" -> الملف
  3. تسمية المكان "depth_point_cloud.vert"
  4. اضبطه كملف نصي.

في ملف .فر الجديد، أضف الرمز التالي:

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. تسترد ارتفاع كل نقطة (محور ص في إحداثيات العالم).
  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);

أضِف الرمز التالي في نهاية مجموعة التجربة داخل onDrawFrame لإظهار أحدث عمق للإطار الحالي.

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

من خلال هذه التغييرات، من المفترض أن يُنشئ التطبيق بنجاح ويُظهر سحابة النقاط العميقة.

مثال على تمثيل البيانات في السحابة الإلكترونية لنقطة العمق بشكل أولي

  • يتم تلوين كل عينة نقطة حسب العمق.
  • النقاط الحمراء قريبة، النقاط الخضراء/الزرقاء بعيدة
  • فقدان بعض البيانات أو "الثقوب" يمكن رؤيتها في المناطق التي لا توجد فيها ميزات صور كافية، مثل الجدران أو الأسقف البيضاء الفارغة.
  • يمكنك تعديل حجم النقاط المعروضة من خلال ضبط الخط GLES20.glUniform1f(pointSizeUniform, 5.0f); داخل DepthRenderer.draw(). ويظهر على اليسار حجما النقاط 5 و10.

7. تحليل السُحب الإلكترونية الثلاثية الأبعاد (الجزء 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;
 }

لن ترى الآن سوى نقاط الثقة العالية ونقاط الإغلاق.

فلترة المسافة

تجعل السحابة الإلكترونية النقطية في نطاق 1.5 متر من الكاميرا.

المقارنة بين النقاط والمستويات الثلاثية الأبعاد

يمكنك مقارنة النقاط والمستوى الهندسي باستخدامها لفلترة بعضها البعض، مثل إزالة النقاط القريبة من مستويات الواقع المعزّز المرصودة.

ستترك هذه الخطوة فقط "غير مستوية" والنقاط التي تميل إلى تمثيل الأسطح على الأجسام في البيئة المحيطة. أضِف الطريقة filterUsingPlanes() إلى أسفل فئة DepthData. تتكرر هذه الطريقة عبر النقاط الموجودة، وتتحقق من كل نقطة مقابل كل مستوى، ويلغي تنشيط أي نقطة قريبة جدًا من مستوى الواقع المعزّز، فتترك مناطق غير مستوية تبرز الكائنات في المشهد.

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

يؤدي تشغيل الدرس التطبيقي حول الترميز الآن إلى عرض مجموعة فرعية من النقاط. وتمثل هذه النقاط الأجسام في المشهد، وتتجاهل الأسطح المستوية التي توضع عليها هذه الأجسام. يمكنك استخدام هذه البيانات لتقدير حجم الكائنات وموضعها بتجميع النقاط معًا.

فنجان شاي

الميكروفون

سمّاعات رأس

وسادة

نقاط المجموعة

يتضمّن هذا الدرس التطبيقي حول الترميز خوارزمية تجميع 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 لتجميع سُحب النقاط التي تم استردادها في مجموعات، وعرض النتائج كمربّعات حدود محاذية للمحور.

      // 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 Depth من خلال جلسة ARCore، وتحويل معلومات العمق إلى سُحب إلكترونية ثلاثية الأبعاد، وإجراء عمليات التصفية والعرض الأساسية عند هذه النقاط.

8. Build-Run-Test

أنشئ تطبيقك وشغِّله واختبِره.

إنشاء تطبيقك وتشغيله

اتّبِع الخطوات التالية لإنشاء تطبيقك وتشغيله:

  1. وصِّل جهازًا متوافقًا مع ARCore عبر USB.
  2. قم بتشغيل مشروعك باستخدام الزر ► في شريط القوائم.
  3. انتظر حتى يتم إنشاء التطبيق ونشره على جهازك.

في المرة الأولى التي تحاول فيها نشر التطبيق على جهازك، عليك

السماح بتصحيح أخطاء الجهاز عبر USB

على الجهاز. انقر على "حسنًا" للمتابعة.

في المرة الأولى التي يتم فيها تشغيل تطبيقك على الجهاز، سيُطلب منك تحديد ما إذا كان لدى التطبيق الإذن باستخدام كاميرا الجهاز. يجب منح إذن الوصول لمواصلة استخدام وظائف الواقع المعزّز.

اختبار تطبيقك

عند تشغيل تطبيقك، يمكنك اختبار سلوكه الأساسي من خلال حمل جهازك والتحرك في أرجاء المساحة ومسح المنطقة ببطء. حاول جمع 10 ثوانٍ على الأقل من البيانات ومسح المنطقة من عدة اتجاهات قبل الانتقال إلى الخطوة التالية.

9. تهانينا

تهانينا، لقد نجحت في إنشاء وتشغيل أول تطبيق للواقع المعزّز المستند إلى العمق باستخدام واجهة برمجة التطبيقات ARCore Raw Depth API من Google. نحن متحمسون لرؤية ما ستنشئه!

10. تحديد المشاكل وحلّها

إعداد جهاز Android لإجراء التطوير

  1. وصِّل جهازك بجهاز التطوير باستخدام كابل USB. في حال تطوير البرامج باستخدام نظام التشغيل Windows، قد تحتاج إلى تثبيت برنامج تشغيل USB المناسب لجهازك.
  2. نفِّذ الخطوات التالية لتفعيل تصحيح أخطاء الجهاز عبر USB في نافذة خيارات المطوّرين:
  • افتح تطبيق الإعدادات.
  • إذا كان جهازك يعمل بالإصدار 8.0 من نظام التشغيل Android أو إصدار أحدث، اختَر النظام.
  • انتقِل إلى أسفل الصفحة واختَر لمحة عن الهاتف.
  • انتقِل إلى أسفل الصفحة وانقر على رقم الإصدار سبع مرات.
  • ارجع إلى الشاشة السابقة وانتقِل إلى أسفل الشاشة وانقر على خيارات المطوّرين.
  • في نافذة خيارات المطوّرين، انتقِل للأسفل للعثور على خيار تصحيح أخطاء 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

الأسئلة الشائعة