مرحبًا بالجميع على Android

مقدمة

يستخدم برنامج Hello World! التعليمي إطار عمل MediaPipe لتطوير تطبيق Android يستخدم رسمًا بيانيًا لـ MediaPipe على Android.

ما ستقوم بإنشائه

هو تطبيق كاميرا بسيط لرصد حافة Sobel في الوقت الفعلي عند تطبيقه على بث فيديو مباشر على جهاز Android.

edge_detection_android_gpu_gif

ضبط إعدادات الجهاز

  1. ثبِّت إطار عمل MediaPipe على النظام الخاص بك، ويمكنك الاطّلاع على دليل تثبيت إطار العمل لمعرفة التفاصيل.
  2. قم بتثبيت Android Development SDK وAndroid NDK. يمكنك الاطّلاع على كيفية إجراء ذلك أيضًا في [دليل تثبيت إطار العمل].
  3. فعِّل خيارات المطوّرين على جهاز Android.
  4. عليك إعداد Bazel على نظامك لإنشاء تطبيق Android ونشره.

رسم بياني لرصد الحواف

سنستخدم الرسم البياني التالي، 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 بسيط يعرض عبارة "Hello World!" على الشاشة. يمكنك تخطّي هذه الخطوة إذا كنت على دراية بإنشاء تطبيقات Android باستخدام bazel.

عليك إنشاء دليل جديد لإنشاء تطبيق Android عليه. على سبيل المثال، يمكن العثور على الرمز الكامل لهذا البرنامج التعليمي على 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، وهي 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. يوفر إطار العمل أداة تُسمى 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 عادي. يوفّر إطار العمل فئة، وهي ExternalTextureConverter لتحويل الصورة المخزَّنة في كائن SurfaceTexture إلى كائن زخرفة OpenGL عادي.

لاستخدام ExternalTextureConverter، نحتاج أيضًا إلى EGLContext الذي يتم إنشاؤه وإدارته من خلال عنصر EglManager. أضف تبعية إلى ملف BUILD لاستخدام EglManager، "//mediapipe/java/com/google/mediapipe/glutil".

في 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 البياني في نظام Android

إضافة التبعيات ذات الصلة

لاستخدام رسم MediaPipe البياني، علينا إضافة تبعيات إلى إطار عمل MediaPipe على نظام Android. سنضيف أولاً قاعدة إنشاء لإنشاء 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.

في قاعدة الإصدار الثنائي لنظام Android 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.

عليك إعداد مدير مواد العرض في onCreate(Bundle) قبل إعداد eglManager:

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

وإعداده في onCreate(Bundle) بعد إعداد eglManager:

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

يحتاج processor إلى استهلاك الإطارات المحوَّلة من converter للمعالجة. يمكنك إضافة السطر التالي إلى onResume() بعد إعداد converter:

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

إذا واجهت أي مشاكل، يُرجى الاطّلاع على الرمز الكامل للبرنامج التعليمي هنا.