Вы можете использовать ML Kit для обнаружения и отслеживания объектов в последовательных кадрах видео.
Когда вы передаете изображение в ML Kit, он обнаруживает на изображении до пяти объектов, а также положение каждого объекта на изображении. При обнаружении объектов в видеопотоках каждому объекту присваивается уникальный идентификатор, который можно использовать для отслеживания объекта от кадра к кадру. Вы также можете включить приблизительную классификацию объектов, которая помечает объекты описаниями широких категорий.
Попробовать
- Попробуйте поработать с примером приложения, чтобы понять, как использовать этот API.
- Пример полной реализации этого API можно посмотреть в приложении Material Design.
Подготовка
- В файле
build.gradleна уровне проекта добавьте репозиторий Maven от Google в разделыbuildscriptиallprojects. - Добавьте зависимости для библиотек ML Kit для Android в файл gradle на уровне приложения вашего модуля, который обычно называется
app/build.gradle:dependencies { // ... implementation 'com.google.mlkit:object-detection:17.0.2' }
1. Настройте детектор объектов
Чтобы обнаруживать и отслеживать объекты, сначала создайте экземпляр ObjectDetector и при необходимости укажите настройки детектора, которые вы хотите изменить по сравнению со значениями по умолчанию.
Настройте детектор объектов для вашего варианта использования с помощью объекта
ObjectDetectorOptions. Вы можете изменить следующие настройки:Настройки детектора объектов Режим обнаружения STREAM_MODE(по умолчанию) |SINGLE_IMAGE_MODEВ режиме
STREAM_MODE(по умолчанию) детектор объектов работает с низкой задержкой, но при первых нескольких вызовах может выдавать неполные результаты (например, не указывать ограничивающие рамки или ярлыки категорий). Кроме того, вSTREAM_MODEдетектор присваивает объектам идентификаторы отслеживания, которые можно использовать для отслеживания объектов в кадрах. Используйте этот режим, если вам нужно отслеживать объекты или если важна низкая задержка, например при обработке видеопотоков в реальном времени.В режиме
SINGLE_IMAGE_MODEдетектор обнаружения объектов возвращает результат после определения граничной рамки объекта. Если вы также включите классификацию, результат будет возвращен после того, как станут доступны и граничная рамка, и ярлык категории. В результате задержка обнаружения может быть выше. Кроме того, вSINGLE_IMAGE_MODEидентификаторы отслеживания не назначаются. Используйте этот режим, если задержка не критична и вы не хотите иметь дело с частичными результатами.Обнаружение и отслеживание нескольких объектов false(по умолчанию) |trueОпределяет, нужно ли обнаруживать и отслеживать до пяти объектов или только самый заметный (по умолчанию).
Классифицировать объекты false(по умолчанию) |trueОпределяет, нужно ли классифицировать обнаруженные объекты по общим категориям. Если обнаружение объектов включено, детектор объектов классифицирует объекты по следующим категориям: модные товары, еда, товары для дома, места и растения.
API обнаружения и отслеживания объектов оптимизирован для этих двух основных вариантов использования:
- Обнаружение и отслеживание в реальном времени наиболее заметного объекта в видоискателе камеры.
- Распознавание нескольких объектов на статичном изображении.
Чтобы настроить API для этих сценариев использования:
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. Подготовьте входное изображение
Чтобы обнаруживать и отслеживать объекты, передайте изображения методуprocess() экземпляра ObjectDetector.
Детектор объектов запускается непосредственно из 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, указывающий положение объекта на изображении. |
||||||
| Идентификатор отслеживания | Целое число, которое идентифицирует объект на изображениях. Null в режиме 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 Material Design и коллекцией шаблонов Material Design для функций на основе машинного обучения.
Повышение эффективности
Если вы хотите использовать обнаружение объектов в приложении, работающем в реальном времени, следуйте этим рекомендациям, чтобы добиться наилучшей частоты кадров:
При использовании режима потоковой передачи в приложении реального времени не используйте обнаружение нескольких объектов, так как большинство устройств не смогут обеспечить адекватную частоту кадров.
Если классификация вам не нужна, отключите ее.
- Если вы используете API
Cameraилиcamera2, ограничьте вызовы детектора. Если во время работы детектора становится доступен новый кадр видео, отбросьте его. Пример можно найти в классеVisionProcessorBaseв примере приложения из краткого руководства. - Если вы используете API
CameraX, убедитесь, что для стратегии противодавления задано значение по умолчаниюImageAnalysis.STRATEGY_KEEP_ONLY_LATEST. Это гарантирует, что для анализа будет отправляться только одно изображение за раз. Если во время работы анализатора будут созданы дополнительные изображения, они будут автоматически удалены и не будут поставлены в очередь на доставку. После того как анализируемое изображение будет закрыто вызовом ImageProxy.close(), будет доставлено следующее последнее изображение. - Если вы используете выходные данные детектора для наложения графики на входное изображение, сначала получите результат от ML Kit, а затем визуализируйте изображение и наложение за один шаг. Это приводит к отрисовке на поверхности экрана только один раз для каждого входного кадра. Пример можно найти в классах
CameraSourcePreviewиGraphicOverlayв примере приложения из краткого руководства. - Если вы используете Camera2 API, делайте снимки в формате
ImageFormat.YUV_420_888. Если вы используете старый Camera API, делайте снимки в форматеImageFormat.NV21.