Вы можете использовать ML Kit для обнаружения и отслеживания объектов в последовательных видеокадрах.
Когда вы передаете изображение в ML Kit, он обнаруживает до пяти объектов на изображении вместе с положением каждого объекта на изображении. При обнаружении объектов в видеопотоках каждый объект имеет уникальный идентификатор, который вы можете использовать для отслеживания объекта от кадра к кадру. Вы также можете опционально включить грубую классификацию объектов, которая маркирует объекты с помощью общих описаний категорий.
Попробуйте это
- Поэкспериментируйте с образцом приложения , чтобы увидеть пример использования этого API.
- Полную реализацию этого API можно увидеть в демонстрационном приложении Material Design .
Прежде чем начать
- В файле
build.gradle
на уровне проекта обязательно включите репозиторий Maven от Google в разделыbuildscript
иallprojects
. - Добавьте зависимости для библиотек Android ML Kit в файл 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 для этих вариантов использования:
Котлин
// 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()
Ява
// 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
:Котлин
val objectDetector = ObjectDetection.getClient(options)
Ява
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
вычисляют значение поворота автоматически.
Котлин
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 // ... } } }
Ява
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 // ... } } }
Если вы не используете библиотеку камеры, которая выдает угол поворота изображения, вы можете рассчитать его на основе угла поворота устройства и ориентации датчика камеры на устройстве:
Котлин
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 }
Ява
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()
:
Котлин
val image = InputImage.fromMediaImage(mediaImage, rotation)
Java
InputImage image = InputImage.fromMediaImage(mediaImage, rotation);
Использование URI файла
Чтобы создать объект InputImage
из URI файла, передайте контекст приложения и URI файла в InputImage.fromFilePath()
. Это полезно, когда вы используете намерение ACTION_GET_CONTENT
, чтобы предложить пользователю выбрать изображение из своего приложения галереи.
Котлин
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
с буфером или массивом, вместе с высотой изображения, шириной, форматом кодировки цвета и степенью поворота:
Котлин
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 )
Ява
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
, сделайте следующее объявление:
Котлин
val image = InputImage.fromBitmap(bitmap, 0)
Java
InputImage image = InputImage.fromBitmap(bitmap, rotationDegree);
Изображение представлено объектом Bitmap
вместе с градусами поворота.
3. Обработайте изображение.
Передайте изображение методуprocess()
: Котлин
objectDetector.process(image) .addOnSuccessListener { detectedObjects -> // Task completed successfully // ... } .addOnFailureListener { e -> // Task failed with an exception // ... }
Ява
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. | ||||||
Этикетки |
|
Котлин
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 } }
Ява
// 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
в примере приложения быстрого запуска для примера. - Если вы используете API Camera2, захватывайте изображения в формате
ImageFormat.YUV_420_888
. Если вы используете старый API Camera, захватывайте изображения в форматеImageFormat.NV21
.