1. 簡介
ARCore 平台可讓您在行動裝置上建構擴增實境 (AR) 應用程式。Google 的 ARCore Depth API 可存取 ARCore 工作階段中每個影格的深度圖像。深度影像中的每個像素都會提供攝影機與環境之間的距離測量結果。
Raw Depth API 會提供未經過螢幕空間篩選作業的深度圖片,這類作業旨在平滑處理結果並進行插補。這些值在幾何上更準確,但可能缺少資料,且與相關聯的攝影機影像較不一致。
本程式碼研究室會展示如何使用 Raw Depth API 分析場景的 3D 幾何結構。您將建構簡單的 AR 應用程式,使用原始深度資料偵測及顯示世界幾何結構。
深度和原始深度 API 僅支援部分支援 ARCore 的裝置。Depth API 僅適用於 Android。
建構項目
在本程式碼研究室中,您將建構應用程式,使用每個影格的原始深度圖片,對周遭世界執行幾何分析。這個應用程式將:
- 確認目標裝置是否支援深度。
- 擷取每個攝影機影格的原始深度影像。
- 將原始深度圖像重新投影至 3D 點,並根據信賴度和幾何形狀篩選這些點。
- 使用原始深度點雲,區隔感興趣的 3D 物件。
|
搶先預覽您要建構的內容。 |
注意:如果過程中發生問題,請跳至最後一節,查看疑難排解提示。
2. 先決條件
您需要特定硬體和軟體才能完成這個程式碼研究室。
硬體需求
- 已啟用 USB 偵錯功能的支援 ARCore 的裝置,並透過 USB 傳輸線連接至開發機器。這部裝置也必須支援 Depth API。
軟體需求
- ARCore SDK 1.31.0 以上版本。
- 已安裝 Android Studio (4.0.1 以上版本) 的開發機器。
3. 設定
設定開發機器
使用 USB 傳輸線將 ARCore 裝置連接至電腦。確認裝置允許 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 Studio,然後選擇「Open an existing Android Studio project」。
- 前往儲存原始深度 ZIP 檔案的本機目錄。
- 按兩下
arcore_rawdepthapi_codelab目錄。
arcore_rawdepthapi_codelab 目錄是具有多個模組的單一 Gradle 專案。如果 Android Studio 左上方的「Project」窗格尚未顯示,請按一下下拉式選單中的「Projects」。
結果看起來會像這樣:
| 這個專案包含下列模組:
|
您將在 part0_work 模組中工作。此外,本程式碼研究室的每個部分都有完整解決方案。每個模組都是可建構的應用程式。
4. 執行範例應用程式
請按照下列步驟執行 Raw Depth 啟動應用程式。
- 依序前往「Run」>「Run...」> ‘part0_work'。
- 在「Select Deployment Target」對話方塊中,從「Connected Devices」清單選取裝置,然後按一下「OK」。
Android Studio 會建構初始應用程式,並在裝置上執行。
| 首次執行應用程式時,系統會要求 CAMERA 權限。輕觸「允許」即可繼續。 |
| 目前,這個應用程式不會執行任何動作。這是最基本的 AR 應用程式,會顯示場景的攝影機畫面,但不會執行任何其他動作。現有程式碼與 ARCore SDK 發布的 Hello AR 範例類似。 |
接著,您將使用 Raw Depth API 擷取周圍場景的幾何結構。
5. 設定 Raw Depth API (第 1 部分)
確認目標裝置支援深度資訊
並非所有支援 ARCore 的裝置都能執行 Depth API。在 RawDepthCodelabActivity.java 的 onResume() 函式中建立新的 Session 之前,請先確認目標裝置支援深度。
找出現有程式碼:
// 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 會提供未經過平滑處理的深度圖像,以及相應的置信度圖像,其中包含原始深度圖像中每個像素的深度置信度。更新剛才修改的 try-catch 陳述式下方的程式碼,啟用原始深度。
try {
// ************ New code to add ***************
// Enable raw depth estimation and auto focus mode while ARCore is running.
Config config = session.getConfig();
config.setDepthMode(Config.DepthMode.RAW_DEPTH_ONLY);
config.setFocusMode(Config.FocusMode.AUTO);
session.configure(config);
// ************ End new code to add ***************
session.resume();
} catch (CameraNotAvailableException e) {
messageSnackbarHelper.showError(this, "Camera not available. Try restarting the app.");
session = null;
return;
}
現在 AR 階段作業已正確設定,應用程式可以使用以深度為基礎的功能。
呼叫 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.
}
這個類別用於將深度影像轉換為點雲。點雲代表場景幾何,內含點清單,每個點都有 3D 座標 (x、y、z) 和介於 0 到 1 之間的信賴度值。
在類別底部新增 create() 方法,使用 Raw Depth API 新增呼叫來填入這些值。這個方法會查詢最新的深度和信賴度圖片,並儲存產生的點雲。深度和信賴度圖片會包含相符的資料。
public static FloatBuffer create(Frame frame, Anchor cameraPoseAnchor) {
try {
Image depthImage = frame.acquireRawDepthImage16Bits();
Image confidenceImage = frame.acquireRawDepthConfidenceImage();
// Retrieve the intrinsic camera parameters corresponding to the depth image to
// transform 2D depth pixels into 3D points. See more information about the depth values
// at
// https://developers.google.com/ar/develop/java/depth/overview#understand-depth-values.
final CameraIntrinsics intrinsics = frame.getCamera().getTextureIntrinsics();
float[] modelMatrix = new float[16];
cameraPoseAnchor.getPose().toMatrix(modelMatrix, 0);
final FloatBuffer points = convertRawDepthImagesTo3dPointBuffer(
depthImage, confidenceImage, intrinsics, modelMatrix);
depthImage.close();
confidenceImage.close();
return points;
} catch (NotYetAvailableException e) {
// This normally means that depth data is not available yet.
// This is normal, so you don't have to spam the logcat with this.
}
return null;
}
|
|
|
|
|
|
|
|
程式碼也會在這個時間點儲存攝影機錨點,以便呼叫輔助方法 convertRawDepthImagesTo3dPointBuffer(),將深度資訊轉換為世界座標。這個輔助方法會擷取深度影像中的每個像素,並使用相機內部參數,將深度反向投影到相對於相機的 3D 點。接著,相機錨點會將該點的位置轉換為世界座標。每個現有像素都會轉換為 3D 點 (以公尺為單位),並與其信賴度一併儲存。
在 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");
}
}
算繪深度資料
接著,請提供算繪著色器的來源。在 DepthRenderer 類別底部新增下列 update() 方法。這個方法會將最新的深度資訊做為輸入內容,並將點雲資料複製到 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");
}
在 DepthRenderer 類別底部新增 draw() 方法,將最新資料繪製到畫面上。這個方法會取得 3D 點雲資訊,並將其投影回攝影機檢視畫面,以便在螢幕上算繪。
/** 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 Studio 中:
|
在新的 .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 色彩對應,可提升視覺化效果。這個檔案會執行下列步驟:
- 擷取每個點的海拔高度 (世界座標中的 Y 軸)。
- 計算與該高度相關聯的顏色 (紅色=低,藍色=高)。
- 計算每個點的螢幕位置。
- 為每個點設定大小 (以像素為單位),如
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;
}
如要套用這項算繪作業,請在 RawDepthCodelabActivity 內將呼叫新增至 DepthRenderer 類別。
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 內的 try-catch 區塊結尾處新增下列程式碼,顯示目前影格的最新深度。
// Visualize depth points.
depthRenderer.update(points);
depthRenderer.draw(camera);
完成這些變更後,應用程式現在應該可以順利建構,並顯示深度點雲。
| 原始深度點雲視覺化範例
|
7. 分析 3D 點雲 (第 3 部分)
確認 AR 工作階段中存在深度資料後,即可進行分析。分析深度的重要工具是每個像素的信賴度值。使用信賴度值分析 3D 點雲。
使低信賴度像素失效
您已擷取每個深度像素的信賴度值,並將其與 DepthData 內的每個點一併儲存,但尚未加以使用。
confidenceNormalized 的值介於 0 到 1 之間,0 表示信心度低,1 表示信心度高。修改 DepthData 類別中的 convertRawDepthImagesTo3dPointBuffer() 方法,避免儲存信心度過低而無用的像素。
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 公尺內。 |
比較 3D 點和平面
您可以比較幾何圖形 3D 點和平面,並使用這些資訊相互篩選,例如移除靠近觀察到的 AR 平面的點。
這個步驟只會留下「非平面」點,這些點通常代表環境中物體的表面。在 DepthData 類別底部新增 filterUsingPlanes() 方法。這個方法會逐一檢查現有點,並比對每個平面,然後撤銷太靠近 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);
最後,在 RawDepthCodelabActivity 內的 onDrawFrame() 中加入下列程式碼行,將擷取的點雲分組到叢集中,並以軸對齊的邊界方塊呈現結果。
// 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 工作階段擷取原始深度,將深度資訊轉換為 3D 點雲,並對這些點執行基本篩選和算繪作業。
8. 建構、執行、測試
建構、執行及測試應用程式。
建構並執行應用程式
請按照下列步驟建構及執行應用程式:
- 透過 USB 插入支援 ARCore 的裝置。
- 按一下選單列中的「►」按鈕,執行專案。
- 等待應用程式建構完成並部署到裝置。
首次嘗試將應用程式部署至裝置時,您需要
允許 USB 偵錯
在裝置上。選取「確定」即可繼續操作。
首次在裝置上執行應用程式時,系統會詢問應用程式是否具有裝置相機的使用權限。如要繼續使用 AR 功能,請務必允許存取權。
測試應用程式
執行應用程式時,您可以握住裝置、在空間中移動,並緩慢掃描某個區域,測試應用程式的基本行為。請嘗試收集至少 10 秒的資料,並從多個方向掃描該區域,再進行下一個步驟。
9. 恭喜
恭喜,您已成功使用 Google 的 ARCore Raw Depth API,建構及執行第一個以深度為基礎的擴增實境應用程式。我們很期待看到您的成果!
10. 疑難排解
設定 Android 裝置以進行開發
- 使用 USB 傳輸線將裝置連接至開發機器。如果您使用 Windows 進行開發,可能需要為裝置安裝適當的 USB 驅動程式。
- 在「開發人員選項」視窗中執行下列步驟,啟用「USB 偵錯」:
- 開啟「設定」應用程式。
- 如果裝置使用 Android 8.0 以上版本,請選取「系統」。
- 捲動至底部,然後選取「關於手機」。
- 捲動至底部,然後輕觸「版本號碼」七下。
- 返回上一個畫面並捲動至底部,然後輕觸「開發人員選項」。
- 在「開發人員選項」視窗中,向下捲動並啟用「USB 偵錯」。
如要進一步瞭解這個程序,請前往 Google 的 Android 開發人員網站。
與授權相關的建構失敗
如果遇到與授權相關的建構失敗問題 (Failed to install the following Android SDK packages as some licences have not been accepted),可以使用下列指令查看及接受這些授權:
cd <path to Android SDK>
tools/bin/sdkmanager --licenses

























