Utilizzare la profondità nell'app per Android

L'API Profondità aiuta la fotocamera di un dispositivo a comprendere le dimensioni e la forma degli oggetti reali in una scena. Utilizza la fotocamera per creare immagini di profondità o mappe di profondità, aggiungendo così un livello di realismo AR alle tue app. Puoi utilizzare le informazioni fornite da un'immagine di profondità per fare in modo che gli oggetti virtuali appaiono con precisione davanti o dietro a oggetti del mondo reale, offrendo esperienze utente coinvolgenti e realistiche.

Le informazioni sulla profondità vengono calcolate in base al movimento e potrebbero essere combinate con le informazioni fornite da un sensore di profondità hardware, come un sensore del tempo di volo (ToF), se disponibile. Un dispositivo non ha bisogno di un sensore ToF per supportare l'API Profondità.

Prerequisiti

Assicurati di aver compreso i concetti fondamentali di AR e di configurare una sessione ARCore prima di procedere.

Limitare l'accesso ai dispositivi che supportano la profondità

Se la tua app richiede il supporto dell'API Profondità, perché una parte principale dell'esperienza AR si basa sulla profondità o perché non esiste un fallback pratico per le parti dell'app che utilizzano la profondità, puoi scegliere di limitare la distribuzione della tua app nel Google Play Store ai dispositivi che supportano l'API Profondità aggiungendo la seguente riga a AndroidManifest.xml, oltre alle modifiche di AndroidManifest.xml descritte nella guida:

<uses-feature android:name="com.google.ar.core.depth" />

Attiva profondità

In una nuova sessione ARCore, controlla se il dispositivo di un utente supporta la profondità. Non tutti i dispositivi compatibili con ARCore supportano l'API Profondità a causa dei vincoli della potenza di elaborazione. Per risparmiare risorse, la profondità è disattivata per impostazione predefinita su ARCore. Attiva la modalità depth per fare in modo che la tua app utilizzi l'API Profondità.

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)

Acquisisci immagini di profondità

Chiama Frame.acquireDepthImage16Bits() per ottenere l'immagine di profondità per il frame corrente.

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

L'immagine restituita fornisce il buffer dell'immagine non elaborato, che può essere passato a uno strumento di protezione dei frammenti per utilizzarlo sulla GPU per occultare ogni oggetto sottoposto a rendering. È orientato in OPENGL_NORMALIZED_DEVICE_COORDINATES e può essere modificato in TEXTURE_NORMALIZED chiamando il numero Frame.transformCoordinates2d(). Una volta che l'immagine della profondità è accessibile all'interno di un'area di dati dell'oggetto, è possibile accedere direttamente a queste misurazioni della profondità per la gestione delle coperture.

Comprendere i valori di profondità

Dato il punto A sulla geometria del mondo reale osservata e un punto 2D a che rappresenta lo stesso punto nell'immagine in profondità, il valore fornito dall'API depth all'indirizzo a è uguale alla lunghezza di CA proiettata sull'asse principale. Può anche essere indicata come la coordinata z di A rispetto all'origine della fotocamera C. Quando si utilizza l'API Profondità, è importante comprendere che i valori di profondità non sono la lunghezza del raggio CA stesso, ma la sua proiezione.

Usa il livello di profondità negli screenr

Analizza le informazioni di profondità per il frame corrente

Utilizza le funzioni helper DepthGetMillimeters() e DepthGetVisibility() in uno strumento di identificazione dei frammenti per accedere alle informazioni di profondità per la posizione corrente dello schermo. Utilizza quindi queste informazioni per nascondere selettivamente le parti dell'oggetto visualizzato.

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

Occultamento di oggetti virtuali

Occultare gli oggetti virtuali nel corpo dello strumento di protezione dei frammenti. Aggiorna il canale alfa dell'oggetto in base alla sua profondità. Verrà visualizzato un oggetto nascosto.

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

Puoi eseguire il rendering dell'occlusione utilizzando il rendering in due passaggi o il rendering per oggetto con passaggio in avanti. L'efficienza di ogni approccio dipende dalla complessità della scena e da altre considerazioni specifiche dell'app.

Rendering passa in avanti per oggetto

Il rendering per oggetto in avanti determina l'occlusione di ciascun pixel dell'oggetto nel relativo ombreggiatore materiale. Se i pixel non sono visibili, vengono ritagliati, in genere tramite una combinazione alfa, simulando così l'occlusione sul dispositivo dell'utente.

Rendering in due passaggi

Con il rendering in due passaggi, il primo passaggio esegue il rendering di tutti i contenuti virtuali in un buffer intermedio. Il secondo passaggio fonde la scena virtuale con lo sfondo in base alla differenza tra la profondità del mondo reale e la profondità della scena virtuale. Questo approccio non richiede alcun lavoro aggiuntivo dello screenr specifico per oggetto e generalmente produce risultati più uniformi rispetto al metodo del passaggio in avanti.

Estrai la distanza da un'immagine in profondità

Per utilizzare l'API Profondità per scopi diversi dall'occultare gli oggetti virtuali o la visualizzazione dei dati sulla profondità, estrai informazioni dall'immagine di profondità.

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

Conversione delle coordinate tra immagini della fotocamera e immagini di profondità

Le immagini ottenute utilizzando getCameraImage() potrebbero avere proporzioni diverse rispetto alle immagini di profondità. In questo caso, l'immagine di profondità è un ritaglio dell'immagine della fotocamera e non tutti i pixel nell'immagine della fotocamera hanno una stima della profondità valida corrispondente.

Per ottenere le coordinate di profondità dell'immagine per le coordinate nell'immagine della CPU:

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

Per ottenere le coordinate dell'immagine della CPU per le coordinate della profondità di un'immagine:

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

Hit test di profondità

Gli hit test consentono agli utenti di posizionare gli oggetti in un luogo reale della scena. In precedenza, gli hit test potevano essere condotti solo sui piani rilevati, limitando le posizioni a grandi superfici piane, come i risultati mostrati dagli Android verdi. Gli hit test di profondità sfruttano informazioni di profondità non elaborate e uniformi per fornire risultati più precisi, anche su superfici non piane e con superficie ridotta. L'immagine è mostrata con i dispositivi Android rossi.

Per utilizzare hit-test abilitati per la profondità, chiama hitTest() e cerca DepthPoints nell'elenco di ritorno.

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)

Passaggio successivo

  • Consenti un monitoraggio più preciso con l'API Raw depth.
  • Dai un'occhiata al ARCore Insights Lab, che illustra diversi modi per accedere ai dati sulla profondità.