يمكنك استخدام أدوات تعلّم الآلة للتعرّف على الكيانات في صورة وتصنيفها. تتوافق واجهة برمجة التطبيقات هذه مع مجموعة كبيرة من نماذج تصنيف الصور المخصّصة. يُرجى الرجوع إلى النماذج المخصّصة باستخدام حزمة تعلّم الآلة للحصول على إرشادات حول متطلبات توافق النماذج، ومعرفة كيفية العثور على نماذج مدرّبة مسبقًا، وكيفية تدريب نماذجك الخاصة.
هناك طريقتان لدمج تصنيف الصور مع النماذج المخصّصة: إما عن طريق تجميع مسار التعلّم كجزء من تطبيقك، أو من خلال استخدام مسار غير مجمّع يعتمد على "خدمات Google Play". إذا حددت المسار غير المجمَّع، سيصبح تطبيقك أصغر. انظر الجدول ادناه للتعرُّف على التفاصيل.
مُجمَّعة | غير مجمعة | |
---|---|---|
اسم المكتبة | com.google.mlkit:image-labeling-custom | com.google.android.gms:play-services-mlkit-image-labeling-custom |
التنفيذ | مسار التعلّم مرتبط بشكل ثابت بتطبيقك في وقت الإصدار. | يتم تنزيل مسار التعلُّم ديناميكيًا عبر "خدمات Google Play". |
حجم التطبيق | زيادة في الحجم بمقدار 3.8 ميغابايت تقريبًا | زيادة في الحجم بمقدار 200 كيلوبايت تقريبًا |
وقت الإعداد | يتوفّر المسار على الفور. | قد تضطر إلى انتظار تنزيل مسار التعلّم قبل الاستخدام الأول. |
مرحلة مراحل نشاط واجهة برمجة التطبيقات | مدى التوفّر للجمهور العام (GA) | إصدار تجريبي |
هناك طريقتان لدمج نموذج مخصّص: تجميع النموذج من خلال وضعه داخل مجلد مواد العرض في التطبيق أو تنزيله ديناميكيًا من Firebase. يقارن الجدول التالي بين هذين الخيارين.
النموذج المجمّع | النموذج المستضاف |
---|---|
النموذج هو جزء من حزمة APK لتطبيقك، ما يزيد من حجمه. | النموذج ليس جزءًا من APK. وتتم استضافته عن طريق تحميله إلى أداة تعلُّم الآلة من Firebase. |
يتوفر الطراز على الفور، حتى عندما يكون جهاز Android غير متصل بالإنترنت. | يتم تنزيل النموذج عند الطلب |
عدم الحاجة إلى مشروع Firebase | يجب توفّر مشروع في Firebase. |
يجب إعادة نشر تطبيقك لتحديث النموذج. | إرسال تحديثات النموذج بدون إعادة نشر تطبيقك |
بدون اختبار A/B مدمج | إجراء اختبار A/B بسهولة باستخدام ميزة الإعداد عن بُعد في Firebase |
تجربة السمات والبيانات
- للاطّلاع على مثال حول استخدام النموذج المستضاف، يمكنك الاطّلاع على تطبيق vision سريع التشغيل للاطّلاع على مثال عن استخدام النموذج المجمّع وتطبيق التشغيل السريع automl للاطّلاع على مثال حول استخدام النموذج المستضاف.
قبل البدء
في ملف
build.gradle
على مستوى المشروع، احرص على تضمين مستودع Maven من Google في كل من القسمَينbuildscript
وallprojects
.أضِف التبعيات لمكتبات ML Kit لنظام التشغيل Android إلى ملف الدرجة على مستوى التطبيق في الوحدة، والذي يكون عادةً
app/build.gradle
. اختر إحدى التبعيات التالية بناءً على احتياجاتك:لدمج مسار التعلُّم مع تطبيقك:
dependencies { // ... // Use this dependency to bundle the pipeline with your app implementation 'com.google.mlkit:image-labeling-custom:17.0.2' }
لاستخدام مسار التعلّم في "خدمات Google Play":
dependencies { // ... // Use this dependency to use the dynamically downloaded pipeline in Google Play Services implementation 'com.google.android.gms:play-services-mlkit-image-labeling-custom:16.0.0-beta5' }
إذا اخترت استخدام مسار التعلّم في "خدمات Google Play"، يمكنك ضبط تطبيقك بحيث يتم تنزيل مسار التعلّم تلقائيًا إلى الجهاز بعد تثبيت التطبيق من "متجر Play". لإجراء ذلك، أضِف البيان التالي إلى ملف
AndroidManifest.xml
في تطبيقك:<application ...> ... <meta-data android:name="com.google.mlkit.vision.DEPENDENCIES" android:value="custom_ica" /> <!-- To use multiple downloads: android:value="custom_ica,download2,download3" --> </application>
يمكنك أيضًا التحقّق بشكل صريح من توفّر مسار التعلّم وطلب التنزيل من خلال ModuleInstallClient API في خدمات Google Play.
إذا لم تمكّن عمليات تنزيل مسار التثبيت أو تطلب تنزيلاً صريحًا، يتم تنزيل هذا المسار في المرة الأولى التي تشغّل فيها المصنِّف. لا تعرض الطلبات التي تجريها قبل اكتمال التنزيل أي نتائج.
أضف التبعية
linkFirebase
إذا كنت تريد تنزيل نموذج ديناميكيًا من Firebase:لتنزيل نموذج ديناميكيًا من Firebase، أضِف التبعية
linkFirebase
:dependencies { // ... // Image labeling feature with model downloaded from Firebase implementation 'com.google.mlkit:image-labeling-custom:17.0.2' // Or use the dynamically downloaded pipeline in Google Play Services // implementation 'com.google.android.gms:play-services-mlkit-image-labeling-custom:16.0.0-beta5' implementation 'com.google.mlkit:linkfirebase:17.0.0' }
إذا أردت تنزيل نموذج، تأكّد من إضافة Firebase إلى مشروع Android الخاص بك، إذا لم يسبق لك إجراء ذلك، فهذا الإجراء ليس مطلوبًا عند تجميع النموذج.
1- تحميل النموذج
ضبط مصدر نموذج محلي
لتجميع النموذج مع تطبيقك:
انسخ ملف النموذج (الذي ينتهي عادةً بالأرقام
.tflite
أو.lite
) إلى مجلدassets/
لتطبيقك. (قد تحتاج إلى إنشاء المجلد أولاً من خلال النقر بزر الماوس الأيمن على المجلدapp/
، ثم النقر على جديد > مجلد > مجلد مواد العرض).بعد ذلك، أضف ما يلي إلى ملف
build.gradle
في تطبيقك للتأكد من أنّ Gradle لا يضغط ملف النموذج عند إنشاء التطبيق:android { // ... aaptOptions { noCompress "tflite" // or noCompress "lite" } }
سيتم تضمين ملف النموذج في حزمة التطبيق وإتاحته لحزمة تعلّم الآلة كمادة عرض أولية.
إنشاء كائن
LocalModel
، مع تحديد المسار إلى ملف النموذج:Kotlin
val localModel = LocalModel.Builder() .setAssetFilePath("model.tflite") // or .setAbsoluteFilePath(absolute file path to model file) // or .setUri(URI to model file) .build()
Java
LocalModel localModel = new LocalModel.Builder() .setAssetFilePath("model.tflite") // or .setAbsoluteFilePath(absolute file path to model file) // or .setUri(URI to model file) .build();
ضبط مصدر نموذج مستضاف على Firebase
لاستخدام النموذج الذي تتم استضافته عن بُعد، أنشِئ عنصر RemoteModel
بحلول
FirebaseModelSource
، مع تحديد الاسم الذي تم تخصيصه للنموذج عند
نشره:
Kotlin
// Specify the name you assigned in the Firebase console. val remoteModel = CustomRemoteModel .Builder(FirebaseModelSource.Builder("your_model_name").build()) .build()
Java
// Specify the name you assigned in the Firebase console. CustomRemoteModel remoteModel = new CustomRemoteModel .Builder(new FirebaseModelSource.Builder("your_model_name").build()) .build();
بعد ذلك، ابدأ مهمة تنزيل النموذج، مع تحديد الشروط التي تريد السماح بالتنزيل بموجبها. إذا لم يكن النموذج مثبَّتًا على الجهاز أو في حال توفُّر إصدار أحدث من النموذج، سيتم تنزيل النموذج من Firebase بشكل غير متزامن:
Kotlin
val downloadConditions = DownloadConditions.Builder() .requireWifi() .build() RemoteModelManager.getInstance().download(remoteModel, downloadConditions) .addOnSuccessListener { // Success. }
Java
DownloadConditions downloadConditions = new DownloadConditions.Builder() .requireWifi() .build(); RemoteModelManager.getInstance().download(remoteModel, downloadConditions) .addOnSuccessListener(new OnSuccessListener() { @Override public void onSuccess(@NonNull Task task) { // Success. } });
تبدأ العديد من التطبيقات مهمّة التنزيل في رمز الإعداد الخاص بها، ويمكنك تنفيذ ذلك في أي وقت قبل أن تحتاج إلى استخدام النموذج.
ضبط أداة تصنيف الصور
بعد ضبط مصادر النماذج، يمكنك إنشاء كائن ImageLabeler
من أحدها.
تتوفّر الخيارات التالية:
الخيارات | |
---|---|
confidenceThreshold
|
الحد الأدنى لدرجة الثقة للتصنيفات التي تم رصدها. وفي حال عدم ضبط هذه السياسة، سيتم استخدام أي حدّ أدنى للمصنِّف تحدِّده البيانات الوصفية للنموذج. إذا كان النموذج لا يحتوي على أي بيانات وصفية أو إذا لم تحدّد البيانات الوصفية حدًا للمصنِّف، سيتم استخدام حد أقصى تلقائي هو 0.0. |
maxResultCount
|
الحد الأقصى لعدد التصنيفات المطلوب عرضها. وفي حال ترك هذه السياسة بدون ضبط، سيتم استخدام القيمة التلقائية التي تبلغ 10. |
إذا كان لديك فقط نموذج مجمَّع محليًا، ما عليك سوى إنشاء تصنيف من
كائن LocalModel
:
Kotlin
val customImageLabelerOptions = CustomImageLabelerOptions.Builder(localModel) .setConfidenceThreshold(0.5f) .setMaxResultCount(5) .build() val labeler = ImageLabeling.getClient(customImageLabelerOptions)
Java
CustomImageLabelerOptions customImageLabelerOptions = new CustomImageLabelerOptions.Builder(localModel) .setConfidenceThreshold(0.5f) .setMaxResultCount(5) .build(); ImageLabeler labeler = ImageLabeling.getClient(customImageLabelerOptions);
إذا كان لديك نموذج مُستضاف عن بُعد، عليك التأكّد ممّا إذا تمّ تنزيله قبل تشغيله. ويمكنك التحقّق من حالة مهمة تنزيل النموذج
باستخدام طريقة isModelDownloaded()
لدى مدير النماذج.
على الرغم من أنّه عليك فقط تأكيد ذلك قبل تشغيل المُصنِّف، إذا كان لديك نموذج مُستضاف عن بُعد ونموذج مجمَّع محليًا، قد يكون من المنطقي إجراء هذا الفحص عند إنشاء مثيل لمصنّف الصورة: عليك إنشاء مُصنِّف من النموذج البعيد إذا كان قد تمّ تنزيله ومن النموذج المحلي.
Kotlin
RemoteModelManager.getInstance().isModelDownloaded(remoteModel) .addOnSuccessListener { isDownloaded -> val optionsBuilder = if (isDownloaded) { CustomImageLabelerOptions.Builder(remoteModel) } else { CustomImageLabelerOptions.Builder(localModel) } val options = optionsBuilder .setConfidenceThreshold(0.5f) .setMaxResultCount(5) .build() val labeler = ImageLabeling.getClient(options) }
Java
RemoteModelManager.getInstance().isModelDownloaded(remoteModel) .addOnSuccessListener(new OnSuccessListener() { @Override public void onSuccess(Boolean isDownloaded) { CustomImageLabelerOptions.Builder optionsBuilder; if (isDownloaded) { optionsBuilder = new CustomImageLabelerOptions.Builder(remoteModel); } else { optionsBuilder = new CustomImageLabelerOptions.Builder(localModel); } CustomImageLabelerOptions options = optionsBuilder .setConfidenceThreshold(0.5f) .setMaxResultCount(5) .build(); ImageLabeler labeler = ImageLabeling.getClient(options); } });
إذا لم يكن لديك سوى نموذج مُستضاف عن بُعد، يجب إيقاف الوظائف ذات الصلة بالنموذج، على سبيل المثال، تعطيل أو إخفاء جزء من واجهة المستخدم، حتى تتأكد من تنزيل النموذج. يمكنك إجراء ذلك من خلال إرفاق مستمع
بطريقة download()
الخاصة بمدير النماذج:
Kotlin
RemoteModelManager.getInstance().download(remoteModel, conditions) .addOnSuccessListener { // Download complete. Depending on your app, you could enable the ML // feature, or switch from the local model to the remote model, etc. }
Java
RemoteModelManager.getInstance().download(remoteModel, conditions) .addOnSuccessListener(new OnSuccessListener() { @Override public void onSuccess(Void v) { // Download complete. Depending on your app, you could enable // the ML feature, or switch from the local model to the remote // model, etc. } });
2. تحضير صورة الإدخال
بعد ذلك، أنشئ كائنInputImage
من الصورة لكل صورة تريد تصنيفها. يعمل تصنيف الصور بشكل أسرع عند استخدام Bitmap
أو إذا كنت تستخدم Camera2 API، YUV_420_888 media.Image
،
وننصح باستخدام هذه الواجهة عندما يكون ذلك ممكنًا.
يمكنك إنشاء كائن InputImage
من مصادر مختلفة، وفيما يلي شرح لكل منها.
استخدام media.Image
لإنشاء عنصر InputImage
من عنصر media.Image
، مثلاً عند التقاط صورة من
كاميرا جهاز، مرِّر الكائن media.Image
وتدوير الصورة
إلى InputImage.fromMediaImage()
.
إذا كنت تستخدم مكتبة
CameraX، ستحتسب الفئتان OnImageCapturedListener
وImageAnalysis.Analyzer
قيمة التدوير
نيابةً عنك.
Kotlin
private class YourImageAnalyzer : ImageAnalysis.Analyzer { override fun analyze(imageProxy: ImageProxy) { val mediaImage = imageProxy.image if (mediaImage != null) { val image = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees) // Pass image to an ML Kit Vision API // ... } } }
Java
private class YourAnalyzer implements ImageAnalysis.Analyzer { @Override public void analyze(ImageProxy imageProxy) { Image mediaImage = imageProxy.getImage(); if (mediaImage != null) { InputImage image = InputImage.fromMediaImage(mediaImage, imageProxy.getImageInfo().getRotationDegrees()); // Pass image to an ML Kit Vision API // ... } } }
إذا كنت لا تستخدم مكتبة كاميرا تمنحك درجة دوران الصورة، يمكنك احتسابها من درجة دوران الجهاز واتجاه أداة استشعار الكاميرا في الجهاز:
Kotlin
private val ORIENTATIONS = SparseIntArray() init { ORIENTATIONS.append(Surface.ROTATION_0, 0) ORIENTATIONS.append(Surface.ROTATION_90, 90) ORIENTATIONS.append(Surface.ROTATION_180, 180) ORIENTATIONS.append(Surface.ROTATION_270, 270) } /** * Get the angle by which an image must be rotated given the device's current * orientation. */ @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) @Throws(CameraAccessException::class) private fun getRotationCompensation(cameraId: String, activity: Activity, isFrontFacing: Boolean): Int { // Get the device's current rotation relative to its "native" orientation. // Then, from the ORIENTATIONS table, look up the angle the image must be // rotated to compensate for the device's rotation. val deviceRotation = activity.windowManager.defaultDisplay.rotation var rotationCompensation = ORIENTATIONS.get(deviceRotation) // Get the device's sensor orientation. val cameraManager = activity.getSystemService(CAMERA_SERVICE) as CameraManager val sensorOrientation = cameraManager .getCameraCharacteristics(cameraId) .get(CameraCharacteristics.SENSOR_ORIENTATION)!! if (isFrontFacing) { rotationCompensation = (sensorOrientation + rotationCompensation) % 360 } else { // back-facing rotationCompensation = (sensorOrientation - rotationCompensation + 360) % 360 } return rotationCompensation }
Java
private static final SparseIntArray ORIENTATIONS = new SparseIntArray(); static { ORIENTATIONS.append(Surface.ROTATION_0, 0); ORIENTATIONS.append(Surface.ROTATION_90, 90); ORIENTATIONS.append(Surface.ROTATION_180, 180); ORIENTATIONS.append(Surface.ROTATION_270, 270); } /** * Get the angle by which an image must be rotated given the device's current * orientation. */ @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) private int getRotationCompensation(String cameraId, Activity activity, boolean isFrontFacing) throws CameraAccessException { // Get the device's current rotation relative to its "native" orientation. // Then, from the ORIENTATIONS table, look up the angle the image must be // rotated to compensate for the device's rotation. int deviceRotation = activity.getWindowManager().getDefaultDisplay().getRotation(); int rotationCompensation = ORIENTATIONS.get(deviceRotation); // Get the device's sensor orientation. CameraManager cameraManager = (CameraManager) activity.getSystemService(CAMERA_SERVICE); int sensorOrientation = cameraManager .getCameraCharacteristics(cameraId) .get(CameraCharacteristics.SENSOR_ORIENTATION); if (isFrontFacing) { rotationCompensation = (sensorOrientation + rotationCompensation) % 360; } else { // back-facing rotationCompensation = (sensorOrientation - rotationCompensation + 360) % 360; } return rotationCompensation; }
بعد ذلك، مرِّر الكائن media.Image
وقيمة درجة الدوران إلى InputImage.fromMediaImage()
:
Kotlin
val image = InputImage.fromMediaImage(mediaImage, rotation)
Java
InputImage image = InputImage.fromMediaImage(mediaImage, rotation);
استخدام معرّف موارد منتظم (URI) للملف
لإنشاء كائن InputImage
من معرّف موارد منتظم (URI) للملف، أدخِل سياق التطبيق ومعرّف الموارد المنتظم (URI) للملف إلى InputImage.fromFilePath()
. ويكون هذا الإجراء مفيدًا عند
استخدام هدف ACTION_GET_CONTENT
لتوجيه المستخدم إلى اختيار
صورة من تطبيق معرض الصور الخاص به.
Kotlin
val image: InputImage try { image = InputImage.fromFilePath(context, uri) } catch (e: IOException) { e.printStackTrace() }
Java
InputImage image; try { image = InputImage.fromFilePath(context, uri); } catch (IOException e) { e.printStackTrace(); }
استخدام ByteBuffer
أو ByteArray
لإنشاء عنصر InputImage
من ByteBuffer
أو ByteArray
، يجب أولاً احتساب درجة تدوير الصورة على النحو الموضّح سابقًا لإدخال media.Image
.
بعد ذلك، يمكنك إنشاء الكائن InputImage
باستخدام المخزن المؤقت أو المصفوفة، بالإضافة إلى ارتفاع الصورة وعرضها وتنسيق ترميز الألوان ودرجة تدويرها:
Kotlin
val image = InputImage.fromByteBuffer( byteBuffer, /* image width */ 480, /* image height */ 360, rotationDegrees, InputImage.IMAGE_FORMAT_NV21 // or IMAGE_FORMAT_YV12 ) // Or: val image = InputImage.fromByteArray( byteArray, /* image width */ 480, /* image height */ 360, rotationDegrees, InputImage.IMAGE_FORMAT_NV21 // or IMAGE_FORMAT_YV12 )
Java
InputImage image = InputImage.fromByteBuffer(byteBuffer, /* image width */ 480, /* image height */ 360, rotationDegrees, InputImage.IMAGE_FORMAT_NV21 // or IMAGE_FORMAT_YV12 ); // Or: InputImage image = InputImage.fromByteArray( byteArray, /* image width */480, /* image height */360, rotation, InputImage.IMAGE_FORMAT_NV21 // or IMAGE_FORMAT_YV12 );
استخدام Bitmap
لإنشاء عنصر InputImage
من عنصر Bitmap
، عليك إجراء التعريف التالي:
Kotlin
val image = InputImage.fromBitmap(bitmap, 0)
Java
InputImage image = InputImage.fromBitmap(bitmap, rotationDegree);
يتم تمثيل الصورة بكائن Bitmap
مع درجات التدوير.
3. تشغيل برنامج تصنيف الصور
لتصنيف الكائنات في صورة، مرِّر الكائن image
إلى طريقة process()
في ImageLabeler
.
Kotlin
labeler.process(image) .addOnSuccessListener { labels -> // Task completed successfully // ... } .addOnFailureListener { e -> // Task failed with an exception // ... }
Java
labeler.process(image) .addOnSuccessListener(new OnSuccessListener<List<ImageLabel>>() { @Override public void onSuccess(List<ImageLabel> labels) { // Task completed successfully // ... } }) .addOnFailureListener(new OnFailureListener() { @Override public void onFailure(@NonNull Exception e) { // Task failed with an exception // ... } });
4. الحصول على معلومات عن الكيانات المصنّفة
إذا نجحت عملية تصنيف الصور، يتم تمرير قائمة بكائناتImageLabel
إلى أداة معالجة الحدث. يمثل كل عنصر ImageLabel
عنصرًا تم تصنيفه في الصورة. يمكنك الحصول على الوصف النصي لكل تسمية (إذا كان متاحًا في البيانات الوصفية لملف نموذج TensorFlow Lite) ونتيجة الثقة والفهرس. مثال:
Kotlin
for (label in labels) { val text = label.text val confidence = label.confidence val index = label.index }
Java
for (ImageLabel label : labels) { String text = label.getText(); float confidence = label.getConfidence(); int index = label.getIndex(); }
نصائح لتحسين الأداء في الوقت الفعلي
إذا أردت تصنيف الصور في تطبيق في الوقت الفعلي، عليك اتّباع الإرشادات التالية لتحقيق أفضل معدلات عرض الإطارات:
- إذا كنت تستخدم واجهة برمجة التطبيقات
Camera
أوcamera2
، يمكنك تقليل حجم الطلبات إلى أداة تصنيف الصور. في حال توفّر إطار فيديو جديد أثناء تشغيل مُصنِّف الصور، أفلِته. للاطّلاع على مثال، يمكنك الاطّلاع على صفVisionProcessorBase
في نموذج التطبيق السريع. - إذا كنت تستخدم واجهة برمجة التطبيقات
CameraX
، تأكَّد من ضبط استراتيجية الضغط العكسي على قيمتها التلقائيةImageAnalysis.STRATEGY_KEEP_ONLY_LATEST
. يضمن ذلك تسليم صورة واحدة فقط لتحليلها في كل مرة. في حال إنشاء المزيد من الصور عندما تكون أداة التحليل مشغولة، سيتم تجاهلها تلقائيًا ولن يتم وضعها في قائمة الانتظار للتسليم. بعد إغلاق الصورة التي يتم تحليلها عن طريق استدعاء ImageProxy.Close()، سيتم عرض آخر صورة تالية. - إذا كنت تستخدم مُخرجات تصنيف الصور لتركيب رسومات على الصورة التي تم إدخالها، احصل أولاً على النتيجة من ML Kit، ثم اعرض الصورة والتراكب في خطوة واحدة. ويتم عرض هذا النص على سطح الشاشة
مرة واحدة فقط لكل إطار إدخال. للاطّلاع على مثال، يمكنك الاطّلاع على صفَي
CameraSourcePreview
وGraphicOverlay
في نموذج تطبيق البدء السريع. - في حال استخدام Camera2 API، التقِط الصور بتنسيق
ImageFormat.YUV_420_888
. إذا كنت تستخدم واجهة برمجة تطبيقات الكاميرا القديمة، يجب التقاط الصور بتنسيقImageFormat.NV21
.