מבוא
במדריך הזה של Hello World! נעשה שימוש ב-MediaPipe Framework כדי לפתח אפליקציה ל-Android שמפעילה תרשים MediaPipe ב-Android.
מה תפַתחו
אפליקציית מצלמה פשוטה לזיהוי קצה Sobel בזמן אמת שמוחלת על שידור וידאו בשידור חי במכשיר Android.
הגדרה
- לפרטים נוספים, קראו את המדריך להתקנת מסגרת כדי להתקין את MediaPipe Framework.
- התקן את Android Development SDK ו-Android NDK. קראו גם איך לעשות זאת [במדריך להתקנת מסגרת].
- מפעילים את האפשרויות למפתחים במכשיר Android.
- כדי לפתח את האפליקציה ל-Android ולפרוס אותה, צריך להגדיר את 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"
}
המחשה חזותית של התרשים מוצגת בהמשך:
התרשים הזה כולל מקור קלט יחיד בשם input_video
לכל הפריימים הנכנסים שיסופקו על ידי מצלמת המכשיר.
הצומת הראשון בתרשים, LuminanceCalculator
, לוקח חבילה יחידה (מסגרת תמונה) ומחיל שינוי בבהירות באמצעות תוכנת הצללה של OpenGL. מסגרת התמונה שתתקבל נשלחת לזרם הפלט luma_video
.
הצומת השני, SobelEdgesCalculator
, מחיל זיהוי קצה על חבילות נכנסות ב-stream של 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);
}
}
מוסיפים ל-$APPLICATION_PATH
קובץ מניפסט, AndroidManifest.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
, ולכן אנחנו צריכים הפניות מתאימות לעיצוב. הוספה של 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!
.
שימוש במצלמה דרך 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>
כשהמשתמש לא מעניק הרשאת גישה למצלמה, המסך ייראה כך:
עכשיו נוסיף את האובייקטים 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
ב-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. ה-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
:
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, אנחנו צריכים להוסיף יחסי תלות ל-framework של MediaPipe ב-Android. קודם כול נוסיף כלל build כדי ליצור 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"
לכלל ה-build של mediapipe_lib
בקובץ BUILD
.
בשלב הבא צריך להוסיף יחסי תלות ספציפיים לתרשים MediaPipe שבו אנחנו רוצים להשתמש באפליקציה.
קודם כול, מוסיפים יחסי תלות לכל קוד המחשבון בכלל ה-build של libmediapipe_jni.so
:
"//mediapipe/graphs/edge_detection:mobile_calculators",
הגרפים של MediaPipe הם קובצי .pbtxt
, אבל כדי להשתמש בהם באפליקציה אנחנו צריכים להשתמש בכלל ה-build mediapipe_binary_graph
על מנת ליצור קובץ .binarypb
.
בכלל ה-build הבינארי של helloworld
ל-Android, מוסיפים את היעד 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
. אחרי אתחול השורה 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 פועל בפיד המצלמה בשידור חי! איזה כיף!
אם נתקלת בבעיות, אפשר לעיין בקוד המלא של המדריך כאן.