Die Depth API hilft der Kamera eines Geräts, die Größe und Form der realen Objekte in einer Szene zu erkennen. Dabei wird mithilfe der Kamera Tiefenbilder oder Tiefenkarten erstellt. So erhält deine App AR-realistische Erlebnisse. Mithilfe der Informationen eines Tiefenbildes können Sie virtuelle Objekte vor oder hinter realen Objekten exakt anzeigen lassen. So entstehen immersive und realistische User Experiences.
Tiefeninformationen werden aus Bewegung berechnet und können mit Informationen von einem Hardware-Tiefensensor wie einem Flugzeitsensor (ToF) kombiniert werden, sofern verfügbar. Geräte benötigen zur Unterstützung der Depth API keinen ToF-Sensor.
Vorbereitung
Machen Sie sich mit den grundlegenden AR-Konzepten vertraut. und Konfigurieren einer ARCore-Sitzung beschrieben, bevor du fortfährst.
Zugriff auf Geräte mit Tiefenunterstützung einschränken
Wenn für Ihre App Depth API-Unterstützung erforderlich ist, weil ein zentraler Bestandteil des
Das AR-Erlebnis erfordert Tiefe.
Teile der App mit Tiefendaten aus, können Sie den Vertrieb Ihrer App
im Google Play Store herunter, um
Geräte, die die Depth API unterstützen, durch Hinzufügen von
folgende Zeile in Ihre AndroidManifest.xml
ein, zusätzlich zum
AndroidManifest.xml
-Änderungen wie
Leitfaden zum Aktivieren von ARCore:
<uses-feature android:name="com.google.ar.core.depth" />
Tiefe aktivieren
Prüfen Sie in einer neuen ARCore-Sitzung, ob das Gerät des Nutzers „Tiefendaten“ unterstützt. Aufgrund von Einschränkungen bei der Prozessorleistung unterstützen nicht alle ARCore-kompatiblen Geräte die Depth API. Um Ressourcen zu sparen, ist die Tiefe in ARCore standardmäßig deaktiviert. Aktivieren Sie den Tiefenmodus, damit Ihre App die Depth API verwenden kann.
Java
Config config = session.getConfig(); // Check whether the user's device supports the Depth API. boolean isDepthSupported = session.isDepthModeSupported(Config.DepthMode.AUTOMATIC); if (isDepthSupported) { config.setDepthMode(Config.DepthMode.AUTOMATIC); } session.configure(config);
Kotlin
val config = session.config // Check whether the user's device supports the Depth API. val isDepthSupported = session.isDepthModeSupported(Config.DepthMode.AUTOMATIC) if (isDepthSupported) { config.depthMode = Config.DepthMode.AUTOMATIC } session.configure(config)
Tiefenaufnahmen aufnehmen
Rufen Sie Frame.acquireDepthImage16Bits()
auf, um das Tiefenbild für den aktuellen Frame abzurufen.
Java
// Retrieve the depth image for the current frame, if available. Image depthImage = null; try { depthImage = frame.acquireDepthImage16Bits(); // Use the depth image here. } catch (NotYetAvailableException e) { // This means that depth data is not available yet. // Depth data will not be available if there are no tracked // feature points. This can happen when there is no motion, or when the // camera loses its ability to track objects in the surrounding // environment. } finally { if (depthImage != null) { depthImage.close(); } }
Kotlin
// Retrieve the depth image for the current frame, if available. try { frame.acquireDepthImage16Bits().use { depthImage -> // Use the depth image here. } } catch (e: NotYetAvailableException) { // This means that depth data is not available yet. // Depth data will not be available if there are no tracked // feature points. This can happen when there is no motion, or when the // camera loses its ability to track objects in the surrounding // environment. }
Das zurückgegebene Bild stellt den Zwischenspeicher des Rohbilds bereit, der zur Verwendung auf der GPU für jedes zu verdeckende gerenderte Objekt an einen Fragment-Shader übergeben werden kann. Sie ist auf OPENGL_NORMALIZED_DEVICE_COORDINATES
ausgerichtet und kann durch Aufrufen von Frame.transformCoordinates2d()
in TEXTURE_NORMALIZED
geändert werden. Sobald das Tiefenbild in einem Objekt-Shader zugänglich ist, kann direkt auf diese Tiefenmessungen zugegriffen werden, um Verdeckungen zu verarbeiten.
Tiefenwerte
Angegebener Punkt A
in der beobachteten realen Geometrie und ein 2D-Punkt a
denselben Punkt im Tiefenbild darstellt, wird der Wert, der durch das
Die API in a
entspricht der Länge von CA
, die auf die Hauptachse projiziert wird.
Dies kann auch als Z-Koordinate von A
relativ zur Kamera bezeichnet werden.
Ursprung C
. Wenn Sie mit der Depth API arbeiten,
sollten Sie wissen,
Die Tiefenwerte entsprechen nicht der Länge des Strahls CA
selbst, sondern der Projektion.
davon.
Tiefe in Shadern verwenden
Tiefeninformationen für den aktuellen Frame parsen
Verwenden Sie die Hilfsfunktionen DepthGetMillimeters()
und DepthGetVisibility()
in einem Fragment-Shader, um auf die Tiefeninformationen für die aktuelle Bildschirmposition zuzugreifen. Verwenden Sie diese Informationen dann, um Teile des gerenderten Objekts selektiv zu verdecken.
// Use DepthGetMillimeters() and DepthGetVisibility() to parse the depth image
// for a given pixel, and compare against the depth of the object to render.
float DepthGetMillimeters(in sampler2D depth_texture, in vec2 depth_uv) {
// Depth is packed into the red and green components of its texture.
// The texture is a normalized format, storing millimeters.
vec3 packedDepthAndVisibility = texture2D(depth_texture, depth_uv).xyz;
return dot(packedDepthAndVisibility.xy, vec2(255.0, 256.0 * 255.0));
}
// Return a value representing how visible or occluded a pixel is relative
// to the depth image. The range is 0.0 (not visible) to 1.0 (completely
// visible).
float DepthGetVisibility(in sampler2D depth_texture, in vec2 depth_uv,
in float asset_depth_mm) {
float depth_mm = DepthGetMillimeters(depth_texture, depth_uv);
// Instead of a hard Z-buffer test, allow the asset to fade into the
// background along a 2 * kDepthTolerancePerMm * asset_depth_mm
// range centered on the background depth.
const float kDepthTolerancePerMm = 0.015f;
float visibility_occlusion = clamp(0.5 * (depth_mm - asset_depth_mm) /
(kDepthTolerancePerMm * asset_depth_mm) + 0.5, 0.0, 1.0);
// Use visibility_depth_near to set the minimum depth value. If using
// this value for occlusion, avoid setting it too close to zero. A depth value
// of zero signifies that there is no depth data to be found.
float visibility_depth_near = 1.0 - InverseLerp(
depth_mm, /*min_depth_mm=*/150.0, /*max_depth_mm=*/200.0);
// Use visibility_depth_far to set the maximum depth value. If the depth
// value is too high (outside the range specified by visibility_depth_far),
// the virtual object may get inaccurately occluded at further distances
// due to too much noise.
float visibility_depth_far = InverseLerp(
depth_mm, /*min_depth_mm=*/7500.0, /*max_depth_mm=*/8000.0);
const float kOcclusionAlpha = 0.0f;
float visibility =
max(max(visibility_occlusion, kOcclusionAlpha),
max(visibility_depth_near, visibility_depth_far));
return visibility;
}
Virtuelle Objekte ausschließen
Schließen Sie virtuelle Objekte in den Textkörper des Fragment-Shaders ein. Aktualisiert den Alphakanal des Objekts anhand seiner Tiefe. Dadurch wird ein verdecktes Objekt gerendert.
// Occlude virtual objects by updating the object’s alpha channel based on its depth.
const float kMetersToMillimeters = 1000.0;
float asset_depth_mm = v_ViewPosition.z * kMetersToMillimeters * -1.;
// Compute the texture coordinates to sample from the depth image.
vec2 depth_uvs = (u_DepthUvTransform * vec3(v_ScreenSpacePosition.xy, 1)).xy;
gl_FragColor.a *= DepthGetVisibility(u_DepthTexture, depth_uvs, asset_depth_mm);
Sie können Verdeckungen mit 2-Pass-Rendering oder pro Objekt mit Vorwärtsdurchlauf rendern. Die Effizienz der einzelnen Ansätze hängt von der Komplexität der Szene und weiteren anwendungsspezifischen Aspekten ab.
Vorwärtsdurchlauf-Rendering pro Objekt
Beim Vorwärtsdurchlauf-Rendering wird pro Objekt die Verdeckung jedes Pixels des Objekts in seinem Material-Shader bestimmt. Wenn die Pixel nicht sichtbar sind, werden sie abgeschnitten, in der Regel durch Alpha-Überblendung, um eine Verdeckung auf dem Gerät des Nutzers zu simulieren.
Rendering mit zwei Durchgängen
Beim Rendering mit zwei Durchgängen rendert der erste Durchlauf den gesamten virtuellen Inhalt in einem Zwischenpuffer. Beim zweiten Pass wird die virtuelle Szene basierend auf dem Unterschied zwischen der realen und der virtuellen Szenentiefe mit dem Hintergrund verschmelzen lassen. Dieser Ansatz erfordert keine zusätzliche objektspezifische Shader-Arbeit und liefert im Allgemeinen einheitlichere Ergebnisse als die Vorwärtspass-Methode.
Entfernung aus einem Tiefenbild extrahieren
Wenn Sie die Depth API zu anderen Zwecken als zum Verdecken virtueller Objekte oder zum Visualisieren von Tiefendaten verwenden möchten, extrahieren Sie Informationen aus dem Tiefenbild.
Java
/** Obtain the depth in millimeters for depthImage at coordinates (x, y). */ public int getMillimetersDepth(Image depthImage, int x, int y) { // The depth image has a single plane, which stores depth for each // pixel as 16-bit unsigned integers. Image.Plane plane = depthImage.getPlanes()[0]; int byteIndex = x * plane.getPixelStride() + y * plane.getRowStride(); ByteBuffer buffer = plane.getBuffer().order(ByteOrder.nativeOrder()); return Short.toUnsignedInt(buffer.getShort(byteIndex)); }
Kotlin
/** Obtain the depth in millimeters for [depthImage] at coordinates ([x], [y]). */ fun getMillimetersDepth(depthImage: Image, x: Int, y: Int): UInt { // The depth image has a single plane, which stores depth for each // pixel as 16-bit unsigned integers. val plane = depthImage.planes[0] val byteIndex = x * plane.pixelStride + y * plane.rowStride val buffer = plane.buffer.order(ByteOrder.nativeOrder()) val depthSample = buffer.getShort(byteIndex) return depthSample.toUInt() }
Koordinaten zwischen Kamerabildern und Tiefenbildern konvertieren
Mit getCameraImage()
aufgenommene Bilder haben möglicherweise ein anderes Seitenverhältnis als Tiefenaufnahmen.
In diesem Fall ist das Tiefenbild ein Ausschnitt des Kamerabilds und nicht alle Pixel im Kamerabild haben eine entsprechende gültige Tiefenschätzung.
So erhalten Sie Tiefenbildkoordinaten für Koordinaten im CPU-Bild:
Java
float[] cpuCoordinates = new float[] {cpuCoordinateX, cpuCoordinateY}; float[] textureCoordinates = new float[2]; frame.transformCoordinates2d( Coordinates2d.IMAGE_PIXELS, cpuCoordinates, Coordinates2d.TEXTURE_NORMALIZED, textureCoordinates); if (textureCoordinates[0] < 0 || textureCoordinates[1] < 0) { // There are no valid depth coordinates, because the coordinates in the CPU image are in the // cropped area of the depth image. return null; } return new Pair<>( (int) (textureCoordinates[0] * depthImage.getWidth()), (int) (textureCoordinates[1] * depthImage.getHeight()));
Kotlin
val cpuCoordinates = floatArrayOf(cpuCoordinateX.toFloat(), cpuCoordinateY.toFloat()) val textureCoordinates = FloatArray(2) frame.transformCoordinates2d( Coordinates2d.IMAGE_PIXELS, cpuCoordinates, Coordinates2d.TEXTURE_NORMALIZED, textureCoordinates, ) if (textureCoordinates[0] < 0 || textureCoordinates[1] < 0) { // There are no valid depth coordinates, because the coordinates in the CPU image are in the // cropped area of the depth image. return null } return (textureCoordinates[0] * depthImage.width).toInt() to (textureCoordinates[1] * depthImage.height).toInt()
So erhalten Sie CPU-Bildkoordinaten für Tiefenbildkoordinaten:
Java
float[] textureCoordinates = new float[] { (float) depthCoordinateX / (float) depthImage.getWidth(), (float) depthCoordinateY / (float) depthImage.getHeight() }; float[] cpuCoordinates = new float[2]; frame.transformCoordinates2d( Coordinates2d.TEXTURE_NORMALIZED, textureCoordinates, Coordinates2d.IMAGE_PIXELS, cpuCoordinates); return new Pair<>((int) cpuCoordinates[0], (int) cpuCoordinates[1]);
Kotlin
val textureCoordinates = floatArrayOf( depthCoordinatesX.toFloat() / depthImage.width.toFloat(), depthCoordinatesY.toFloat() / depthImage.height.toFloat(), ) val cpuCoordinates = FloatArray(2) frame.transformCoordinates2d( Coordinates2d.TEXTURE_NORMALIZED, textureCoordinates, Coordinates2d.IMAGE_PIXELS, cpuCoordinates, ) return cpuCoordinates[0].toInt() to cpuCoordinates[1].toInt()
Tiefen-Treffertest
Treffertests ermöglichen es Nutzern, Objekte an einem realen Ort in der Szene zu platzieren. Bisher konnten Treffertests nur auf erkannten Flugzeugen durchgeführt werden, wobei die Standorte auf große, flache Oberflächen beschränkt waren, wie die Ergebnisse der grünen Android-Geräte. Bei Tiefen-Treffertests werden sowohl glatte als auch rohe Tiefeninformationen genutzt, um genauere Trefferergebnisse zu liefern, selbst auf nicht planaren Oberflächen und Oberflächen mit geringer Textur. Dies wird durch die roten Android-Symbole dargestellt.
<ph type="x-smartling-placeholder">
Wenn Sie tiefengestützte Treffertests verwenden möchten, rufen Sie hitTest()
auf und suchen Sie in der Rückgabeliste nach DepthPoints
.
Java
// Create a hit test using the Depth API. List<HitResult> hitResultList = frame.hitTest(tap); for (HitResult hit : hitResultList) { Trackable trackable = hit.getTrackable(); if (trackable instanceof Plane || trackable instanceof Point || trackable instanceof DepthPoint) { useHitResult(hit); break; } }
Kotlin
// Create a hit test using the Depth API. val hitResult = frame .hitTest(tap) .filter { val trackable = it.trackable trackable is Plane || trackable is Point || trackable is DepthPoint } .firstOrNull() useHitResult(hitResult)
Weiteres Vorgehen
- Ermöglichen Sie eine genauere Erkennung mit der Raw Depth API.
- Im ARCore Depth Lab werden verschiedene Möglichkeiten für den Zugriff auf detaillierte Daten vorgestellt.