С помощью 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, указывающий положение объекта на изображении. |
||||||
| Идентификатор отслеживания | Целое число, которое идентифицирует объект на разных изображениях. Нулевое значение в режиме 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 и коллекцией шаблонов для функций на основе машинного обучения.
Повышение эффективности
Если вы хотите использовать обнаружение объектов в приложении, работающем в реальном времени, следуйте приведенным ниже рекомендациям, чтобы добиться оптимальной частоты кадров.
При использовании потокового режима в приложении реального времени не применяйте обнаружение нескольких объектов, поскольку большинство устройств не смогут обеспечить достаточную частоту кадров.
Отключите классификацию, если она вам не нужна.
- Если вы используете 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.