مقدمة
يستخدم برنامج Hello World! التعليمي إطار عمل MediaPipe لتطوير تطبيق Android يستخدم رسمًا بيانيًا لـ MediaPipe على Android.
ما ستقوم بإنشائه
هو تطبيق كاميرا بسيط لرصد حافة Sobel في الوقت الفعلي عند تطبيقه على بث فيديو مباشر على جهاز Android.
ضبط إعدادات الجهاز
- ثبِّت إطار عمل MediaPipe على النظام الخاص بك، ويمكنك الاطّلاع على دليل تثبيت إطار العمل لمعرفة التفاصيل.
- قم بتثبيت Android Development SDK وAndroid NDK. يمكنك الاطّلاع على كيفية إجراء ذلك أيضًا في [دليل تثبيت إطار العمل].
- فعِّل خيارات المطوّرين على جهاز Android.
- عليك إعداد 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"
}
في ما يلي عرض مرئي للرسم البياني:
يحتوي هذا الرسم البياني على بث إدخال واحد باسم 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!
.
استخدام الكاميرا عبر 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>
عندما لا يمنح المستخدم إذنًا بالوصول إلى الكاميرا، ستبدو الشاشة على النحو التالي:
الآن، سنضيف الكائنين 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 قيد التشغيل على خلاصة كاميرا مباشرة. تهانينا!
إذا واجهت أي مشاكل، يُرجى الاطّلاع على الرمز الكامل للبرنامج التعليمي هنا.