سلام دنیا! در اندروید

مقدمه

این سلام دنیا! آموزش از MediaPipe Framework برای توسعه یک برنامه اندرویدی استفاده می کند که یک نمودار MediaPipe را در اندروید اجرا می کند.

آنچه را که خواهید ساخت

یک برنامه دوربین ساده برای تشخیص لبه Sobel در زمان واقعی که در یک پخش ویدیوی زنده در دستگاه Android اعمال می شود.

edge_detection_android_gpu_gif

راه اندازی

  1. MediaPipe Framework را روی سیستم خود نصب کنید، برای جزئیات به راهنمای نصب Framework مراجعه کنید.
  2. Android Development SDK و Android NDK را نصب کنید. نحوه انجام این کار را نیز در [راهنمای نصب چارچوب] مشاهده کنید.
  3. گزینه های توسعه دهنده را در دستگاه Android خود فعال کنید.
  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 را نمایش می دهد.

حداقل راه اندازی اولیه برنامه

ابتدا با یک برنامه اندروید ساده شروع می کنیم که "Hello World!" روی صفحه نمایش اگر با ساخت برنامه های اندروید با استفاده از bazel آشنا هستید، می توانید این مرحله را نادیده بگیرید.

یک دایرکتوری جدید ایجاد کنید که در آن برنامه اندروید خود را ایجاد کنید. برای مثال، کد کامل این آموزش را می‌توانید در mediapipe/examples/android/src/java/com/google/mediapipe/apps/basic بیابید. ما به این مسیر به عنوان $APPLICATION_PATH در سراسر کدها اشاره خواهیم کرد.

توجه داشته باشید که در مسیر برنامه:

  • نام برنامه helloworld است.
  • $PACKAGE_PATH برنامه com.google.mediapipe.apps.basic است. این مورد در قطعه‌های کد در این آموزش استفاده می‌شود، بنابراین لطفاً به یاد داشته باشید که هنگام کپی/استفاده از قطعه‌های کد $PACKAGE_PATH خودتان استفاده کنید.

یک فایل activity_main.xml را به $APPLICATION_PATH/res/layout اضافه کنید. این یک TextView در تمام صفحه برنامه با رشته Hello World! :

<?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>

یک MainActivity.java ساده به $APPLICATION_PATH اضافه کنید که محتوای طرح‌بندی 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);
  }
}

یک فایل مانیفست، 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>

برای ساختن برنامه، یک فایل BUILD به $APPLICATION_PATH اضافه کنید، و ${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 basic_lib تولید شده برای ساخت یک APK باینری برای نصب در دستگاه Android شما استفاده می کند.

برای ساخت اپلیکیشن از دستور زیر استفاده کنید:

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

فایل APK تولید شده را با استفاده از adb install نصب کنید. به عنوان مثال:

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 Framework ارائه می‌شود، یعنی PermissionHelper استفاده کنیم. برای استفاده از آن، یک وابستگی "//mediapipe/java/com/google/mediapipe/components:android_components" را در قانون mediapipe_lib در BUILD اضافه کنید.

برای استفاده از PermissionHelper در MainActivity ، خط زیر را به تابع 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 ذخیره می شود. برای استفاده از اینها، ابتدا باید طرح برنامه خود را تغییر دهیم.

کل بلوک کد TextView را از $APPLICATION_PATH/res/layout/activity_main.xml حذف کنید و به جای آن کد زیر را اضافه کنید:

<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 است. هنگامی که مجوز دسترسی به دوربین داده نمی شود، برنامه ما 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 اضافه می کنیم تا بتوانیم از آن برای نمایش فریم های دوربین با استفاده از یک شی SurfaceTexture به نام previewFrameTexture استفاده کنیم.

برای استفاده از previewFrameTexture برای دریافت فریم دوربین، از CameraX استفاده می کنیم. Framework ابزاری به نام CameraXPreviewHelper برای استفاده از CameraX فراهم می کند. این کلاس هنگامی که دوربین از طریق 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>

و انتخاب را در BUILD در قانون باینری اندروید helloworld با یک ورودی جدید در manifest_values ​​مشخص کنید:

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

اکنون، در MainActivity برای بازیابی متادیتا مشخص شده در manifest_values ، یک شی 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);

در این مرحله، برنامه باید با موفقیت ساخته شود. با این حال، هنگامی که برنامه را روی دستگاه خود اجرا می کنید، یک صفحه سیاه می بینید (حتی اگر مجوز دوربین داده شده باشد). این به این دلیل است که حتی با وجود اینکه متغیر surfaceTexture ارائه شده توسط CameraXPreviewHelper را ذخیره می کنیم، previewSurfaceView هنوز از خروجی آن استفاده نمی کند و آن را روی صفحه نمایش نمی دهد.

از آنجایی که می خواهیم از فریم ها در نمودار MediaPipe استفاده کنیم، در این آموزش کدی برای مشاهده مستقیم خروجی دوربین اضافه نمی کنیم. در عوض، از نحوه ارسال فریم های دوربین برای پردازش به نمودار MediaPipe و نمایش خروجی نمودار روی صفحه می گذریم.

راه اندازی ExternalTextureConverter

SurfaceTexture فریم های تصویر را از یک جریان به عنوان یک بافت OpenGL ES می گیرد. برای استفاده از نمودار MediaPipe، فریم های گرفته شده از دوربین باید در یک شیء بافت معمولی Open GL ذخیره شوند. Framework یک کلاس ExternalTextureConverter برای تبدیل تصویر ذخیره شده در یک شی SurfaceTexture به یک آبجکت بافت معمولی OpenGL ارائه می دهد.

برای استفاده از 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);

به یاد بیاورید که ما تابع onResume() در MainActivity تعریف کردیم تا تأیید کنیم مجوزهای دوربین اعطا شده است و startCamera() را فراخوانی کنیم. قبل از این بررسی، خط زیر را در onResume() اضافه کنید تا شی converter اولیه شود:

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

این converter اکنون از GLContext مدیریت شده توسط eglManager استفاده می کند.

همچنین باید تابع onPause() را در MainActivity نادیده بگیریم تا اگر برنامه به حالت مکث رفت، 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) {}
     });

در این بلوک کد، ما یک SurfaceHolder.Callback سفارشی را به previewDisplayView اضافه می کنیم و تابع surfaceChanged(SurfaceHolder holder, int format, int width, int height) برای محاسبه اندازه نمایش مناسب فریم های دوربین روی صفحه نمایش دستگاه و گره زدن previewFrameTexture شی و ارسال فریم های displaySize محاسبه شده به converter .

ما اکنون آماده استفاده از فریم های دوربین در نمودار MediaPipe هستیم.

استفاده از نمودار MediaPipe در اندروید

وابستگی های مربوطه را اضافه کنید

برای استفاده از نمودار MediaPipe، باید وابستگی هایی را به چارچوب MediaPipe در اندروید اضافه کنیم. ابتدا یک قانون ساخت برای ساختن یک cc_binary با استفاده از کد JNI چارچوب MediaPipe اضافه می کنیم و سپس یک قانون 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,
)

وابستگی ":mediapipe_jni_lib" را به قانون ساخت mediapipe_lib در فایل BUILD اضافه کنید.

در مرحله بعد، باید وابستگی‌هایی را به نمودار MediaPipe که می‌خواهیم در برنامه استفاده کنیم اضافه کنیم.

ابتدا، وابستگی ها را به همه کدهای ماشین حساب در قانون ساخت libmediapipe_jni.so اضافه کنید:

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

نمودارهای MediaPipe فایل‌های .pbtxt هستند، اما برای استفاده از آنها در برنامه، باید از قانون ساخت mediapipe_binary_graph برای تولید یک فایل .binarypb استفاده کنیم.

در قانون ساخت باینری اندروید 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 نام فایل گراف باینری را نشان می‌دهد که توسط فیلد output_name در هدف mediapipe_binary_graph تعیین می‌شود. 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 استفاده کنید

ابتدا باید دارایی را بارگذاری کنیم که حاوی .binarypb است که از فایل .pbtxt گراف کامپایل شده است. برای انجام این کار، می توانیم از ابزار 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 ایجاد شد، ما Surface به VideoSurfaceOutput processor را داشتیم. وقتی از بین رفت، آن را از VideoSurfaceOutput processor حذف می کنیم.

و بس! اکنون باید بتوانید با موفقیت برنامه را روی دستگاه بسازید و اجرا کنید و تشخیص لبه Sobel را در حال اجرا در فید دوربین زنده مشاهده کنید! تبریک میگم

edge_detection_android_gpu_gif

اگر با مشکلی مواجه شدید، لطفاً کد کامل آموزش را اینجا ببینید.