Hello World! ב-Android

מבוא

שלום לעולם הזה! המדריך משתמש ב-MediaPipe Framework כדי לפתח אפליקציה ל-Android מפעיל תרשים MediaPipe ב-Android.

מה תפַתחו

אפליקציית מצלמה פשוטה לזיהוי קצה של Sobel בזמן אמת, שמופעלת על סרטון בשידור חי להפעיל סטרימינג במכשיר Android.

edge_detection_android_gpu_gif

הגדרה

  1. למידע על התקנת MediaPipe Framework במערכת, ראו התקנת Framework מדריך לפרטים.
  2. מתקינים את Android Development SDK ו-Android NDK. איך לעשות את זה גם [מדריך להתקנת framework].
  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 בכל ה-Codelab.

שימו לב שבנתיב לאפליקציה:

  • שם האפליקציה הוא 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 בכלל הבינארי של Android 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. צריך להוסיף תלות ל-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 object:

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. קודם נוסיף כלל build כדי לבנות cc_binary באמצעות קוד JNI של ה-framework של 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" לכלל ה-build של mediapipe_lib ב- את הקובץ BUILD.

בשלב הבא צריך להוסיף יחסי תלות ספציפיים לתרשים MediaPipe שבו רוצים להשתמש באפליקציה.

קודם כול צריך להוסיף יחסי תלות לכל קוד המחשבון בשדה libmediapipe_jni.so כלל build:

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

תרשימי MediaPipe הם קבצים מסוג .pbtxt, אך כדי להשתמש בהם באפליקציה אנחנו צריכים כדי להשתמש בכלל ה-build mediapipe_binary_graph כדי ליצור קובץ .binarypb.

בכלל ה-build הבינארי של Android helloworld, מוסיפים את mediapipe_binary_graph ספציפי לתרשים כנכס:

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

בכלל ה-build של 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 צריך לטעון את ה-framework של MediaPipe. כמו כן, framework משתמש ב-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 End פועלת במצלמה בשידור חי פיד! מזל טוב!

edge_detection_android_gpu_gif

אם נתקלת בבעיות, אפשר לעיין בקוד המלא של המדריך כאן.