تصنيف الصور باستخدام حزمة تعلّم الآلة على نظام التشغيل Android

يمكنك استخدام حزمة تعلّم الآلة لتصنيف العناصر التي يتم التعرّف عليها في صورة. يتوافق النموذج التلقائي المُقدَّم مع حزمة ML Kit مع أكثر من 400 تصنيف مختلف.

الميزةغير مجمعةمُجمَّعة
التنفيذيتم تنزيل النموذج ديناميكيًا من خلال "خدمات Google Play".يكون النموذج مرتبطًا بشكلٍ ثابت بوقت الإصدار.
حجم التطبيقزيادة في الحجم بمقدار 200 كيلوبايت تقريبًا.زيادة في الحجم بمقدار 5.7 ميغابايت تقريبًا
وقت الإعدادقد تضطر إلى الانتظار حتى يتم تنزيل النموذج قبل الاستخدام لأول مرة.الطراز متاح على الفور.

التجربة الآن

  • يمكنك تجربة نموذج التطبيق للاطّلاع على مثال على استخدام واجهة برمجة التطبيقات هذه.

قبل البدء

  1. في ملف build.gradle على مستوى المشروع، احرص على تضمين مستودع Maven من Google في القسمَين buildscript وallprojects.

  2. أضِف الاعتماديات الخاصة بمكتبات ML Kit على Android إلى ملف Gradle الخاص بالوحدة، والذي يكون عادةً app/build.gradle. اختر إحدى التبعيات التالية بناءً على احتياجاتك:

    لدمج النموذج مع تطبيقك:

    dependencies {
      // ...
      // Use this dependency to bundle the model with your app
      implementation 'com.google.mlkit:image-labeling:17.0.8'
    }
    

    لاستخدام النموذج في "خدمات Google Play":

    dependencies {
      // ...
      // Use this dependency to use the dynamically downloaded model in Google Play Services
      implementation 'com.google.android.gms:play-services-mlkit-image-labeling:16.0.8'
    }
    
  3. إذا اخترت استخدام النموذج في "خدمات Google Play"، يمكنك ضبط تطبيقك لتنزيل النموذج تلقائيًا على الجهاز بعد تثبيت التطبيق من "متجر Play". لإجراء ذلك، يُرجى إضافة البيان التالي إلى ملف AndroidManifest.xml في تطبيقك:

    <application ...>
          ...
          <meta-data
              android:name="com.google.mlkit.vision.DEPENDENCIES"
              android:value="ica" >
          <!-- To use multiple models: android:value="ica,model2,model3" -->
    </application>
    

    يمكنك أيضًا التحقّق بشكل صريح من مدى توفّر الطراز وطلب تنزيله من خلال ModuleInstallClient API في "خدمات Google Play".

    إذا لم تُمكّن عمليات تنزيل نموذج وقت التثبيت أو تطلب تنزيلاً صريحًا، سيتم تنزيل النموذج في المرة الأولى التي تُشغِّل فيها أداة التصنيف. لا ينتج عن الطلبات التي تقدمها قبل اكتمال التنزيل أي نتائج.

أنت الآن جاهز لتسمية الصور.

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

أنشئ عنصر InputImage من صورتك. تعمل أداة تصنيف الصور بشكل أسرع عند استخدام Bitmap أو YUV_420_888 media.Image إذا كنت تستخدم واجهة برمجة التطبيقات، وYUV_420_888، ويُنصح باستخدامها عند الإمكان.

يمكنك إنشاء كائن 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 مع درجات التدوير.

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

لتصنيف العناصر في صورة، مرِّر كائن InputImage إلى طريقة process في ImageLabeler.

  1. أولاً، احصل على مثيل ImageLabeler.

    إذا أردت استخدام مصنِّف الصور على الجهاز فقط، عليك تقديم البيان التالي:

Kotlin

// To use default options:
val labeler = ImageLabeling.getClient(ImageLabelerOptions.DEFAULT_OPTIONS)

// Or, to set the minimum confidence required:
// val options = ImageLabelerOptions.Builder()
//     .setConfidenceThreshold(0.7f)
//     .build()
// val labeler = ImageLabeling.getClient(options)

Java

// To use default options:
ImageLabeler labeler = ImageLabeling.getClient(ImageLabelerOptions.DEFAULT_OPTIONS);

// Or, to set the minimum confidence required:
// ImageLabelerOptions options =
//     new ImageLabelerOptions.Builder()
//         .setConfidenceThreshold(0.7f)
//         .build();
// ImageLabeler labeler = ImageLabeling.getClient(options);
  1. بعد ذلك، اضبط الصورة على طريقة process():

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
                // ...
            }
        });

3- الحصول على معلومات عن العناصر المصنّفة

إذا نجحت عملية تصنيف الصور، يتم تمرير قائمة بكائنات ImageLabel إلى مستمع الناجح. ويمثل كل كائن ImageLabel عنصرًا تم تصنيفه في الصورة. يتيح النموذج الأساسي استخدام أكثر من 400 تصنيف مختلف. يمكنك الحصول على وصف نصي لكل تصنيف، والفهرسة بين جميع التصنيفات التي يدعمها النموذج، ونتيجة الثقة للمطابقة. مثلاً:

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 في نموذج تطبيق Quickstart للحصول على مثال.
  • إذا كنت تستخدم واجهة برمجة تطبيقات CameraX، تأكَّد من ضبط استراتيجية الضغط العكسي على قيمتها التلقائية ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST. وهذا يضمن تسليم صورة واحدة فقط للتحليل في كل مرة. إذا تم إنتاج المزيد من الصور عندما تكون أداة التحليل مشغولة، سيتم تجاهلها تلقائيًا ولن يتم وضعها في قائمة الانتظار للتسليم. بعد إغلاق الصورة التي يتم تحليلها من خلال استدعاء ImageProxy.Close() ، سيتم تسليم آخر صورة تالية.
  • في حال استخدام مُخرجات مصنِّف الصور لتركيب رسومات على الصورة التي تم إدخالها، يمكنك أولاً الحصول على النتيجة من ML Kit، ثم عرض الصورة والمحتوى الذي يظهر على سطح الصورة في خطوة واحدة. ويظهر هذا الإجراء على سطح العرض مرة واحدة فقط لكل إطار إدخال. اطّلِع على صفّتَي CameraSourcePreview و GraphicOverlay في نموذج تطبيق Quickstart للحصول على مثال.
  • في حال استخدام واجهة برمجة التطبيقات Camera2 API، يمكنك التقاط صور بتنسيق ImageFormat.YUV_420_888. في حال استخدام واجهة برمجة التطبيقات للكاميرا القديمة، يمكنك التقاط صور بتنسيق ImageFormat.NV21.