अपने Android ऐप्लिकेशन में गहराई का इस्तेमाल करें

डेप्थ एपीआई की मदद से, डिवाइस का कैमरा किसी सीन में मौजूद असली ऑब्जेक्ट के साइज़ और आकार को समझ पाता है. यह ऐप्लिकेशन, डेप्थ इमेज या डेप्थ मैप बनाने के लिए कैमरे का इस्तेमाल करता है. इससे आपके ऐप्लिकेशन में एआर की एक लेयर जुड़ जाती है. डेप्थ इमेज में दी गई जानकारी का इस्तेमाल करके, वर्चुअल ऑब्जेक्ट को असल दुनिया की ऑब्जेक्ट के सामने या उनके पीछे सटीक तरीके से दिखाया जा सकता है. इससे, लोगों को असली लगने वाला इमर्सिव अनुभव मिलता है.

गहराई की जानकारी गति से ली जाती है और अगर उपलब्ध हो, तो इसे हार्डवेयर डेप्थ सेंसर से मिली जानकारी के साथ जोड़ा जा सकता है, जैसे कि फ़्लाइट का समय (ToF) सेंसर. डेप्थ एपीआई के साथ काम करने के लिए, किसी डिवाइस में ToF सेंसर की ज़रूरत नहीं होती.

ज़रूरी शर्तें

पक्का करें कि आपको एआर के बुनियादी सिद्धांतों के बारे में पता हो साथ ही, आगे बढ़ने से पहले ARCore सेशन को कॉन्फ़िगर करने का तरीका जानें.

डेप्थ की सुविधा वाले डिवाइसों के ऐक्सेस पर पाबंदी लगाएं

अगर आपके ऐप्लिकेशन को डेप्थ एपीआई की सुविधा चाहिए, तो ऐसा इसलिए हो सकता है, क्योंकि एआर का अनुभव, डिवाइस की गहराई पर निर्भर करता है या ऐप्लिकेशन के वे हिस्से जो गहराई का इस्तेमाल करते हैं, तो आप Play Store पर मौजूद ऐप्लिकेशन को डेप्थ एपीआई की सुविधा वाले डिवाइस इसके अतिरिक्त, आपके AndroidManifest.xml की अगली पंक्ति इसमें AndroidManifest.xml बदलावों के बारे में बताया गया है ARCore चालू करें गाइड:

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

डेप्थ की सुविधा चालू करें

नए ARCore सेशन में, देखें कि उपयोगकर्ता के डिवाइस पर 'डेप्थ' सुविधा काम करती है या नहीं. प्रोसेसिंग पावर कम होने की वजह से, ARCore के साथ काम करने वाले सभी डिवाइस, डेप्थ एपीआई के साथ काम नहीं करते. संसाधन सेव करने के लिए, ARCore पर डिफ़ॉल्ट रूप से 'डेप्थ' सेटिंग बंद होती है. अपने ऐप्लिकेशन में डेप्थ एपीआई इस्तेमाल करने के लिए, डेप्थ मोड चालू करें.

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)

डेप्थ इमेज पाएं

मौजूदा फ़्रेम की डेप्थ इमेज पाने के लिए, Frame.acquireDepthImage16Bits() पर कॉल करें.

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

दिखाई गई इमेज, रॉ इमेज बफ़र देती है, जिसे रेंडर किए गए हर ऑब्जेक्ट को दिखाने के लिए, जीपीयू पर इस्तेमाल करने के लिए फ़्रैगमेंट शेडर को भेजा जा सकता है. यह सुविधा OPENGL_NORMALIZED_DEVICE_COORDINATES के लिए है और Frame.transformCoordinates2d() को कॉल करके इसे TEXTURE_NORMALIZED में बदला जा सकता है. जब ऑब्जेक्ट शेडर में डेप्थ इमेज को ऐक्सेस किया जा सकता है, तब इन डेप्थ के मेज़रमेंट को सीधे तौर पर ऑक्लूज़न हैंडल करने के लिए ऐक्सेस किया जा सकता है.

डेप्थ वैल्यू को समझना

असल दुनिया की देखी गई ज्यामिति और 2D पॉइंट a पर पॉइंट A दिया गया है गहराई से इमेज में एक ही पॉइंट को दिखाता है, जो डेप्थ के आधार पर दी गई वैल्यू है a पर एपीआई, मुख्य ऐक्सिस पर अनुमानित CA की लंबाई के बराबर है. इसे कैमरे के मुकाबले A के z-कोऑर्डिनेट के तौर पर भी दिखाया जा सकता है ऑरिजिन C. डेप्थ एपीआई के साथ काम करते समय, यह समझना ज़रूरी है कि गहराई के मान, खुद किरण CA की लंबाई नहीं हैं, बल्कि प्रक्षेपण हैं कर सकते हैं.

शेडर में डेप्थ का इस्तेमाल करें

मौजूदा फ़्रेम के लिए डेप्थ की जानकारी पार्स करें

स्क्रीन की मौजूदा पोज़िशन की डेप्थ की जानकारी ऐक्सेस करने के लिए, फ़्रैगमेंट शेडर में हेल्पर फ़ंक्शन DepthGetMillimeters() और DepthGetVisibility() का इस्तेमाल करें. इसके बाद, रेंडर किए गए ऑब्जेक्ट के हिस्सों को चुनने के लिए, इस जानकारी का इस्तेमाल करें.

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

वर्चुअल ऑब्जेक्ट शामिल करें

फ़्रैगमेंट शेडर के मुख्य हिस्से में वर्चुअल ऑब्जेक्ट शामिल करें. ऑब्जेक्ट के ऐल्फ़ा चैनल को उसकी गहराई के आधार पर अपडेट करें. ऐसा करने से, रोका गया ऑब्जेक्ट रेंडर हो जाएगा.

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

आप दो-पास रेंडरिंग या प्रति-ऑब्जेक्ट, फ़ॉरवर्ड-पास रेंडरिंग का इस्तेमाल करके रोक लगा सकते हैं. हर तरीका, सीन की जटिलता और ऐप्लिकेशन से जुड़ी दूसरी बातों पर निर्भर करता है.

हर ऑब्जेक्ट के हिसाब से, फ़ॉरवर्ड पास रेंडरिंग

हर ऑब्जेक्ट के हिसाब से, फ़ॉरवर्ड-पास रेंडरिंग से यह तय होता है कि उसके मटीरियल शेडर में ऑब्जेक्ट का हर पिक्सल दिखेगा या नहीं. अगर पिक्सल नहीं दिख रहे हैं, तो उन्हें क्लिप करने के लिए इस्तेमाल किया जाता है. आम तौर पर, ऐल्फ़ा ब्लेंडिंग की मदद से ऐसा किया जाता है. इस तरह, उपयोगकर्ता के डिवाइस पर ऑब्जेक्ट को दिखने में मुश्किल हो जाती है.

टू-पास रेंडरिंग

टू-पास रेंडरिंग की मदद से पहला पास, सभी वर्चुअल कॉन्टेंट को इंटरमीडियरी बफ़र में रेंडर करता है. दूसरा पास, असल दुनिया की गहराई और वर्चुअल सीन की डेप्थ के बीच के फ़र्क़ के आधार पर, वर्चुअल सीन को बैकग्राउंड के साथ मिला देता है. इस तरीके से, किसी ऑब्जेक्ट के हिसाब से शेडर का इस्तेमाल नहीं करना पड़ता. साथ ही, फ़ॉरवर्ड-पास तरीके की तुलना में, आम तौर पर एक जैसे दिखने वाले नतीजे ज़्यादा मिलते हैं.

गहराई वाली इमेज से दूरी निकालें

वर्चुअल ऑब्जेक्ट को दिखाने या डेप्थ का डेटा विज़ुअलाइज़ करने के अलावा, किसी डेप्थ एपीआई का इस्तेमाल करने के लिए, डेप्थ इमेज से जानकारी निकालें.

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

कैमरा इमेज और डेप्थ इमेज के बीच निर्देशांक बदलना

getCameraImage() का इस्तेमाल करके मिली इमेज का आसपेक्ट रेशियो (लंबाई-चौड़ाई का अनुपात) डेप्थ इमेज से अलग हो सकता है. इस मामले में, इमेज की डेप्थ इमेज, कैमरे से ली गई इमेज के बराबर होती है. हालांकि, यह ज़रूरी नहीं है कि कैमरा इमेज में मौजूद सभी पिक्सल के लिए डेप्थ का सही अनुमान लगाया गया हो.

सीपीयू इमेज पर निर्देशांकों के लिए गहराई इमेज निर्देशांक पाने के लिए:

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

गहराई वाले इमेज निर्देशांक के लिए सीपीयू इमेज कोऑर्डिनेट पाने के लिए:

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

डेप्थ हिट-टेस्ट

हिट-टेस्ट की मदद से उपयोगकर्ता, सीन में असल दुनिया की किसी जगह पर ऑब्जेक्ट रख सकते हैं. पहले, हिट-टेस्ट सिर्फ़ उन हवाई जहाज़ों पर किए जा सकते थे जिनका पता लगाया गया था. इसमें, जगहों को बड़ी और सपाट जगहों तक सीमित किया जाता था. उदाहरण के लिए, हरे रंग के Android डिवाइस पर दिखने वाले नतीजे. डेप्थ हिट-टेस्ट ज़्यादा सटीक हिट नतीजे देने के लिए, आसान और रॉ डेप्थ, दोनों तरह की जानकारी का फ़ायदा लेते हैं. यहां तक कि नॉन-प्लेनर और लो टेक्सचर सरफ़ेस पर भी हिट के सटीक नतीजे मिलते हैं. यह लाल रंग के Androids के साथ दिखाया जाता है.

गहराई से की जाने वाले हिट-टेस्ट का इस्तेमाल करने के लिए, hitTest() पर कॉल करें और लौटाए गए आइटम की सूची में 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)

आगे क्या होगा

  • Raw depth API की मदद से ज़्यादा सटीक सेंसिंग चालू करें.
  • ARCore depth Lab के बारे में जानें, जो डेप्थ डेटा को ऐक्सेस करने के अलग-अलग तरीकों के बारे में बताता है.