يمكنك استخدام مجموعة أدوات تعلّم الآلة لرصد الكائنات وتتبُّعها في إطارات الفيديو المتتالية.
عند تمرير صورة إلى مجموعة أدوات تعلّم الآلة، يتم رصد ما يصل إلى خمسة كائنات في الصورة بالإضافة إلى موضع كل عنصر في الصورة. عند اكتشاف عناصر في مجموعات بث الفيديو، يكون لكل كائن معرّف فريد يمكنك استخدامه لتتبع الكائن من إطار إلى آخر. ويمكنك اختياريًا تفعيل تصنيف الكائنات بشكل خشن، والذي يصنِّف الكائنات باستخدام أوصاف فئات واسعة.
تجربة السمات والبيانات
- يمكنك تجربة نموذج التطبيق للاطّلاع على مثال لاستخدام واجهة برمجة التطبيقات هذه.
- راجع تطبيق عرض التصميم متعدد الأبعاد للتعرّف على طريقة تنفيذ واجهة برمجة التطبيقات هذه بشكل شامل.
قبل البدء
- في ملف
build.gradle
على مستوى المشروع، تأكَّد من تضمين مستودع Google Maven في كلٍّ من القسمينbuildscript
وallprojects
. - أضِف التبعيات لمكتبات ML Kit في Android إلى ملف Gradle على مستوى التطبيق في وحدتك، والذي عادة ما يكون
app/build.gradle
:dependencies { // ... implementation 'com.google.mlkit:object-detection:17.0.0' }
1- ضبط أداة رصد الكائنات
لاكتشاف الكائنات وتتبعها، أنشئ أولاً مثيلاً لـ ObjectDetector
وحدد اختياريًا أي إعدادات لأداة الكشف تريد تغييرها من الإعداد التلقائي.
اضبط أداة رصد الكائنات في حالة الاستخدام باستخدام كائن
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();
الحصول على مثال
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. | ||||||
التصنيفات |
|
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
.