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

مقدمة

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

ما الذي ستقوم ببنائه

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

edge_detection_android_gpu_gif

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

  1. تثبيت إطار عمل MediaPipe على النظام، وراجِع تثبيت إطار العمل. الدليل للاطّلاع على التفاصيل.
  2. تثبيت حزمة تطوير البرامج (SDK) لتطوير Android و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 يعرض هذا الإجراء a 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 واستهداف إصدار الحزمة إلى 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 الثنائية لنظام Android مع إدخال جديد في 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

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