При передаче изображения в ML Kit программа обнаруживает до пяти объектов на изображении, а также определяет положение каждого объекта. При обнаружении объектов в видеопотоках каждый объект имеет уникальный идентификатор, который можно использовать для отслеживания объекта от кадра к кадру.
Для классификации обнаруженных объектов можно использовать собственную модель классификации изображений. Подробную информацию о требованиях к совместимости моделей, местах поиска предварительно обученных моделей и способах обучения собственных моделей см. в разделе «Создание пользовательских моделей с помощью ML Kit».
Существует два способа интеграции пользовательской модели. Вы можете включить модель в состав приложения, поместив её в папку ресурсов, или динамически загрузить её из облачного хранилища. В следующей таблице сравниваются эти два варианта.
| Комплексная модель | Хостинговая модель |
|---|---|
| Эта модель является частью APK-файла вашего приложения, что увеличивает его размер. | Эта модель не является частью вашего APK-файла. Она размещается путем загрузки в Cloud Storage. Мы рекомендуем использовать Cloud Storage для Firebase . |
| Данная модель доступна сразу же, даже когда устройство Android находится в автономном режиме. | Ваше приложение должно содержать код для загрузки модели по запросу. |
| Нет необходимости в проекте Firebase. | Требуется проект Firebase (если используется Cloud Storage for Firebase). |
| Для обновления модели необходимо повторно опубликовать приложение. | Обновляйте модель приложения, не переиздавая его. |
| Встроенного A/B-тестирования нет. | A/B-тестирование с использованием Firebase Remote Config |
Попробуйте!
- Пример использования встроенной модели можно найти в приложении Vision Quickstart , а пример использования размещенной модели — в приложении AutoML Quickstart .
- Для получения полной реализации этого API ознакомьтесь с демонстрационным приложением Material Design .
Прежде чем начать
1. В файлеbuild.gradle.kts на уровне проекта убедитесь, что репозиторий Maven от Google включен как в раздел buildscript , так и в раздел allprojects .Добавьте зависимости для библиотек ML Kit Android в файл gradle вашего модуля, обычно это
app/build.gradle.kts:Для включения модели в ваше приложение:
dependencies { // ... // Object detection & tracking feature with custom bundled model implementation("com.google.mlkit:object-detection-custom:17.0.2") }Если вы хотите загрузить модель из Cloud Storage for Firebase , убедитесь, что вы добавили Firebase в свой проект Android , если вы еще этого не сделали. Это не требуется при сборке модели.
1. Загрузите модель.
Вы можете загрузить модель из локального источника или из удаленного источника.
Настройте локальный источник модели.
Чтобы включить модель в ваше приложение:
Скопируйте файл модели (обычно с расширением
.tfliteили.lite) в папкуassets/вашего приложения. (Возможно, вам потребуется сначала создать папку, щелкнув правой кнопкой мыши по папкеapp/, а затем выбрав New > Folder > Assets Folder .)Создайте объект
LocalModel, указав путь к файлу модели:Котлин
val localModel = LocalModel.Builder() .setAssetFilePath("model.tflite") // or .setAbsoluteFilePath(absolute path to model file) // or .setUri(URI to model file) .build()
Java
LocalModel localModel = new LocalModel.Builder() .setAssetFilePath("model.tflite") // or .setAbsoluteFilePath(absolute path to model file) // or .setUri(URI to model file) .build();
Настройте удаленно размещенный источник модели.
Для использования модели, размещенной удаленно, необходимо загрузить файл модели в локальное хранилище устройства, используя собственную логику приложения, а затем загрузить его как локальную модель. Мы рекомендуем использовать Cloud Storage for Firebase для размещения модели. Подробности реализации см. в руководстве по миграции Firebase ML в Cloud Storage .
2. Настройте детектор объектов.
После настройки источников модели настройте детектор объектов для вашего варианта использования с помощью объекта CustomObjectDetectorOptions . Вы можете изменить следующие параметры:
| Настройки детектора объектов | |
|---|---|
| Режим обнаружения | STREAM_MODE (по умолчанию) | SINGLE_IMAGE_MODE В В |
| Обнаружение и отслеживание нескольких объектов | false (по умолчанию) | trueВы можете выбрать, обнаруживать и отслеживать до пяти объектов или только самый заметный объект (по умолчанию). |
| Классифицировать объекты | false (по умолчанию) | true Определяет, следует ли классифицировать обнаруженные объекты с использованием предоставленной пользовательской модели классификации. Чтобы использовать свою собственную модель классификации, необходимо установить этот параметр в |
| Порог достоверности классификации | Минимальный показатель достоверности обнаруженных меток. Если не задано, будет использоваться любой пороговый уровень классификатора, указанный в метаданных модели. Если модель не содержит метаданных или метаданные не указывают пороговый уровень классификатора, будет использоваться пороговое значение по умолчанию, равное 0,0. |
| Максимальное количество меток на объект | Максимальное количество меток для каждого объекта, которое вернет детектор. Если не указано, будет использоваться значение по умолчанию — 10. |
API для обнаружения и отслеживания объектов оптимизирован для двух основных сценариев использования:
- Обнаружение и отслеживание в реальном времени наиболее заметного объекта в видоискателе камеры.
- Обнаружение нескольких объектов на статическом изображении.
Для настройки API под эти сценарии использования с помощью локально упакованной модели:
Котлин
// Live detection and tracking val customObjectDetectorOptions = CustomObjectDetectorOptions.Builder(localModel) .setDetectorMode(CustomObjectDetectorOptions.STREAM_MODE) .enableClassification() .setClassificationConfidenceThreshold(0.5f) .setMaxPerObjectLabelCount(3) .build() // Multiple object detection in static images val customObjectDetectorOptions = CustomObjectDetectorOptions.Builder(localModel) .setDetectorMode(CustomObjectDetectorOptions.SINGLE_IMAGE_MODE) .enableMultipleObjects() .enableClassification() .setClassificationConfidenceThreshold(0.5f) .setMaxPerObjectLabelCount(3) .build() val objectDetector = ObjectDetection.getClient(customObjectDetectorOptions)
Java
// Live detection and tracking CustomObjectDetectorOptions customObjectDetectorOptions = new CustomObjectDetectorOptions.Builder(localModel) .setDetectorMode(CustomObjectDetectorOptions.STREAM_MODE) .enableClassification() .setClassificationConfidenceThreshold(0.5f) .setMaxPerObjectLabelCount(3) .build(); // Multiple object detection in static images CustomObjectDetectorOptions customObjectDetectorOptions = new CustomObjectDetectorOptions.Builder(localModel) .setDetectorMode(CustomObjectDetectorOptions.SINGLE_IMAGE_MODE) .enableMultipleObjects() .enableClassification() .setClassificationConfidenceThreshold(0.5f) .setMaxPerObjectLabelCount(3) .build(); ObjectDetector objectDetector = ObjectDetection.getClient(customObjectDetectorOptions);
Если вы используете удаленно размещенную модель, вам необходимо убедиться, что она была загружена, прежде чем запускать ее.
Хотя подтверждение этого требуется только перед запуском детектора, если у вас есть как удаленно размещенная модель, так и локально упакованная модель, имеет смысл выполнить эту проверку при создании экземпляра детектора изображений: создать детектор из удаленной модели, если она была загружена, и из локальной модели в противном случае.
Котлин
val modelFile = File(context.cacheDir, "my_remote_model.tflite") val model = if (modelFile.exists()) { // Use the downloaded model if available LocalModel.Builder().setAbsoluteFilePath(modelFile.absolutePath).build() } else { // Fall back to the bundled model LocalModel.Builder().setAssetFilePath("model.tflite").build() } val customObjectDetectorOptions = CustomObjectDetectorOptions.Builder(model) .setDetectorMode(CustomObjectDetectorOptions.SINGLE_IMAGE_MODE) .enableClassification() .setClassificationConfidenceThreshold(0.5f) .setMaxPerObjectLabelCount(3) .build() val objectDetector = ObjectDetection.getClient(customObjectDetectorOptions)
Java
File modelFile = new File(context.getCacheDir(), "my_remote_model.tflite"); LocalModel model; if (modelFile.exists()) { // Use the downloaded model if available model = new LocalModel.Builder().setAbsoluteFilePath(modelFile.getAbsolutePath()).build(); } else { // Fall back to the bundled model model = new LocalModel.Builder().setAssetFilePath("model.tflite").build(); } CustomObjectDetectorOptions customObjectDetectorOptions = new CustomObjectDetectorOptions.Builder(model) .setDetectorMode(CustomObjectDetectorOptions.SINGLE_IMAGE_MODE) .enableClassification() .setClassificationConfidenceThreshold(0.5f) .setMaxPerObjectLabelCount(3) .build(); ObjectDetector objectDetector = ObjectDetection.getClient(customObjectDetectorOptions);
Если у вас есть только удаленно размещенная модель, следует отключить связанные с ней функции — например, сделать часть пользовательского интерфейса неактивной или скрытой — до тех пор, пока вы не убедитесь, что модель загружена.
Котлин
val localFile = File(context.cacheDir, "my_remote_model.tflite") if (localFile.exists()) { // Model is already cached, initialize immediately initializeDetector(localFile) } else { // Model is not yet available, show loading UI and start download showLoadingUI() val storage = Firebase.storage val modelRef = storage.getReferenceFromUrl("gs://YOUR_BUCKET/path/to/model.tflite") modelRef.getFile(localFile) .addOnSuccessListener { // Download complete, initialize the detector hideLoadingUI() initializeDetector(localFile) } .addOnFailureListener { // Handle download error showErrorUI() } } private fun initializeDetector(modelFile: File) { val localModel = LocalModel.Builder().setAbsoluteFilePath(modelFile.absolutePath).build() val customObjectDetectorOptions = CustomObjectDetectorOptions.Builder(localModel) .setDetectorMode(CustomObjectDetectorOptions.SINGLE_IMAGE_MODE) .enableClassification() .build() val objectDetector = ObjectDetection.getClient(customObjectDetectorOptions) // Enable ML-related UI features here enableMLFeatures(objectDetector) }
Java
File localFile = new File(context.getCacheDir(), "my_remote_model.tflite"); if (localFile.exists()) { // Model is already cached, initialize immediately initializeDetector(localFile); } else { // Model is not yet available, show loading UI and start download showLoadingUI(); FirebaseStorage storage = FirebaseStorage.getInstance(); StorageReference modelRef = storage.getReferenceFromUrl("gs://YOUR_BUCKET/path/to/model.tflite"); modelRef.getFile(localFile) .addOnSuccessListener(new OnSuccessListener<FileDownloadTask.TaskSnapshot>() { @Override public void onSuccess(FileDownloadTask.TaskSnapshot taskSnapshot) { // Download complete, initialize the detector hideLoadingUI(); initializeDetector(localFile); } }) .addOnFailureListener(new OnFailureListener() { @Override public void onFailure(@NonNull Exception exception) { // Handle download error showErrorUI(); } }); } private void initializeDetector(File modelFile) { LocalModel localModel = new LocalModel.Builder().setAbsoluteFilePath(modelFile.getAbsolutePath()).build(); CustomObjectDetectorOptions customObjectDetectorOptions = new CustomObjectDetectorOptions.Builder(localModel) .setDetectorMode(CustomObjectDetectorOptions.SINGLE_IMAGE_MODE) .enableClassification() .build(); ObjectDetector objectDetector = ObjectDetection.getClient(customObjectDetectorOptions); // Enable ML-related UI features here enableMLFeatures(objectDetector); }
3. Подготовьте входное изображение.
Создайте объектInputImage из вашего изображения. Детектор объектов работает непосредственно с объектами 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 // ... } } }
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 // ... } } }
Если вы не используете библиотеку для работы с камерой, которая предоставляет угол поворота изображения, вы можете рассчитать его, исходя из угла поворота устройства и ориентации датчика камеры в устройстве:
Котлин
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() :
Котлин
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 )
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 необходимо сделать следующее объявление:
Котлин
val image = InputImage.fromBitmap(bitmap, 0)
Java
InputImage image = InputImage.fromBitmap(bitmap, rotationDegree);
Изображение представлено объектом Bitmap вместе с градусами поворота.
4. Запустите детектор объектов.
Котлин
objectDetector .process(image) .addOnFailureListener(e -> {...}) .addOnSuccessListener(results -> { for (detectedObject in results) { // ... } });
Java
objectDetector .process(image) .addOnFailureListener(e -> {...}) .addOnSuccessListener(results -> { for (DetectedObject detectedObject : results) { // ... } });
5. Получите информацию об объектах с соответствующими обозначениями.
Если вызов функции process() проходит успешно, список объектов DetectedObject передается обработчику успешного выполнения.
Каждый DetectedObject содержит следующие свойства:
| Ограничивающая рамка | Rect , указывающий положение объекта на изображении. | ||||||
| Идентификатор отслеживания | Целочисленное значение, идентифицирующее объект на разных изображениях. В режиме SINGLE_IMAGE_MODE — значение равно null. | ||||||
| Метки |
|
Котлин
// The list of detected objects contains one item if multiple // object detection wasn't enabled. for (detectedObject in results) { val boundingBox = detectedObject.boundingBox val trackingId = detectedObject.trackingId for (label in detectedObject.labels) { val text = label.text val index = label.index val confidence = label.confidence } }
Java
// The list of detected objects contains one item if multiple // object detection wasn't enabled. for (DetectedObject detectedObject : results) { Rect boundingBox = detectedObject.getBoundingBox(); Integer trackingId = detectedObject.getTrackingId(); for (Label label : detectedObject.getLabels()) { String text = label.getText(); int index = label.getIndex(); 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.