1. مقدمة
ARCore هي منصة لإنشاء تطبيقات الواقع المعزّز (AR) على الأجهزة الجوّالة. تتيح ARCore Depth API من Google إمكانية الوصول إلى صورة عمق لكل إطار في جلسة ARCore. يوفّر كل بكسل في صورة العمق قياسًا للمسافة من الكاميرا إلى البيئة المحيطة.
توفّر واجهة برمجة التطبيقات Raw Depth API صور عمق لا يتم تمريرها من خلال عمليات فلترة مساحة الشاشة المصمَّمة لتسوية النتائج واستيفائها. هذه القيم أكثر دقة من الناحية الهندسية، ولكنّها قد تتضمّن بيانات ناقصة وقد تكون أقل تطابقًا مع صورة الكاميرا المرتبطة بها.
يوضّح هذا الدرس التطبيقي حول الترميز كيفية استخدام Raw Depth API لإجراء تحليل هندسي ثلاثي الأبعاد للمشهد. ستنشئ تطبيقًا بسيطًا متوافقًا مع الواقع المعزّز يستخدم بيانات العمق الأولية لرصد أشكال العالم وعرضها.
لا تتوافق واجهتا برمجة التطبيقات Depth وRaw Depth إلا مع مجموعة فرعية من الأجهزة المتوافقة مع ARCore. تتوفّر Depth API على Android فقط.
ما ستنشئه
في هذا الدرس التطبيقي، ستنشئ تطبيقًا يستخدم صور العمق الأولية لكل إطار لإجراء تحليل هندسي للعالم من حولك. سيؤدي هذا التطبيق إلى ما يلي:
- تحقَّق مما إذا كان الجهاز المستهدف يتيح استخدام ميزة "العمق".
- استرجِع صورة العمق الأولية لكل إطار كاميرا.
- إعادة عرض صور العمق الأولية في نقاط ثلاثية الأبعاد وفلترة هذه النقاط استنادًا إلى الثقة والشكل الهندسي
- استخدِم مجموعة نقاط العمق الأولية لتقسيم الكائنات الثلاثية الأبعاد التي تهمّك.
|
نظرة خاطفة على ما ستنشئه |
ملاحظة: إذا واجهت مشاكل أثناء تنفيذ الخطوات، انتقِل إلى القسم الأخير للاطّلاع على بعض النصائح لتحديد المشاكل وحلّها.
2. المتطلبات الأساسية
ستحتاج إلى أجهزة وبرامج معيّنة لإكمال هذا الدرس التطبيقي حول الترميز.
متطلبات الأجهزة
- جهاز متوافق مع ARCore تم تفعيل خيار تصحيح أخطاء الجهاز عبر USB عليه، وتم توصيله بجهاز التطوير باستخدام كابل USB يجب أن يكون هذا الجهاز متوافقًا أيضًا مع Depth API.
متطلبات البرامج
- الإصدار 1.31.0 من حزمة تطوير البرامج (SDK) في ARCore أو إصدار أحدث
- جهاز تطوير مثبَّت عليه استوديو Android (الإصدار 4.0.1 أو إصدار أحدث)
3- إعداد
إعداد جهاز التطوير
وصِّل جهاز ARCore بالكمبيوتر باستخدام كابل USB. تأكَّد من أنّ جهازك يسمح بتصحيح أخطاء الجهاز عبر USB. افتح وحدة طرفية ونفِّذ الأمر adb devices، كما هو موضّح أدناه:
adb devices List of devices attached <DEVICE_SERIAL_NUMBER> device
سيكون <DEVICE_SERIAL_NUMBER> سلسلة فريدة لجهازك. تأكَّد من ظهور جهاز واحد فقط قبل المتابعة.
تنزيل الرمز وتثبيته
يمكنك إما استنساخ المستودع:
git clone https://github.com/googlecodelabs/arcore-rawdepthapi
أو نزِّل ملف ZIP واستخرِج ملفاته:
اتّبِع الخطوات التالية لبدء العمل باستخدام الرمز.
- شغِّل "استوديو Android" واختَر فتح مشروع حالي في "استوديو Android".
- انتقِل إلى الدليل المحلي الذي خزّنت فيه ملف Raw Depth ZIP.
- انقر مرّتين على الدليل
arcore_rawdepthapi_codelab.
الدليل arcore_rawdepthapi_codelab هو مشروع Gradle واحد يتضمّن وحدات متعددة. إذا لم تكن لوحة "المشروع" في أعلى يمين "استوديو Android" معروضة في لوحة "المشروع"، انقر على المشاريع من القائمة المنسدلة.
يجب أن تبدو النتيجة على النحو التالي:
| يحتوي هذا المشروع على الوحدات التالية:
|
ستعمل في الوحدة part0_work. تتوفّر أيضًا حلول كاملة لكل جزء من الدرس العملي. كل وحدة هي تطبيق قابل للإنشاء.
4. تشغيل التطبيق النموذجي
اتّبِع الخطوات التالية لتشغيل تطبيق Raw Depth التجريبي.
- انتقِل إلى تشغيل > تشغيل... > ‘part0_work'.
- في مربّع الحوار اختيار هدف النشر، اختَر جهازك من قائمة الأجهزة المتصلة وانقر على حسنًا.
سينشئ استوديو Android التطبيق الأوّلي ويشغّله على جهازك.
| عند تشغيل التطبيق للمرة الأولى، سيطلب الإذن CAMERA. انقر على سماح للمتابعة. |
| في الوقت الحالي، لا يفعل التطبيق أي شيء.هذا هو أبسط تطبيق للواقع المعزّز، حيث يعرض شاشة الكاميرا لمشهدك، ولكن لا يفعل أي شيء آخر.يشبه الرمز الحالي نموذج Hello AR المنشور مع حزمة تطوير البرامج (SDK) الخاصة بـ ARCore. |
بعد ذلك، ستستخدم Raw Depth API لاسترداد شكل المشهد الهندسي من حولك.
5- إعداد Raw Depth API (الجزء 1)
التأكّد من أنّ جهاز الاختبار متوافق مع Depth
لا يمكن تشغيل Depth API على جميع الأجهزة المتوافقة مع ARCore. تأكَّد من أنّ جهاز الاختبار يتيح استخدام Depth قبل إضافة وظيفة إلى تطبيقك داخل الدالة 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-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;
}
تم الآن ضبط "جلسة الواقع المعزّز" بشكلٍ مناسب، ويمكن للتطبيق استخدام الميزات المستندة إلى العمق.
استدعاء 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()طريقة في أسفل الفئة. تستعلم هذه الطريقة عن أحدث صور العمق والدقة، وتخزّن السحابة النقطية الناتجة. ستتضمّن صور العمق وصور الثقة بيانات متطابقة.
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;
}
|
|
|
|
|
|
|
|
يخزّن الرمز أيضًا نقطة ربط الكاميرا في هذا الوقت، حتى يمكن تحويل معلومات العمق إلى إحداثيات عالمية من خلال استدعاء طريقة مساعدة 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
|
املأ هذه الفئة بالرمز التالي:
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. تأخذ هذه الطريقة أحدث معلومات العمق كمدخلات وتنسخ بيانات السحابة النقطية إلى وحدة معالجة الرسومات.
/**
* 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/.
| إضافة برنامج تظليل .vert جديدفي "استوديو Android"، اتّبِع الخطوات التالية:
|
في ملف .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 لتحسين التمثيل المرئي. وينفِّذ الخطوات التالية:
- تعرض هذه السمة ارتفاع كل نقطة (المحور الصادي في إحداثيات العالم).
- تحسب هذه السمة لونًا مرتبطًا بهذا الارتفاع (أحمر=منخفض، أزرق=مرتفع).
- تحسب هذه الطريقة موضع كل نقطة على الشاشة.
- تضبط هذه السمة حجم كل نقطة (بالبكسل)، كما هو محدّد في الطريقة
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);
بعد إجراء هذه التغييرات، من المفترض أن يتم إنشاء التطبيق بنجاح وعرض سحابة النقاط الخاصة بالعمق.
| مثال على عرض مرئي لبيانات السحابة النقطية الأولية للعمق
|
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);
سيؤدي تشغيل الدرس التطبيقي حول الترميز الآن إلى عرض مجموعة فرعية من النقاط. تمثّل هذه النقاط العناصر في المشهد، مع تجاهل الأسطح المستوية التي تستند إليها العناصر. يمكنك استخدام هذه البيانات لتقدير حجم وموضع العناصر من خلال تجميع النقاط معًا.
|
|
|
|
كوب شاي | الميكروفون | سمّاعات رأس | وسادة |
نقاط التجميع
يتضمّن هذا الدرس التطبيقي حول الترميز خوارزمية بسيطة جدًا لتجميع النقاط في السحابة. عدِّل الدرس العملي لتقسيم السحب النقطية التي تم استرجاعها إلى مجموعات محدّدة بمربّعات حدودية متوازية المحاور.
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. إنشاء-تشغيل-اختبار
إنشاء تطبيقك وتشغيله واختباره
إنشاء تطبيقك وتشغيله
اتّبِع الخطوات التالية لإنشاء تطبيقك وتشغيله:
- وصِّل جهازًا متوافقًا مع ARCore عبر USB.
- نفِّذ مشروعك باستخدام الزر "►" في شريط القوائم.
- انتظِر إلى أن يتم إنشاء التطبيق ونشره على جهازك.
في المرة الأولى التي تحاول فيها نشر التطبيق على جهازك، عليك
السماح بتصحيح أخطاء الجهاز عبر USB
على الجهاز انقر على "حسنًا" للمتابعة.
في المرة الأولى التي تشغّل فيها تطبيقك على الجهاز، سيُطلب منك تحديد ما إذا كان التطبيق لديه الإذن باستخدام كاميرا جهازك. يجب السماح بالوصول لمواصلة استخدام وظائف الواقع المعزّز.
اختبار تطبيقك
عند تشغيل تطبيقك، يمكنك اختبار سلوكه الأساسي من خلال حمل جهازك والتنقّل في محيطك ومسح منطقة ضوئيًا ببطء. حاوِل جمع بيانات لمدة 10 ثوانٍ على الأقل ومسح المنطقة من عدّة اتجاهات قبل الانتقال إلى الخطوة التالية.
9- تهانينا
تهانينا، لقد أنشأت تطبيق الواقع المعزّز الأول المستند إلى العمق وشغّلته بنجاح باستخدام واجهة برمجة التطبيقات "العمق الأولي" في ARCore من Google. نحن متحمّسون لرؤية ما ستنشئه.
10. تحديد المشاكل وحلّها
إعداد جهاز Android للتطوير
- وصِّل جهازك بجهاز التطوير باستخدام كابل USB. إذا كنت تستخدم نظام التشغيل Windows في عملية التطوير، قد تحتاج إلى تثبيت برنامج تشغيل USB المناسب لجهازك.
- اتّبِع الخطوات التالية لتفعيل تصحيح أخطاء الجهاز عبر 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

























