नमस्ते! Android पर

शुरुआती जानकारी

यह Hello World! ट्यूटोरियल, MediaPipe फ़्रेमवर्क का इस्तेमाल करके ऐसा Android ऐप्लिकेशन डेवलप करता है जो Android पर MediaPipe ग्राफ़ चलाता है.

आप क्या बनाएंगे

इस आसान कैमरा ऐप्लिकेशन को, Android डिवाइस पर लाइव वीडियो स्ट्रीम के लिए रीयल-टाइम में Sobel के किनारे की पहचान करने की सुविधा दी गई है.

edge_detection_android_gpu_gif

सेटअप

  1. अपने सिस्टम पर MediaPipe फ़्रेमवर्क इंस्टॉल करें. ज़्यादा जानकारी के लिए, फ़्रेमवर्क इंस्टॉल करने की गाइड देखें.
  2. Android डेवलपमेंट SDK टूल और Android NDK इंस्टॉल करें. इसका तरीका जानने के लिए, [फ़्रेमवर्क इंस्टॉलेशन गाइड] में भी जाएं.
  3. अपने Android डिवाइस पर डेवलपर के लिए सेटिंग और टूल चालू करें.
  4. Android ऐप्लिकेशन को बनाने और डिप्लॉय करने के लिए, अपने सिस्टम पर Bazel सेटअप करें.

किनारे की पहचान के लिए ग्राफ़

हम नीचे दिए गए ग्राफ़ का इस्तेमाल करेंगे, edge_detection_mobile_gpu.pbtxt:

# MediaPipe graph that performs GPU Sobel edge detection on a live video stream.
# Used in the examples in
# mediapipe/examples/android/src/java/com/mediapipe/apps/basic and
# mediapipe/examples/ios/edgedetectiongpu.

# Images coming into and out of the graph.
input_stream: "input_video"
output_stream: "output_video"

# Converts RGB images into luminance images, still stored in RGB format.
node: {
  calculator: "LuminanceCalculator"
  input_stream: "input_video"
  output_stream: "luma_video"
}

# Applies the Sobel filter to luminance images stored in RGB format.
node: {
  calculator: "SobelEdgesCalculator"
  input_stream: "luma_video"
  output_stream: "output_video"
}

ग्राफ़ का विज़ुअलाइज़ेशन नीचे दिखाया गया है:

edge_detection_mobile_gpu

इस ग्राफ़ में, आने वाले सभी फ़्रेम के लिए input_video नाम की एक इनपुट स्ट्रीम है, जो आपके डिवाइस के कैमरे से मिलेगी.

ग्राफ़ का पहला नोड, LuminanceCalculator, एक पैकेट (इमेज फ़्रेम) लेता है और OpenGL शेडर का इस्तेमाल करके ल्यूमिनेंस में बदलाव करता है. क्लिक से बनने वाला इमेज फ़्रेम, luma_video आउटपुट स्ट्रीम में भेजा जाता है.

दूसरा नोड, SobelEdgesCalculator luma_video स्ट्रीम में आने वाले पैकेट पर किनारे की पहचान करने की सुविधा लागू करता है और आउटपुट स्ट्रीम को output_video आउटपुट स्ट्रीम में दिखाता है.

हमारा Android ऐप्लिकेशन, output_video स्ट्रीम के आउटपुट इमेज फ़्रेम दिखाएगा.

शुरुआती मिनिमम ऐप्लिकेशन सेटअप

हम सबसे पहले एक आसान Android ऐप्लिकेशन से शुरू करते हैं, जो स्क्रीन पर "नमस्ते दुनिया!" दिखाता है. अगर आपको bazel का इस्तेमाल करके Android ऐप्लिकेशन बनाने के बारे में जानकारी है, तो इस चरण को छोड़ा जा सकता है.

एक नई डायरेक्ट्री बनाएं, जहां आपको अपना Android ऐप्लिकेशन बनाना होगा. उदाहरण के लिए, इस ट्यूटोरियल का पूरा कोड mediapipe/examples/android/src/java/com/google/mediapipe/apps/basic पर देखा जा सकता है. हम पूरे कोडलैब में, इस पाथ को $APPLICATION_PATH के तौर पर इस्तेमाल करेंगे.

ध्यान दें कि ऐप्लिकेशन के पाथ में:

  • ऐप्लिकेशन का नाम helloworld है.
  • ऐप्लिकेशन का $PACKAGE_PATH com.google.mediapipe.apps.basic है. इस ट्यूटोरियल में कोड स्निपेट में इसका इस्तेमाल किया गया है. इसलिए, कृपया कोड स्निपेट को कॉपी/इस्तेमाल करते समय अपना खुद का $PACKAGE_PATH इस्तेमाल करना न भूलें.

$APPLICATION_PATH/res/layout में activity_main.xml फ़ाइल जोड़ें. इससे ऐप्लिकेशन की फ़ुल स्क्रीन पर, Hello World! स्ट्रिंग के साथ TextView दिखता है:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

  <TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Hello World!"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toTopOf="parent" />

</android.support.constraint.ConstraintLayout>

नीचे दिखाए गए तरीके के मुताबिक, activity_main.xml लेआउट के कॉन्टेंट को लोड करने के लिए, $APPLICATION_PATH में सामान्य MainActivity.java जोड़ें:

package com.google.mediapipe.apps.basic;

import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;

/** Bare-bones main activity. */
public class MainActivity extends AppCompatActivity {

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
  }
}

AndroidManifest.xml को $APPLICATION_PATH में मेनिफ़ेस्ट फ़ाइल जोड़ें. यह ऐप्लिकेशन शुरू होने पर MainActivity को लॉन्च होगी:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.google.mediapipe.apps.basic">

  <uses-sdk
      android:minSdkVersion="19"
      android:targetSdkVersion="19" />

  <application
      android:allowBackup="true"
      android:label="${appName}"
      android:supportsRtl="true"
      android:theme="@style/AppTheme">
      <activity
          android:name="${mainActivity}"
          android:exported="true"
          android:screenOrientation="portrait">
          <intent-filter>
              <action android:name="android.intent.action.MAIN" />
              <category android:name="android.intent.category.LAUNCHER" />
          </intent-filter>
      </activity>
  </application>

</manifest>

हम अपने ऐप्लिकेशन में, Theme.AppCompat थीम का इस्तेमाल कर रहे हैं. इसलिए, हमें थीम के सही रेफ़रंस की ज़रूरत है. colors.xml को $APPLICATION_PATH/res/values/ में जोड़ें:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="colorPrimary">#008577</color>
    <color name="colorPrimaryDark">#00574B</color>
    <color name="colorAccent">#D81B60</color>
</resources>

styles.xml को $APPLICATION_PATH/res/values/ में जोड़ें:

<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>

</resources>

ऐप्लिकेशन बनाने के लिए, $APPLICATION_PATH में BUILD फ़ाइल जोड़ें और मेनिफ़ेस्ट में ${appName} और ${mainActivity} को BUILD में बताई गई स्ट्रिंग से बदल दिया जाएगा, जैसा कि नीचे दिखाया गया है.

android_library(
    name = "basic_lib",
    srcs = glob(["*.java"]),
    manifest = "AndroidManifest.xml",
    resource_files = glob(["res/**"]),
    deps = [
        "//third_party:android_constraint_layout",
        "//third_party:androidx_appcompat",
    ],
)

android_binary(
    name = "helloworld",
    manifest = "AndroidManifest.xml",
    manifest_values = {
        "applicationId": "com.google.mediapipe.apps.basic",
        "appName": "Hello World",
        "mainActivity": ".MainActivity",
    },
    multidex = "native",
    deps = [
        ":basic_lib",
    ],
)

android_library नियम, MainActivity, संसाधन फ़ाइलों, और AndroidManifest.xml के लिए डिपेंडेंसी जोड़ता है.

android_binary नियम, आपके Android डिवाइस पर इंस्टॉल करने के लिए बाइनरी APK बनाने के लिए जनरेट की गई basic_lib Android लाइब्रेरी का इस्तेमाल करता है.

ऐप्लिकेशन बनाने के लिए, नीचे दिए गए कमांड का इस्तेमाल करें:

bazel build -c opt --config=android_arm64 $APPLICATION_PATH:helloworld

adb install का इस्तेमाल करके जनरेट की गई APK फ़ाइल इंस्टॉल करें. उदाहरण के लिए:

adb install bazel-bin/$APPLICATION_PATH/helloworld.apk

अपने डिवाइस पर ऐप्लिकेशन खोलें. इसमें Hello World! टेक्स्ट वाली स्क्रीन दिखेगी.

bazel_hello_world_android

CameraX से कैमरे का इस्तेमाल किया जा रहा है

कैमरे की अनुमतियां

अपने ऐप्लिकेशन में कैमरे का इस्तेमाल करने के लिए, हमें उपयोगकर्ता से कैमरे का ऐक्सेस मांगने का अनुरोध करना होगा. कैमरा ऐक्सेस करने की अनुमतियों का अनुरोध करने के लिए, इन्हें AndroidManifest.xml में जोड़ें:

<!-- For using the camera -->
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" />

एक ही फ़ाइल में, SDK टूल के कम से कम वर्शन को 21 में बदलें और SDK टूल के वर्शन को 27 पर सेट करें:

<uses-sdk
    android:minSdkVersion="21"
    android:targetSdkVersion="27" />

इससे यह पक्का होता है कि उपयोगकर्ता को कैमरा ऐक्सेस करने की अनुमति मांगने के लिए कहा जाएगा. साथ ही, इससे हमें कैमरा ऐक्सेस करने के लिए, CameraX लाइब्रेरी का इस्तेमाल करने की सुविधा मिलती है.

कैमरा ऐक्सेस करने की अनुमतियों का अनुरोध करने के लिए, हम MediaPipe इतने फ़्रेमवर्क के तहत काम करने वाली सुविधा का इस्तेमाल कर सकते हैं, इन कॉम्पोनेंट के नाम PermissionHelper हैं. इसका इस्तेमाल करने के लिए, BUILD में mediapipe_lib नियम में "//mediapipe/java/com/google/mediapipe/components:android_components" डिपेंडेंसी जोड़ें.

MainActivity में PermissionHelper का इस्तेमाल करने के लिए, onCreate फ़ंक्शन में यह लाइन जोड़ें:

PermissionHelper.checkAndRequestCameraPermissions(this);

यह उपयोगकर्ता को स्क्रीन पर एक डायलॉग दिखाकर, इस ऐप्लिकेशन में कैमरे का इस्तेमाल करने की अनुमतियों का अनुरोध करने के लिए कहता है.

उपयोगकर्ता के रिस्पॉन्स को मैनेज करने के लिए, यह कोड जोड़ें:

@Override
public void onRequestPermissionsResult(
    int requestCode, String[] permissions, int[] grantResults) {
  super.onRequestPermissionsResult(requestCode, permissions, grantResults);
  PermissionHelper.onRequestPermissionsResult(requestCode, permissions, grantResults);
}

@Override
protected void onResume() {
  super.onResume();
  if (PermissionHelper.cameraPermissionsGranted(this)) {
    startCamera();
  }
}

public void startCamera() {}

फ़िलहाल, हम startCamera() वाले तरीके को खाली छोड़ देंगे. जब उपयोगकर्ता प्रॉम्प्ट का जवाब देता है, तो MainActivity फिर से शुरू हो जाएगा और onResume() को कॉल किया जाएगा. कोड पुष्टि करेगा कि कैमरा इस्तेमाल करने की अनुमतियां दी गई हैं. इसके बाद, कैमरा चालू हो जाएगा.

ऐप्लिकेशन फिर से बनाएं और उसे इंस्टॉल करें. इसके बाद, आपको एक प्रॉम्प्ट दिखेगा, जिसमें ऐप्लिकेशन के लिए कैमरे को ऐक्सेस करने का अनुरोध किया जाएगा.

कैमरे का ऐक्सेस

कैमरा ऐक्सेस करने की अनुमतियों की मदद से, हम कैमरे से फ़्रेम ले सकते हैं और उन्हें फ़ेच कर सकते हैं.

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

$APPLICATION_PATH/res/layout/activity_main.xml से पूरा TextView कोड ब्लॉक हटाएं और इसके बजाय, यहां दिया गया कोड जोड़ें:

<FrameLayout
    android:id="@+id/preview_display_layout"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:layout_weight="1">
    <TextView
        android:id="@+id/no_camera_access_view"
        android:layout_height="fill_parent"
        android:layout_width="fill_parent"
        android:gravity="center"
        android:text="@string/no_camera_access" />
</FrameLayout>

इस कोड ब्लॉक में, preview_display_layout नाम का एक नया FrameLayout और इसके अंदर no_camera_access_preview नाम का एक TextView नेस्ट किया गया है. कैमरे के ऐक्सेस की अनुमतियां न मिलने पर, हमारा ऐप्लिकेशन TextView को एक स्ट्रिंग मैसेज के साथ दिखाएगा. यह मैसेज, no_camera_access वैरिएबल में सेव होगा. नीचे दी गई लाइन को $APPLICATION_PATH/res/values/strings.xml फ़ाइल में जोड़ें:

<string name="no_camera_access" translatable="false">Please grant camera permissions.</string>

जब उपयोगकर्ता कैमरा ऐक्सेस करने की अनुमति नहीं देता है, तब स्क्रीन अब ऐसी दिखेगी:

missing_camera_permission_android

अब हम SurfaceTexture और SurfaceView ऑब्जेक्ट को MainActivity में जोड़ देंगे:

private SurfaceTexture previewFrameTexture;
private SurfaceView previewDisplayView;

onCreate(Bundle) फ़ंक्शन में, कैमरे की अनुमतियों का अनुरोध करने से पहले, यहां दी गई दो लाइनें जोड़ें:

previewDisplayView = new SurfaceView(this);
setupPreviewDisplayView();

अब setupPreviewDisplayView() के बारे में बताने वाला कोड जोड़ें:

private void setupPreviewDisplayView() {
  previewDisplayView.setVisibility(View.GONE);
  ViewGroup viewGroup = findViewById(R.id.preview_display_layout);
  viewGroup.addView(previewDisplayView);
}

हमने एक नए SurfaceView ऑब्जेक्ट को तय किया और उसे preview_display_layout FrameLayout ऑब्जेक्ट में जोड़ा, ताकि इसका इस्तेमाल करके कैमरे के फ़्रेम दिखाने के लिए, previewFrameTexture नाम के SurfaceTexture ऑब्जेक्ट को दिखाया जा सके.

कैमरा फ़्रेम पाने के लिए, previewFrameTexture का इस्तेमाल करने के लिए, हम CameraX का इस्तेमाल करेंगे. फ़्रेमवर्क, CameraX का इस्तेमाल करने के लिए CameraXPreviewHelper नाम की एक सुविधा देता है. onCameraStarted(@Nullable SurfaceTexture) से कैमरा चालू होने पर, यह क्लास लिसनर को अपडेट करती है.

इस सुविधा का इस्तेमाल करने के लिए, BUILD फ़ाइल में बदलाव करें, ताकि आप "//mediapipe/java/com/google/mediapipe/components:android_camerax_helper" पर डिपेंडेंसी जोड़ सकें.

अब CameraXPreviewHelper को इंपोर्ट करें और यहां दी गई लाइन को MainActivity में जोड़ें:

private CameraXPreviewHelper cameraHelper;

अब हम लागू करने की अपनी जानकारी को startCamera() में जोड़ सकते हैं:

public void startCamera() {
  cameraHelper = new CameraXPreviewHelper();
  cameraHelper.setOnCameraStartedListener(
    surfaceTexture -> {
      previewFrameTexture = surfaceTexture;
      // Make the display view visible to start showing the preview.
      previewDisplayView.setVisibility(View.VISIBLE);
    });
}

इससे एक नया CameraXPreviewHelper ऑब्जेक्ट बनता है. साथ ही, ऑब्जेक्ट पर पहचान छिपाने वाला लिसनर जुड़ जाता है. जब cameraHelper से यह पता चलता है कि कैमरा चालू हो गया है और फ़्रेम पाने के लिए surfaceTexture उपलब्ध है, तो हम surfaceTexture को previewFrameTexture के तौर पर सेव करते हैं और previewDisplayView को दिखाते हैं, ताकि हमें previewFrameTexture से फ़्रेम दिखने लगे.

हालांकि, कैमरा शुरू करने से पहले, हमें यह तय करना होता है कि हमें कौनसा कैमरा इस्तेमाल करना है. CameraXPreviewHelper, CameraHelper से इनहेरिट किया जाता है. इसमें दो विकल्प, FRONT और BACK मिलते हैं. हम BUILD फ़ाइल से, मेटाडेटा के तौर पर यह फ़ैसला दे सकते हैं कि किसी दूसरे कैमरे का इस्तेमाल करके ऐप्लिकेशन का दूसरा वर्शन बनाने के लिए, कोड में कोई बदलाव करने की ज़रूरत नहीं है.

यह मानते हुए कि हम कैमरे से देखे जाने वाले किसी लाइव सीन में, किनारे का पता लगाने के लिए BACK कैमरे का इस्तेमाल करना चाहते हैं, तो मेटाडेटा को AndroidManifest.xml में जोड़ें:

      ...
      <meta-data android:name="cameraFacingFront" android:value="${cameraFacingFront}"/>
  </application>
</manifest>

और manifest_values में एक नई एंट्री के साथ helloworld के Android बाइनरी नियम में, BUILD में चुना हुआ विकल्प तय करें:

manifest_values = {
    "applicationId": "com.google.mediapipe.apps.basic",
    "appName": "Hello World",
    "mainActivity": ".MainActivity",
    "cameraFacingFront": "False",
},

अब, manifest_values में बताए गए मेटाडेटा को वापस पाने के लिए, MainActivity में जाकर, कोई ApplicationInfo ऑब्जेक्ट जोड़ें:

private ApplicationInfo applicationInfo;

onCreate() फ़ंक्शन में, इन्हें जोड़ें:

try {
  applicationInfo =
      getPackageManager().getApplicationInfo(getPackageName(), PackageManager.GET_META_DATA);
} catch (NameNotFoundException e) {
  Log.e(TAG, "Cannot find application info: " + e);
}

अब startCamera() फ़ंक्शन के आखिर में यह लाइन जोड़ें:

CameraHelper.CameraFacing cameraFacing =
    applicationInfo.metaData.getBoolean("cameraFacingFront", false)
        ? CameraHelper.CameraFacing.FRONT
        : CameraHelper.CameraFacing.BACK;
cameraHelper.startCamera(this, cameraFacing, /*unusedSurfaceTexture=*/ null);

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

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

ExternalTextureConverter सेटअप

SurfaceTexture, किसी स्ट्रीम से OpenGL ES टेक्सचर के तौर पर इमेज फ़्रेम को कैप्चर करता है. MediaPipe ग्राफ़ का इस्तेमाल करने के लिए, कैमरे से कैप्चर किए गए फ़्रेम को सामान्य Open GL टेक्सचर ऑब्जेक्ट में सेव किया जाना चाहिए. फ़्रेमवर्क, SurfaceTexture ऑब्जेक्ट में सेव की गई इमेज को सामान्य OpenGL टेक्सचर ऑब्जेक्ट में बदलने के लिए, ExternalTextureConverter क्लास उपलब्ध कराता है.

ExternalTextureConverter का इस्तेमाल करने के लिए, हमें एक EGLContext की भी ज़रूरत है, जिसे किसी EglManager ऑब्जेक्ट से बनाया और मैनेज किया जाता है. EglManager, "//mediapipe/java/com/google/mediapipe/glutil" का इस्तेमाल करने के लिए, BUILD फ़ाइल पर डिपेंडेंसी जोड़ें.

MainActivity में, ये एलान जोड़ें:

private EglManager eglManager;
private ExternalTextureConverter converter;

onCreate(Bundle) फ़ंक्शन में, कैमरा अनुमतियों का अनुरोध करने से पहले eglManager ऑब्जेक्ट को शुरू करने के लिए स्टेटमेंट जोड़ें:

eglManager = new EglManager(null);

याद रखें कि हमने MainActivity में onResume() फ़ंक्शन को तय किया है, ताकि यह पुष्टि की जा सके कि कैमरा ऐक्सेस करने की अनुमतियां दी गई हैं. इसके बाद, startCamera() को कॉल करें. इस जांच से पहले, converter ऑब्जेक्ट को शुरू करने के लिए, onResume() में यहां दी गई लाइन जोड़ें:

converter = new ExternalTextureConverter(eglManager.getContext());

यह converter अब GLContext का इस्तेमाल करता है, जिसे eglManager मैनेज करता है.

हमें MainActivity में onPause() फ़ंक्शन को भी बदलना होगा, ताकि अगर ऐप्लिकेशन 'रोका गया' की स्थिति में चला जाए, तो हम converter को सही तरीके से बंद कर दें:

@Override
protected void onPause() {
  super.onPause();
  converter.close();
}

previewFrameTexture के आउटपुट को converter से पाइप करने के लिए, कोड के इस ब्लॉक को setupPreviewDisplayView() में जोड़ें:

previewDisplayView
 .getHolder()
 .addCallback(
     new SurfaceHolder.Callback() {
       @Override
       public void surfaceCreated(SurfaceHolder holder) {}

       @Override
       public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
         // (Re-)Compute the ideal size of the camera-preview display (the area that the
         // camera-preview frames get rendered onto, potentially with scaling and rotation)
         // based on the size of the SurfaceView that contains the display.
         Size viewSize = new Size(width, height);
         Size displaySize = cameraHelper.computeDisplaySizeFromViewSize(viewSize);

         // Connect the converter to the camera-preview frames as its input (via
         // previewFrameTexture), and configure the output width and height as the computed
         // display size.
         converter.setSurfaceTextureAndAttachToGLContext(
             previewFrameTexture, displaySize.getWidth(), displaySize.getHeight());
       }

       @Override
       public void surfaceDestroyed(SurfaceHolder holder) {}
     });

इस कोड ब्लॉक में, हम previewDisplayView के लिए कस्टम SurfaceHolder.Callback को जोड़ते हैं. साथ ही, डिवाइस की स्क्रीन पर कैमरे के फ़्रेम के सही डिसप्ले साइज़ का पता लगाने के लिए, surfaceChanged(SurfaceHolder holder, int format, int width, int height) फ़ंक्शन लागू करते हैं. साथ ही, हम previewFrameTexture ऑब्जेक्ट को जोड़ने और कंप्यूट किए गए displaySize के फ़्रेम को converter पर भेजते हैं.

अब हम MediaPipe ग्राफ़ में कैमरा फ़्रेम का इस्तेमाल करने के लिए तैयार हैं.

Android में MediaPipe ग्राफ़ का इस्तेमाल करना

काम की डिपेंडेंसी जोड़ना

MediaPipe ग्राफ़ का इस्तेमाल करने के लिए, हमें Android पर MediaPipe फ़्रेमवर्क में डिपेंडेंसी जोड़नी होगी. हम पहले MediaPipe फ़्रेमवर्क के JNI कोड का इस्तेमाल करके cc_binary बनाने के लिए एक बिल्ड नियम जोड़ेंगे. इसके बाद, अपने ऐप्लिकेशन में इस बाइनरी का इस्तेमाल करने के लिए, एक cc_library नियम बनाएंगे. अपनी BUILD फ़ाइल में यह कोड ब्लॉक जोड़ें:

cc_binary(
    name = "libmediapipe_jni.so",
    linkshared = 1,
    linkstatic = 1,
    deps = [
        "//mediapipe/java/com/google/mediapipe/framework/jni:mediapipe_framework_jni",
    ],
)

cc_library(
    name = "mediapipe_jni_lib",
    srcs = [":libmediapipe_jni.so"],
    alwayslink = 1,
)

BUILD फ़ाइल में, mediapipe_lib बिल्ड नियम में डिपेंडेंसी ":mediapipe_jni_lib" जोड़ें.

इसके बाद, हमें ऐप्लिकेशन में इस्तेमाल किए जाने वाले MediaPipe ग्राफ़ के लिए खास डिपेंडेंसी जोड़नी होंगी.

सबसे पहले, libmediapipe_jni.so बिल्ड नियम में सभी कैलकुलेटर कोड के लिए डिपेंडेंसी जोड़ें:

"//mediapipe/graphs/edge_detection:mobile_calculators",

MediaPipe ग्राफ़, .pbtxt फ़ाइलें होती हैं, लेकिन ऐप्लिकेशन में उनका इस्तेमाल करने के लिए, हमें mediapipe_binary_graph बिल्ड नियम का इस्तेमाल करके .binarypb फ़ाइल जनरेट करनी होगी.

helloworld Android बाइनरी बिल्ड नियम में, ग्राफ़ से जुड़े mediapipe_binary_graph टारगेट को एक एसेट के तौर पर जोड़ें:

assets = [
  "//mediapipe/graphs/edge_detection:mobile_gpu_binary_graph",
],
assets_dir = "",

assets बिल्ड नियम में, अपने ग्राफ़ में इस्तेमाल किए गए TensorFlowLite मॉडल जैसे अन्य एसेट भी जोड़े जा सकते हैं.

इसके अलावा, ग्राफ़ से जुड़ी खास प्रॉपर्टी के लिए एक और manifest_values जोड़ें, ताकि बाद में इन्हें MainActivity में वापस लाया जा सके:

manifest_values = {
    "applicationId": "com.google.mediapipe.apps.basic",
    "appName": "Hello World",
    "mainActivity": ".MainActivity",
    "cameraFacingFront": "False",
    "binaryGraphName": "mobile_gpu.binarypb",
    "inputVideoStreamName": "input_video",
    "outputVideoStreamName": "output_video",
},

ध्यान दें कि binaryGraphName, बाइनरी ग्राफ़ का फ़ाइल नाम दिखाता है. इसे mediapipe_binary_graph टारगेट के output_name फ़ील्ड से तय किया जाता है. inputVideoStreamName और outputVideoStreamName इनपुट और आउटपुट वीडियो स्ट्रीम के नाम हैं, जो ग्राफ़ में बताए गए हैं.

अब MainActivity को MediaPipe फ़्रेमवर्क को लोड करने की ज़रूरत है. साथ ही, फ़्रेमवर्क में OpenCV का इस्तेमाल होता है, इसलिए MainActvity को भी OpenCV लोड करना चाहिए. दोनों डिपेंडेंसी लोड करने के लिए, MainActivity में इस कोड का इस्तेमाल करें (क्लास के अंदर, लेकिन किसी भी फ़ंक्शन के अंदर नहीं):

static {
  // Load all native libraries needed by the app.
  System.loadLibrary("mediapipe_jni");
  System.loadLibrary("opencv_java3");
}

MainActivity में ग्राफ़ का इस्तेमाल करें

सबसे पहले, हमें वह एसेट लोड करनी होगी जिसमें ग्राफ़ की .pbtxt फ़ाइल से कंपाइल किया गया .binarypb हो. ऐसा करने के लिए, हम MediaPipe उपयोगिता, AndroidAssetUtil का इस्तेमाल कर सकते हैं.

eglManager शुरू करने से पहले, onCreate(Bundle) में ऐसेट मैनेजर को शुरू करें:

// Initialize asset manager so that MediaPipe native libraries can access the app assets, e.g.,
// binary graphs.
AndroidAssetUtil.initializeNativeAssetManager(this);

अब हमें एक FrameProcessor ऑब्जेक्ट सेटअप करना होगा, जो converter से तैयार किए गए कैमरा फ़्रेम को MediaPipe ग्राफ़ पर भेजता है और ग्राफ़ चलाता है. साथ ही, आउटपुट तैयार करने के बाद, आउटपुट दिखाने के लिए previewDisplayView को अपडेट करता है. FrameProcessor का एलान करने के लिए, यह कोड जोड़ें:

private FrameProcessor processor;

और eglManager शुरू करने के बाद, onCreate(Bundle) में इसे शुरू करें:

processor =
    new FrameProcessor(
        this,
        eglManager.getNativeContext(),
        applicationInfo.metaData.getString("binaryGraphName"),
        applicationInfo.metaData.getString("inputVideoStreamName"),
        applicationInfo.metaData.getString("outputVideoStreamName"));

processor को प्रोसेस करने के लिए, converter से बदले गए फ़्रेम का इस्तेमाल करना होगा. converter शुरू करने के बाद, यहां दी गई लाइन को onResume() में जोड़ें:

converter.setConsumer(processor);

processor को अपना आउटपुट previewDisplayView को भेजना चाहिए, ऐसा करने के लिए, अपने कस्टम SurfaceHolder.Callback में फ़ंक्शन की ये परिभाषाएं जोड़ें:

@Override
public void surfaceCreated(SurfaceHolder holder) {
  processor.getVideoSurfaceOutput().setSurface(holder.getSurface());
}

@Override
public void surfaceDestroyed(SurfaceHolder holder) {
  processor.getVideoSurfaceOutput().setSurface(null);
}

जब SurfaceHolder बनाया गया था, तब processor के VideoSurfaceOutput में हमारे पास Surface था. इसके खत्म होने पर, हम इसे processor के VideoSurfaceOutput से हटा देते हैं.

यह बहुत आसान है! अब आप डिवाइस पर ऐप्लिकेशन बना सकेंगे और उसे चला सकेंगे. साथ ही, आपको लाइव कैमरा फ़ीड पर Sobel Edge की पहचान करने की सुविधा दिखेगी! बधाई हो!

edge_detection_android_gpu_gif

अगर आपको कोई समस्या हुई है, तो ट्यूटोरियल का पूरा कोड यहां देखें.