ওহে বিশ্ব! অ্যান্ড্রয়েডে

ভূমিকা

এই হ্যালো ওয়ার্ল্ড! টিউটোরিয়াল মিডিয়াপাইপ ফ্রেমওয়ার্ক ব্যবহার করে একটি অ্যান্ড্রয়েড অ্যাপ্লিকেশন তৈরি করতে যা অ্যান্ড্রয়েডে মিডিয়াপাইপ গ্রাফ চালায়।

যা আপনি নির্মাণ করবেন

একটি অ্যান্ড্রয়েড ডিভাইসে একটি লাইভ ভিডিও স্ট্রিমে প্রয়োগ করা রিয়েল-টাইম সোবেল প্রান্ত সনাক্তকরণের জন্য একটি সাধারণ ক্যামেরা অ্যাপ।

edge_detection_android_gpu_gif

সেটআপ

  1. আপনার সিস্টেমে MediaPipe ফ্রেমওয়ার্ক ইনস্টল করুন, বিস্তারিত জানার জন্য ফ্রেমওয়ার্ক ইনস্টলেশন গাইড দেখুন।
  2. Android ডেভেলপমেন্ট SDK এবং Android NDK ইনস্টল করুন। [ফ্রেমওয়ার্ক ইনস্টলেশন গাইড] এ কীভাবে তা করবেন তা দেখুন।
  3. আপনার অ্যান্ড্রয়েড ডিভাইসে বিকাশকারী বিকল্পগুলি সক্ষম করুন৷
  4. অ্যান্ড্রয়েড অ্যাপ তৈরি এবং স্থাপন করতে আপনার সিস্টেমে 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 আউটপুট স্ট্রীমে ফলাফল দেয়।

আমাদের অ্যান্ড্রয়েড অ্যাপ্লিকেশন output_video স্ট্রিমের আউটপুট ইমেজ ফ্রেম প্রদর্শন করবে।

প্রাথমিক ন্যূনতম অ্যাপ্লিকেশন সেটআপ

আমরা প্রথমে একটি সাধারণ অ্যান্ড্রয়েড অ্যাপ্লিকেশন দিয়ে শুরু করি যা প্রদর্শন করে "হ্যালো ওয়ার্ল্ড!" পর্দায় আপনি যদি bazel ব্যবহার করে অ্যান্ড্রয়েড অ্যাপ্লিকেশন তৈরির সাথে পরিচিত হন তবে আপনি এই পদক্ষেপটি এড়িয়ে যেতে পারেন৷

একটি নতুন ডিরেক্টরি তৈরি করুন যেখানে আপনি আপনার অ্যান্ড্রয়েড অ্যাপ্লিকেশন তৈরি করবেন। উদাহরণস্বরূপ, এই টিউটোরিয়ালের সম্পূর্ণ কোডটি 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/layoutactivity_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>

$APPLICATION_PATH এ একটি সাধারণ MainActivity.java যোগ করুন যা নিচে দেখানো হিসাবে activity_main.xml লেআউটের বিষয়বস্তু লোড করে:

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

$APPLICATION_PATHAndroidManifest.xml নামের একটি ম্যানিফেস্ট ফাইল যোগ করুন, যা অ্যাপ্লিকেশন শুরু হলে 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 থিম ব্যবহার করছি, তাই আমাদের উপযুক্ত থিমের উল্লেখ প্রয়োজন। $APPLICATION_PATH/res/values/colors.xml যোগ করুন :

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

$APPLICATION_PATH/res/values/ styles.xml যোগ করুন :

<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 নিয়ম, আপনার অ্যান্ড্রয়েড ডিভাইসে ইনস্টলেশনের জন্য একটি বাইনারি APK তৈরি করতে তৈরি করা basic_lib অ্যান্ড্রয়েড লাইব্রেরি ব্যবহার করে।

অ্যাপটি তৈরি করতে, নিম্নলিখিত কমান্ডটি ব্যবহার করুন:

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" />

এটি নিশ্চিত করে যে ব্যবহারকারীকে ক্যামেরা অনুমতির অনুরোধ করতে বলা হয়েছে এবং ক্যামেরা অ্যাক্সেসের জন্য ক্যামেরাএক্স লাইব্রেরি ব্যবহার করতে আমাদের সক্ষম করে।

ক্যামেরা অনুমতির অনুরোধ করার জন্য, আমরা MediaPipe ফ্রেমওয়ার্ক উপাদানগুলির দ্বারা প্রদত্ত একটি ইউটিলিটি ব্যবহার করতে পারি, যথা PermissionHelper ৷ এটি ব্যবহার করতে, BUILDmediapipe_lib নিয়মে একটি নির্ভরতা "//mediapipe/java/com/google/mediapipe/components:android_components" যোগ করুন।

MainActivityPermissionHelper ব্যবহার করতে, 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>

এই কোড ব্লকটিতে একটি নতুন FrameLayout রয়েছে যার নাম preview_display_layout এবং এর ভিতরে একটি TextView নেস্ট করা হয়েছে, যার নাম no_camera_access_preview । যখন ক্যামেরা অ্যাক্সেসের অনুমতি দেওয়া হয় না, তখন আমাদের অ্যাপ্লিকেশনটি no_camera_access ভেরিয়েবলে সংরক্ষিত একটি স্ট্রিং বার্তা সহ TextView প্রদর্শন করবে। $APPLICATION_PATH/res/values/strings.xml ফাইলে নিম্নলিখিত লাইন যোগ করুন:

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

যখন ব্যবহারকারী ক্যামেরার অনুমতি দেয় না, তখন স্ক্রীনটি এইরকম দেখাবে:

missing_camera_permission_android

এখন, আমরা MainActivitySurfaceTexture এবং SurfaceView অবজেক্ট যোগ করব:

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) এর মাধ্যমে ক্যামেরা চালু হলে এই ক্লাসটি একজন শ্রোতাকে আপডেট করে।

এই ইউটিলিটি ব্যবহার করতে, "//mediapipe/java/com/google/mediapipe/components:android_camerax_helper" এ নির্ভরতা যোগ করতে BUILD ফাইলটি পরিবর্তন করুন।

এখন 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 অ্যান্ড্রয়েড বাইনারি নিয়মে 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 গ্রাফে ফ্রেমগুলি ব্যবহার করতে চাই, তাই এই টিউটোরিয়ালে আমরা সরাসরি ক্যামেরা আউটপুট দেখার জন্য কোড যোগ করব না। পরিবর্তে, আমরা কীভাবে মিডিয়াপাইপ গ্রাফে প্রক্রিয়াকরণের জন্য ক্যামেরা ফ্রেম পাঠাতে পারি এবং স্ক্রিনে গ্রাফের আউটপুট প্রদর্শন করতে পারি সেদিকে এগিয়ে যাই।

ExternalTextureConverter সেটআপ

একটি SurfaceTexture একটি OpenGL ES টেক্সচার হিসাবে একটি স্ট্রীম থেকে ইমেজ ফ্রেম ক্যাপচার করে। একটি MediaPipe গ্রাফ ব্যবহার করতে, ক্যামেরা থেকে ক্যাপচার করা ফ্রেমগুলিকে একটি নিয়মিত ওপেন 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 এখন eglManager দ্বারা পরিচালিত GLContext ব্যবহার করে।

আমাদের MainActivity onPause() ফাংশনটিকে ওভাররাইড করতে হবে যাতে অ্যাপ্লিকেশনটি যদি বিরতিযুক্ত অবস্থায় যায়, আমরা converter সঠিকভাবে বন্ধ করে দিই:

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

converter previewFrameTexture এর আউটপুট পাইপ করতে, 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) {}
     });

এই কোড ব্লকে, আমরা একটি কাস্টম SurfaceHolder.Callback যোগ করি। previewDisplayView দেখতে কলব্যাক করি এবং ডিভাইসের স্ক্রিনে ক্যামেরা ফ্রেমের উপযুক্ত ডিসপ্লে সাইজ গণনা করার জন্য surfaceChanged(SurfaceHolder holder, int format, int width, int height) ফাংশন প্রয়োগ করি। previewFrameTexture অবজেক্ট এবং converter গণনা করা displaySize ফ্রেম পাঠান।

আমরা এখন মিডিয়াপাইপ গ্রাফে ক্যামেরা ফ্রেম ব্যবহার করার জন্য প্রস্তুত।

অ্যান্ড্রয়েডে মিডিয়াপাইপ গ্রাফ ব্যবহার করা

প্রাসঙ্গিক নির্ভরতা যোগ করুন

একটি MediaPipe গ্রাফ ব্যবহার করার জন্য, আমাদের Android-এ 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" নির্ভরতা যোগ করুন।

এরপরে, আমরা যে মিডিয়াপাইপ গ্রাফটি অ্যাপ্লিকেশনটিতে ব্যবহার করতে চাই তার সাথে আমাদের নির্দিষ্ট নির্ভরতা যোগ করতে হবে।

প্রথমত, libmediapipe_jni.so এর সমস্ত ক্যালকুলেটর কোডে নির্ভরতা যোগ করুন তাই নিয়ম তৈরি করুন:

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

MediaPipe গ্রাফগুলি হল .pbtxt ফাইল, কিন্তু এগুলিকে অ্যাপ্লিকেশনে ব্যবহার করার জন্য, একটি .binarypb ফাইল তৈরি করতে আমাদের mediapipe_binary_graph বিল্ড নিয়ম ব্যবহার করতে হবে।

helloworld অ্যান্ড্রয়েড বাইনারি বিল্ড নিয়মে, একটি সম্পদ হিসাবে গ্রাফে নির্দিষ্ট 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 দ্বারা প্রস্তুত ক্যামেরা ফ্রেমগুলিকে মিডিয়াপিপ গ্রাফে পাঠায় এবং গ্রাফটি চালায়, আউটপুট প্রস্তুত করে এবং আউটপুট প্রদর্শনের জন্য 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 Surface টু VideoSurfaceOutput ছিল। এটি ধ্বংস হয়ে গেলে, আমরা processor VideoSurfaceOutput থেকে এটি সরিয়ে ফেলি।

আর এটাই! আপনি এখন ডিভাইসে অ্যাপ্লিকেশনটি সফলভাবে তৈরি করতে এবং চালাতে সক্ষম হবেন এবং একটি লাইভ ক্যামেরা ফিডে সোবেল প্রান্ত সনাক্তকরণ চলমান দেখতে পাবেন! অভিনন্দন!

edge_detection_android_gpu_gif

আপনি যদি কোনো সমস্যায় পড়ে থাকেন, অনুগ্রহ করে টিউটোরিয়ালের সম্পূর্ণ কোডটি এখানে দেখুন।