تصنيف الصور باستخدام مجموعة أدوات تعلُّم الآلة على Android

يمكنك استخدام حزمة تعلّم الآلة لتصنيف العناصر التي تم التعرّف عليها في صورة، إما باستخدام نموذج على الجهاز فقط أو نموذج سحابة. يمكنك الاطّلاع على النظرة العامة للتعرّف على فوائد كل نهج.

قبل البدء

  1. أضِف Firebase إلى مشروع Android إذا لم يسبق لك إجراء ذلك.
  2. أضِف العناصر الاعتمادية لمكتبات 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'
    }
    
  3. إجراء اختياري ولكنّنا ننصح به: إذا كنت تستخدم واجهة برمجة التطبيقات على الجهاز، عليك ضبط تطبيقك لتنزيل نموذج تعلُّم الآلة تلقائيًا على الجهاز بعد تثبيت التطبيق من "متجر 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>
    
    في حال عدم تفعيل عمليات تنزيل نموذج وقت التثبيت، سيتم تنزيل النموذج في المرة الأولى التي تُشغِّل فيها أداة الرصد على الجهاز. ولن تظهر أي نتائج في الطلبات التي ترسلها قبل اكتمال عملية التنزيل.
  4. إذا أردت استخدام النموذج المستند إلى السحابة الإلكترونية ولم يسبق لك تفعيل واجهات برمجة التطبيقات المستنِدة إلى السحابة الإلكترونية لمشروعك، يُرجى إجراء ذلك الآن:

    1. افتح صفحة واجهات برمجة تطبيقات حزمة تعلّم الآلة في وحدة تحكّم Firebase.
    2. إذا لم تكن قد أجريت ترقية لمشروعك إلى خطة أسعار Blaze، انقر على ترقية لإجراء ذلك (لن تتم مطالبتك بالترقية إلا إذا لم يكن مشروعك ضمن خطة Blaze).

      يمكن للمشروعات على مستوى Blaze فقط استخدام واجهات برمجة التطبيقات المستنِدة إلى السحابة الإلكترونية.

    3. في حال لم تكن واجهات برمجة التطبيقات المستنِدة إلى السحابة الإلكترونية مفعَّلة، انقر على تفعيل واجهات برمجة التطبيقات المستنِدة إلى السحابة الإلكترونية.

    إذا كنت تريد استخدام النموذج على الجهاز فقط، يمكنك تخطّي هذه الخطوة.

أصبحت جاهزًا الآن لتصنيف الصور باستخدام نموذج على الجهاز فقط أو نموذج مستند إلى السحابة الإلكترونية.

1- تحضير صورة الإدخال

أنشئ عنصر FirebaseVisionImage من صورتك. يعمل مصنِّف الصور بشكل أسرع عند استخدام Bitmap، أو إذا كنت تستخدم واجهة برمجة التطبيقات camera2 API، media.Image بتنسيق JPEG، ويُنصح باستخدامه متى أمكن.

  • لإنشاء كائن 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+KTX

    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+KTX

    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+KTX

    val image = FirebaseVisionImage.fromMediaImage(mediaImage, rotation)
  • لإنشاء كائن FirebaseVisionImage من معرّف موارد منتظم (URI) لملف، مرِّر سياق التطبيق ومعرّف الموارد المنتظم (URI) للملف إلى FirebaseVisionImage.fromFilePath(). ويكون هذا الإجراء مفيدًا عند استخدام هدف ACTION_GET_CONTENT لتطلب من المستخدم اختيار صورة من تطبيق معرض الصور.

    Java

    FirebaseVisionImage image;
    try {
        image = FirebaseVisionImage.fromFilePath(context, uri);
    } catch (IOException e) {
        e.printStackTrace();
    }

    Kotlin+KTX

    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+KTX

    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+KTX

    val image = FirebaseVisionImage.fromByteBuffer(buffer, metadata)
    // Or: val image = FirebaseVisionImage.fromByteArray(byteArray, metadata)
  • لإنشاء كائن FirebaseVisionImage من كائن Bitmap:

    Java

    FirebaseVisionImage image = FirebaseVisionImage.fromBitmap(bitmap);

    Kotlin+KTX

    val image = FirebaseVisionImage.fromBitmap(bitmap)
    يجب أن تكون الصورة التي يمثّلها الكائن Bitmap في وضع عمودي، بدون الحاجة إلى تدوير إضافي.

2- ضبط أداة تصنيف الصور وتشغيلها

لتصنيف العناصر في صورة، مرِّر كائن FirebaseVisionImage إلى طريقة processImage في FirebaseVisionImageLabeler.

  1. أولاً، احصل على مثيل 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+KTX

    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+KTX

    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)
    

  2. بعد ذلك، اضبط الصورة على طريقة 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+KTX

    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+KTX

for (label in labels) {
  val text = label.text
  val entityId = label.entityId
  val confidence = label.confidence
}

نصائح لتحسين الأداء في الوقت الفعلي

إذا أردت تصنيف الصور في تطبيق في الوقت الفعلي، اتّبِع الإرشادات التالية لتحقيق أفضل معدلات عرض إطارات:

  • تقييد الطلبات إلى مصنِّف الصور إذا أصبح إطار فيديو جديد متاحًا أثناء تشغيل مصنِّف الصور، أفلِت الإطار.
  • إذا كنت تستخدم ناتج مصنِّف الصور لتراكب الرسومات على الصورة التي تم إدخالها، يمكنك أولاً الحصول على النتيجة من ML Kit، ثم عرض الصورة والمحتوى الذي يظهر على سطح الصورة في خطوة واحدة. ومن خلال إجراء ذلك، ستظهر على سطح الشاشة مرة واحدة فقط لكل إطار إدخال.
  • في حال استخدام واجهة برمجة التطبيقات Camera2 API، يمكنك التقاط صور بتنسيق ImageFormat.YUV_420_888.

    في حال استخدام واجهة برمجة التطبيقات للكاميرا القديمة، يمكنك التقاط صور بتنسيق ImageFormat.NV21.

الخطوات اللاحقة