يمكنك استخدام ML Kit لتصنيف الأجسام التي يتم التعرّف عليها في الصورة، وذلك باستخدام إما نموذج على الجهاز أو نموذج على السحابة الإلكترونية. اطّلِع على نظرة عامة للتعرّف على مزايا كل نهج.
قبل البدء
- أضِف Firebase إلى مشروع Android، في حال لم يسبق لك إجراء ذلك.
- أضِف التبعيات لمكتبات ML Kit لنظام التشغيل Android إلى ملف Gradle
(على مستوى التطبيق) للوحدة (عادةً
app/build.gradle
):apply plugin: 'com.android.application' apply plugin: 'com.google.gms.google-services' dependencies { // ... implementation 'com.google.firebase:firebase-ml-vision:24.0.3' implementation 'com.google.firebase:firebase-ml-vision-image-label-model:20.0.1' }
-
إجراء اختياري ولكن يُنصح به: في حال استخدام واجهة برمجة التطبيقات على الجهاز، عليك ضبط إعدادات
تطبيقك لتنزيل نموذج الذكاء الاصطناعي تلقائيًا على الجهاز بعد تثبيت
تطبيقك من "متجر Play".
لإجراء ذلك، أضِف البيان التالي إلى ملف
AndroidManifest.xml
في تطبيقك: في حال عدم تفعيل عمليات تنزيل النماذج في وقت التثبيت، سيتم تنزيل النموذج في المرة الأولى التي يتم فيها تشغيل أداة الكشف على الجهاز. ولن تؤدي الطلبات التي تقدّمها قبل اكتمال عملية التنزيل إلى أي نتائج.<application ...> ... <meta-data android:name="com.google.firebase.ml.vision.DEPENDENCIES" android:value="label" /> <!-- To use multiple models: android:value="label,model2,model3" --> </application>
-
إذا كنت تريد استخدام النموذج المستنِد إلى السحابة الإلكترونية ولم يسبق لك تفعيل واجهات برمجة التطبيقات المستنِدة إلى السحابة الإلكترونية لمشروعك، يمكنك إجراء ذلك الآن:
- افتح صفحة واجهات برمجة التطبيقات في ML Kit في وحدة تحكّم Firebase.
-
إذا لم تكن قد أجريت ترقية لمشروعك إلى خطة أسعار Blaze، انقر على ترقية لإجراء ذلك. (لن يُطلب منك إجراء الترقية إلا إذا كان مشروعك غير مُدرَج في خطة Blaze).
يمكن للمشاريع على مستوى Blaze فقط استخدام واجهات برمجة التطبيقات المستندة إلى Cloud.
- إذا لم تكن واجهات برمجة التطبيقات المستندة إلى السحابة الإلكترونية مفعّلة، انقر على تفعيل واجهات برمجة التطبيقات المستندة إلى السحابة الإلكترونية.
إذا كنت تريد استخدام النموذج على الجهاز فقط، يمكنك تخطّي هذه الخطوة.
يمكنك الآن تصنيف الصور باستخدام نموذج على الجهاز أو نموذج مستند إلى السحابة الإلكترونية.
1. تجهيز صورة الإدخال
أنشئ عنصرًاFirebaseVisionImage
من صورتك.
يتم تشغيل أداة وضع العلامات على الصور بأسرع شكل عند استخدام Bitmap
أو media.Image
بتنسيق JPEG في حال استخدام
camera2 API، ويُنصح باستخدامهما كلما أمكن.
-
لإنشاء عنصر
FirebaseVisionImage
منmedia.Image
، مثلاً عند التقاط صورة من كاميرا الجهاز، عليك تمرير عنصرmedia.Image
ودرجة دوران الصورة إلىFirebaseVisionImage.fromMediaImage()
.إذا كنت تستخدِم مكتبة CameraX، تحتسِب فئتَا
OnImageCapturedListener
وImageAnalysis.Analyzer
قيمة التدوير بالنيابة عنك، لذا ما عليك سوى تحويل التدوير إلى أحد ثوابتROTATION_
في ML Kit قبل استدعاءFirebaseVisionImage.fromMediaImage()
:Java
private class YourAnalyzer implements ImageAnalysis.Analyzer { private int degreesToFirebaseRotation(int degrees) { switch (degrees) { case 0: return FirebaseVisionImageMetadata.ROTATION_0; case 90: return FirebaseVisionImageMetadata.ROTATION_90; case 180: return FirebaseVisionImageMetadata.ROTATION_180; case 270: return FirebaseVisionImageMetadata.ROTATION_270; default: throw new IllegalArgumentException( "Rotation must be 0, 90, 180, or 270."); } } @Override public void analyze(ImageProxy imageProxy, int degrees) { if (imageProxy == null || imageProxy.getImage() == null) { return; } Image mediaImage = imageProxy.getImage(); int rotation = degreesToFirebaseRotation(degrees); FirebaseVisionImage image = FirebaseVisionImage.fromMediaImage(mediaImage, rotation); // Pass image to an ML Kit Vision API // ... } }
Kotlin
private class YourImageAnalyzer : ImageAnalysis.Analyzer { private fun degreesToFirebaseRotation(degrees: Int): Int = when(degrees) { 0 -> FirebaseVisionImageMetadata.ROTATION_0 90 -> FirebaseVisionImageMetadata.ROTATION_90 180 -> FirebaseVisionImageMetadata.ROTATION_180 270 -> FirebaseVisionImageMetadata.ROTATION_270 else -> throw Exception("Rotation must be 0, 90, 180, or 270.") } override fun analyze(imageProxy: ImageProxy?, degrees: Int) { val mediaImage = imageProxy?.image val imageRotation = degreesToFirebaseRotation(degrees) if (mediaImage != null) { val image = FirebaseVisionImage.fromMediaImage(mediaImage, imageRotation) // Pass image to an ML Kit Vision API // ... } } }
إذا كنت لا تستخدم مكتبة كاميرا تمنحك معلومات عن دوران الصورة، يمكنك احتسابها من خلال دوران الجهاز واتجاه كاميرا الاستشعار في الجهاز:
Java
private static final SparseIntArray ORIENTATIONS = new SparseIntArray(); static { ORIENTATIONS.append(Surface.ROTATION_0, 90); ORIENTATIONS.append(Surface.ROTATION_90, 0); ORIENTATIONS.append(Surface.ROTATION_180, 270); ORIENTATIONS.append(Surface.ROTATION_270, 180); } /** * 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, Context context) 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); // On most devices, the sensor orientation is 90 degrees, but for some // devices it is 270 degrees. For devices with a sensor orientation of // 270, rotate the image an additional 180 ((270 + 270) % 360) degrees. CameraManager cameraManager = (CameraManager) context.getSystemService(CAMERA_SERVICE); int sensorOrientation = cameraManager .getCameraCharacteristics(cameraId) .get(CameraCharacteristics.SENSOR_ORIENTATION); rotationCompensation = (rotationCompensation + sensorOrientation + 270) % 360; // Return the corresponding FirebaseVisionImageMetadata rotation value. int result; switch (rotationCompensation) { case 0: result = FirebaseVisionImageMetadata.ROTATION_0; break; case 90: result = FirebaseVisionImageMetadata.ROTATION_90; break; case 180: result = FirebaseVisionImageMetadata.ROTATION_180; break; case 270: result = FirebaseVisionImageMetadata.ROTATION_270; break; default: result = FirebaseVisionImageMetadata.ROTATION_0; Log.e(TAG, "Bad rotation value: " + rotationCompensation); } return result; }
Kotlin
private val ORIENTATIONS = SparseIntArray() init { ORIENTATIONS.append(Surface.ROTATION_0, 90) ORIENTATIONS.append(Surface.ROTATION_90, 0) ORIENTATIONS.append(Surface.ROTATION_180, 270) ORIENTATIONS.append(Surface.ROTATION_270, 180) } /** * 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, context: Context): 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) // On most devices, the sensor orientation is 90 degrees, but for some // devices it is 270 degrees. For devices with a sensor orientation of // 270, rotate the image an additional 180 ((270 + 270) % 360) degrees. val cameraManager = context.getSystemService(CAMERA_SERVICE) as CameraManager val sensorOrientation = cameraManager .getCameraCharacteristics(cameraId) .get(CameraCharacteristics.SENSOR_ORIENTATION)!! rotationCompensation = (rotationCompensation + sensorOrientation + 270) % 360 // Return the corresponding FirebaseVisionImageMetadata rotation value. val result: Int when (rotationCompensation) { 0 -> result = FirebaseVisionImageMetadata.ROTATION_0 90 -> result = FirebaseVisionImageMetadata.ROTATION_90 180 -> result = FirebaseVisionImageMetadata.ROTATION_180 270 -> result = FirebaseVisionImageMetadata.ROTATION_270 else -> { result = FirebaseVisionImageMetadata.ROTATION_0 Log.e(TAG, "Bad rotation value: $rotationCompensation") } } return result }
بعد ذلك، مرِّر العنصر
media.Image
وقيمة الدوران إلىFirebaseVisionImage.fromMediaImage()
:Java
FirebaseVisionImage image = FirebaseVisionImage.fromMediaImage(mediaImage, rotation);
Kotlin
val image = FirebaseVisionImage.fromMediaImage(mediaImage, rotation)
- لإنشاء عنصر
FirebaseVisionImage
من معرّف موارد منتظم لملف، عليك تمرير سياق التطبيق ومعرّف الموارد المنتظم للملف إلىFirebaseVisionImage.fromFilePath()
. يكون ذلك مفيدًا عند استخدام نيةACTION_GET_CONTENT
لطلب تحديد صورة من تطبيق معرض الصور.Java
FirebaseVisionImage image; try { image = FirebaseVisionImage.fromFilePath(context, uri); } catch (IOException e) { e.printStackTrace(); }
Kotlin
val image: FirebaseVisionImage try { image = FirebaseVisionImage.fromFilePath(context, uri) } catch (e: IOException) { e.printStackTrace() }
- لإنشاء عنصر
FirebaseVisionImage
منByteBuffer
أو صفيف بايت، يجب أولاً احتساب ملفه الشخصي للدوران كما هو موضّح أعلاه لإدخالmedia.Image
.بعد ذلك، أنشئ عنصرًا من النوع
FirebaseVisionImageMetadata
يحتوي على ارتفاع الصورة وعرضها وتنسيق ترميز اللون وتدويرها:Java
FirebaseVisionImageMetadata metadata = new FirebaseVisionImageMetadata.Builder() .setWidth(480) // 480x360 is typically sufficient for .setHeight(360) // image recognition .setFormat(FirebaseVisionImageMetadata.IMAGE_FORMAT_NV21) .setRotation(rotation) .build();
Kotlin
val metadata = FirebaseVisionImageMetadata.Builder() .setWidth(480) // 480x360 is typically sufficient for .setHeight(360) // image recognition .setFormat(FirebaseVisionImageMetadata.IMAGE_FORMAT_NV21) .setRotation(rotation) .build()
استخدِم المخزن المؤقت أو الصفيف وعنصر البيانات الوصفية لإنشاء عنصر
FirebaseVisionImage
:Java
FirebaseVisionImage image = FirebaseVisionImage.fromByteBuffer(buffer, metadata); // Or: FirebaseVisionImage image = FirebaseVisionImage.fromByteArray(byteArray, metadata);
Kotlin
val image = FirebaseVisionImage.fromByteBuffer(buffer, metadata) // Or: val image = FirebaseVisionImage.fromByteArray(byteArray, metadata)
- لإنشاء عنصر
FirebaseVisionImage
من عنصرBitmap
:Java
FirebaseVisionImage image = FirebaseVisionImage.fromBitmap(bitmap);
Kotlin
val image = FirebaseVisionImage.fromBitmap(bitmap)
Bitmap
منتصبة، بدون الحاجة إلى إجراء أيّ دوران إضافي.
2- ضبط أداة تصنيف الصور وتشغيلها
لتصنيف الأجسام في صورة، مرِّر عنصرFirebaseVisionImage
إلى processImage
في FirebaseVisionImageLabeler
.
أولاً، احصل على مثيل من
FirebaseVisionImageLabeler
.إذا أردت استخدام أداة وضع تصنيفات للصور على الجهاز:
Java
FirebaseVisionImageLabeler labeler = FirebaseVision.getInstance() .getOnDeviceImageLabeler(); // Or, to set the minimum confidence required: // FirebaseVisionOnDeviceImageLabelerOptions options = // new FirebaseVisionOnDeviceImageLabelerOptions.Builder() // .setConfidenceThreshold(0.7f) // .build(); // FirebaseVisionImageLabeler labeler = FirebaseVision.getInstance() // .getOnDeviceImageLabeler(options);
Kotlin
val labeler = FirebaseVision.getInstance().getOnDeviceImageLabeler() // Or, to set the minimum confidence required: // val options = FirebaseVisionOnDeviceImageLabelerOptions.Builder() // .setConfidenceThreshold(0.7f) // .build() // val labeler = FirebaseVision.getInstance().getOnDeviceImageLabeler(options)
إذا كنت تريد استخدام أداة وضع العلامات على الصور في السحابة الإلكترونية:
Java
FirebaseVisionImageLabeler labeler = FirebaseVision.getInstance() .getCloudImageLabeler(); // Or, to set the minimum confidence required: // FirebaseVisionCloudImageLabelerOptions options = // new FirebaseVisionCloudImageLabelerOptions.Builder() // .setConfidenceThreshold(0.7f) // .build(); // FirebaseVisionImageLabeler labeler = FirebaseVision.getInstance() // .getCloudImageLabeler(options);
Kotlin
val labeler = FirebaseVision.getInstance().getCloudImageLabeler() // Or, to set the minimum confidence required: // val options = FirebaseVisionCloudImageLabelerOptions.Builder() // .setConfidenceThreshold(0.7f) // .build() // val labeler = FirebaseVision.getInstance().getCloudImageLabeler(options)
بعد ذلك، نقْل الصورة إلى طريقة
processImage()
:Java
labeler.processImage(image) .addOnSuccessListener(new OnSuccessListener<List<FirebaseVisionImageLabel>>() { @Override public void onSuccess(List<FirebaseVisionImageLabel> labels) { // Task completed successfully // ... } }) .addOnFailureListener(new OnFailureListener() { @Override public void onFailure(@NonNull Exception e) { // Task failed with an exception // ... } });
Kotlin
labeler.processImage(image) .addOnSuccessListener { labels -> // Task completed successfully // ... } .addOnFailureListener { e -> // Task failed with an exception // ... }
3- الحصول على معلومات عن الأجسام المصنَّفة
في حال نجاح عملية تصنيف الصور، سيتم تمرير قائمة بعناصرFirebaseVisionImageLabel
إلى معالج
النجاح. يمثّل كل عنصر FirebaseVisionImageLabel
شيئًا
تم تصنيفه في الصورة. لكل تصنيف، يمكنك الحصول على وصف
نص التصنيف، ومعرّف عنصر "الشبكة المعرفية"
(إذا كان متاحًا)، ودرجة الثقة في المطابقة. على سبيل المثال:
Java
for (FirebaseVisionImageLabel label: labels) {
String text = label.getText();
String entityId = label.getEntityId();
float confidence = label.getConfidence();
}
Kotlin
for (label in labels) {
val text = label.text
val entityId = label.entityId
val confidence = label.confidence
}
نصائح لتحسين الأداء في الوقت الفعلي
إذا كنت تريد تصنيف الصور في تطبيق يعمل في الوقت الفعلي، اتّبِع هذه الإرشادات لتحقيق أفضل معدّلات عرض اللقطات:
- الحد من عدد طلبات تصنيف الصور إذا أصبح إطار فيديو جديد متاحًا أثناء تشغيل أداة وضع التصنيفات على الصور، يمكنك إسقاط الإطار.
- إذا كنت تستخدِم نتيجة أداة وضع التصنيفات على الصور لوضع الرسومات على صورة الإدخال، يمكنك أولاً الحصول على النتيجة من ML Kit، ثم عرض الصورة ووضع الرسومات عليها في خطوة واحدة. وبذلك، يتم عرض المحتوى على سطح العرض مرّة واحدة فقط لكل إطار إدخال.
-
إذا كنت تستخدم واجهة برمجة التطبيقات Camera2 API، يمكنك التقاط الصور بتنسيق
ImageFormat.YUV_420_888
.إذا كنت تستخدم الإصدار القديم من Camera API، يمكنك التقاط الصور بتنسيق
ImageFormat.NV21
.
الخطوات التالية
- قبل نشر تطبيق يستخدم واجهة برمجة تطبيقات Cloud في قناة الإصدار العلني، عليك اتّخاذ بعض الخطوات الإضافية لمنع أثر الوصول غير المصرّح به إلى واجهة برمجة التطبيقات والحدّ منه.