رصد العناصر وتتبُّعها باستخدام ML Kit على Android

تنظيم صفحاتك في مجموعات يمكنك حفظ المحتوى وتصنيفه حسب إعداداتك المفضّلة.

يمكنك استخدام مجموعة أدوات تعلّم الآلة لرصد الكائنات وتتبُّعها في إطارات الفيديو المتتالية.

عند تمرير صورة إلى مجموعة أدوات تعلّم الآلة، يتم رصد ما يصل إلى خمسة كائنات في الصورة بالإضافة إلى موضع كل عنصر في الصورة. عند اكتشاف عناصر في مجموعات بث الفيديو، يكون لكل كائن معرّف فريد يمكنك استخدامه لتتبع الكائن من إطار إلى آخر. ويمكنك اختياريًا تفعيل تصنيف الكائنات بشكل خشن، والذي يصنِّف الكائنات باستخدام أوصاف فئات واسعة.

تجربة السمات والبيانات

قبل البدء

  1. في ملف build.gradle على مستوى المشروع، تأكَّد من تضمين مستودع Google Maven في كلٍّ من القسمين buildscript و allprojects.
  2. أضِف التبعيات لمكتبات ML Kit في Android إلى ملف Gradle على مستوى التطبيق في وحدتك، والذي عادة ما يكون app/build.gradle:
    dependencies {
      // ...
    
      implementation 'com.google.mlkit:object-detection:17.0.0'
    
    }
    

1- ضبط أداة رصد الكائنات

لاكتشاف الكائنات وتتبعها، أنشئ أولاً مثيلاً لـ ObjectDetector وحدد اختياريًا أي إعدادات لأداة الكشف تريد تغييرها من الإعداد التلقائي.

  1. اضبط أداة رصد الكائنات في حالة الاستخدام باستخدام كائن ObjectDetectorOptions. يمكنك تغيير الإعدادات التالية:

    إعدادات أداة كشف الكائنات
    وضع الاكتشاف STREAM_MODE (تلقائي) | SINGLE_IMAGE_MODE

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

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

    اكتشاف كائنات متعددة وتتبعها false (تلقائي) | true

    يمكنك رصد ما يصل إلى خمسة كائنات أو تتبّعها أو رصد الكائن الأبرز فقط (الإعداد التلقائي).

    تصنيف العناصر false (تلقائي) | true

    لتحديد ما إذا كان سيتم تصنيف الكائنات المكتشفة ضمن فئات تقريبية. عند تفعيل أداة رصد الكائنات، تصنّف العناصر ضمن الفئات التالية: سلع الأزياء والطعام والسلع المنزلية والنباتات والنباتات.

    تم تحسين واجهة برمجة التطبيقات لاكتشاف العناصر وتتبع واجهة برمجة التطبيقات لهاتين حالتي الاستخدام الأساسية:

    • الاكتشاف المباشر وتتبع أكثر الكائنات البارزة في عدسة الكاميرا.
    • اكتشاف كائنات متعددة من صورة ثابتة.

    لتهيئة واجهة برمجة التطبيقات لحالات الاستخدام هذه:

    Kotlin

    // Live detection and tracking
    val options = ObjectDetectorOptions.Builder()
            .setDetectorMode(ObjectDetectorOptions.STREAM_MODE)
            .enableClassification()  // Optional
            .build()
    
    // Multiple object detection in static images
    val options = ObjectDetectorOptions.Builder()
            .setDetectorMode(ObjectDetectorOptions.SINGLE_IMAGE_MODE)
            .enableMultipleObjects()
            .enableClassification()  // Optional
            .build()

    لغة Java

    // Live detection and tracking
    ObjectDetectorOptions options =
            new ObjectDetectorOptions.Builder()
                    .setDetectorMode(ObjectDetectorOptions.STREAM_MODE)
                    .enableClassification()  // Optional
                    .build();
    
    // Multiple object detection in static images
    ObjectDetectorOptions options =
            new ObjectDetectorOptions.Builder()
                    .setDetectorMode(ObjectDetectorOptions.SINGLE_IMAGE_MODE)
                    .enableMultipleObjects()
                    .enableClassification()  // Optional
                    .build();
  2. الحصول على مثال ObjectDetector:

    Kotlin

    val objectDetector = ObjectDetection.getClient(options)

    لغة Java

    ObjectDetector objectDetector = ObjectDetection.getClient(options);

2. إعداد صورة الإدخال

لرصد الكائنات وتتبّعها، مرِّر الصور إلى طريقة المثيل ObjectDetector process().

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

لكل إطار فيديو أو صورة في تسلسل، نفِّذ ما يلي:

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

تمرير الصورة إلى الطريقة process():

Kotlin

objectDetector.process(image)
    .addOnSuccessListener { detectedObjects ->
        // Task completed successfully
        // ...
    }
    .addOnFailureListener { e ->
        // Task failed with an exception
        // ...
    }

لغة Java

objectDetector.process(image)
    .addOnSuccessListener(
        new OnSuccessListener<List<DetectedObject>>() {
            @Override
            public void onSuccess(List<DetectedObject> detectedObjects) {
                // Task completed successfully
                // ...
            }
        })
    .addOnFailureListener(
        new OnFailureListener() {
            @Override
            public void onFailure(@NonNull Exception e) {
                // Task failed with an exception
                // ...
            }
        });

4. الحصول على معلومات حول الكائنات التي تم رصدها

إذا تم استدعاء process() بنجاح، يتم إرسال قائمة DetectedObject إلى المستمع الناجح.

يحتوي كل DetectedObject على الخصائص التالية:

مربّع ربط تمثّل هذه الخاصية Rect الذي يشير إلى موضع العنصر في الصورة.
الرقم التعريفي للتتبع عدد صحيح يحدد الكائن عبر الصور. قيمة فارغة في SINGLE_IMAGE_MODE.
التصنيفات
وصف التصنيف تمثل هذه الخاصية الوصف النصي للتصنيف. وسيكون هذا العنصر واحدًا من ثوابت "السلسلة" المحدّدة في PredefinedCategory.
فهرس التصنيفات فهرس التصنيفات ضمن جميع التصنيفات المتوافقة مع المصنِّف. وسيكون هذا العنصر واحدًا من ثوابت الأعداد الصحيحة المحدّدة في PredefinedCategory.
تصنيف الثقة قيمة الثقة لتصنيف الكائن.

Kotlin

for (detectedObject in detectedObjects) {
    val boundingBox = detectedObject.boundingBox
    val trackingId = detectedObject.trackingId
    for (label in detectedObject.labels) {
        val text = label.text
        if (PredefinedCategory.FOOD == text) {
            ...
        }
        val index = label.index
        if (PredefinedCategory.FOOD_INDEX == index) {
            ...
        }
        val confidence = label.confidence
    }
}

لغة Java

// The list of detected objects contains one item if multiple
// object detection wasn't enabled.
for (DetectedObject detectedObject : detectedObjects) {
    Rect boundingBox = detectedObject.getBoundingBox();
    Integer trackingId = detectedObject.getTrackingId();
    for (Label label : detectedObject.getLabels()) {
        String text = label.getText();
        if (PredefinedCategory.FOOD.equals(text)) {
            ...
        }
        int index = label.getIndex();
        if (PredefinedCategory.FOOD_INDEX == index) {
            ...
        }
        float confidence = label.getConfidence();
    }
}

ضمان تقديم تجربة رائعة للمستخدم

للحصول على أفضل تجربة للمستخدم، اتبع الإرشادات التالية في تطبيقك:

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

يمكنك أيضًا الاطّلاع على مجموعة تطبيق عرض التصميم المتعدد الأبعاد ML Kit ومجموعتي تصميمات الميزات التي تستند إلى تعلُّم الآلة.

Improving performance

إذا كنت تريد استخدام اكتشاف الكائنات في تطبيق في الوقت الفعلي، فاتبع هذه الإرشادات لتحقيق أفضل معدلات عرض إطارات:

  • عند استخدام وضع البث في تطبيق في الوقت الفعلي، لا تستخدم ميزة رصد كائنات متعددة لأن معظم الأجهزة لن تتمكّن من إنشاء معدلات عرض إطارات مناسبة.

  • ويمكنك إيقاف التصنيف إذا لم تكن بحاجة إليه.

  • في حال استخدام واجهة برمجة التطبيقات Camera أو camera2، عليك التحكُّم في عمليات الرصد. في حال توفّر إطار فيديو جديد أثناء تشغيل أداة الرصد، أفلِت الإطار. للاطّلاع على مثال، يمكنك مراجعة الصف VisionProcessorBase في نموذج التطبيق للبدء السريع.
  • إذا كنت تستخدم واجهة برمجة تطبيقات CameraX، تأكّد من ضبط استراتيجية الضغط العكسي على قيمتها التلقائية ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST. ويضمن ذلك تسليم صورة واحدة فقط للتحليل في كل مرة. وإذا تم إنشاء المزيد من الصور عندما تكون أداة التحليل مشغولة، سيتم إسقاطها تلقائيًا ولن يتم وضعها في قائمة الانتظار للتسليم. بعد إغلاق الصورة التي يتم تحليلها عن طريق طلب ImageProxy.close()، سيتم تسليم الصورة التالية التالية.
  • إذا كنت تستخدم إخراج أداة الرصد لتركيب الرسومات على الصورة المُدخلة، عليك الحصول على النتيجة من ML Kit، ثم عرض الصورة والتراكب في خطوة واحدة. يتم عرض هذا المحتوى على سطح العرض مرة واحدة فقط لكل إطار إدخال. للاطّلاع على مثال، يمكنك الاطّلاع على الصفَّين CameraSourcePreview و GraphicOverlay في نموذج التطبيق السريع للبدء.
  • إذا كنت تستخدم Camera2 API، يمكنك التقاط الصور بتنسيق ImageFormat.YUV_420_888. إذا كنت تستخدم إصدارًا قديمًا من واجهة برمجة تطبيقات الكاميرا، التقِط الصور بتنسيق ImageFormat.NV21.