Когда вы передаете изображение в ML Kit, он обнаруживает до пяти объектов на изображении, а также положение каждого объекта на изображении. При обнаружении объектов в видеопотоках каждый объект имеет уникальный идентификатор, по которому можно отслеживать объект от кадра к кадру.
Вы можете использовать пользовательскую модель классификации изображений для классификации обнаруженных объектов. Обратитесь к разделу Пользовательские модели с комплектом ML для получения инструкций по требованиям совместимости моделей, где найти предварительно обученные модели и как обучать собственные модели.
Существует два способа интеграции пользовательской модели. Вы можете связать модель, поместив ее в папку ресурсов вашего приложения, или динамически загрузить ее из Firebase. В следующей таблице сравниваются два варианта.
Модель в комплекте | Размещенная модель |
---|---|
Модель является частью APK-файла вашего приложения, что увеличивает его размер. | Модель не является частью вашего APK. Он размещается путем загрузки в Firebase Machine Learning . |
Модель доступна сразу, даже когда Android-устройство находится в автономном режиме. | Модель скачивается по запросу. |
Нет необходимости в проекте Firebase | Требуется проект Firebase |
Вам необходимо повторно опубликовать свое приложение, чтобы обновить модель. | Отправляйте обновления модели без повторной публикации приложения. |
Нет встроенного A/B-тестирования. | Простое A/B-тестирование с помощью Firebase Remote Config |
Попробуйте это
- См. приложение быстрого запуска Vision для примера использования связанной модели и приложение быстрого запуска automl для примера использования размещенной модели.
- См. демонстрационное приложение Material Design, где представлена комплексная реализация этого API.
Прежде чем начать
В файле
build.gradle
на уровне проекта обязательно включите репозиторий Google Maven как в разделыbuildscript
, так и в разделыallprojects
.Добавьте зависимости для библиотек Android ML Kit в файл градиента уровня приложения вашего модуля, который обычно имеет вид
app/build.gradle
:Для объединения модели с вашим приложением:
dependencies { // ... // Object detection & tracking feature with custom bundled model implementation 'com.google.mlkit:object-detection-custom:17.0.2' }
Для динамической загрузки модели из Firebase добавьте зависимость
linkFirebase
:dependencies { // ... // Object detection & tracking feature with model downloaded // from firebase implementation 'com.google.mlkit:object-detection-custom:17.0.2' implementation 'com.google.mlkit:linkfirebase:17.0.0' }
Если вы хотите загрузить модель , обязательно добавьте Firebase в свой проект Android , если вы еще этого не сделали. Это не требуется при объединении модели.
1. Загрузите модель
Настройте источник локальной модели
Чтобы связать модель с вашим приложением:
Скопируйте файл модели (обычно заканчивающийся на
.tflite
или.lite
) в папкуassets/
вашего приложения. (Возможно, вам придется сначала создать папку, щелкнув правой кнопкой мышиapp/
папку, а затем выбрав «Создать» > «Папка» > «Папка ресурсов» .)Затем добавьте следующее в файл
build.gradle
вашего приложения, чтобы Gradle не сжимал файл модели при сборке приложения:android { // ... aaptOptions { noCompress "tflite" // or noCompress "lite" } }
Файл модели будет включен в пакет приложения и доступен ML Kit в качестве необработанного ресурса.
Создайте объект
LocalModel
, указав путь к файлу модели:Котлин
val localModel = LocalModel.Builder() .setAssetFilePath("model.tflite") // or .setAbsoluteFilePath(absolute file path to model file) // or .setUri(URI to model file) .build()
Ява
LocalModel localModel = new LocalModel.Builder() .setAssetFilePath("model.tflite") // or .setAbsoluteFilePath(absolute file path to model file) // or .setUri(URI to model file) .build();
Настройте источник модели, размещенный в Firebase
Чтобы использовать удаленно размещенную модель, создайте объект CustomRemoteModel
с помощью FirebaseModelSource
, указав имя, которое вы присвоили модели при ее публикации:
Котлин
// Specify the name you assigned in the Firebase console. val remoteModel = CustomRemoteModel .Builder(FirebaseModelSource.Builder("your_model_name").build()) .build()
Ява
// Specify the name you assigned in the Firebase console. CustomRemoteModel remoteModel = new CustomRemoteModel .Builder(new FirebaseModelSource.Builder("your_model_name").build()) .build();
Затем запустите задачу загрузки модели, указав условия, при которых вы хотите разрешить загрузку. Если модели нет на устройстве или доступна более новая версия модели, задача асинхронно загрузит модель из Firebase:
Котлин
val downloadConditions = DownloadConditions.Builder() .requireWifi() .build() RemoteModelManager.getInstance().download(remoteModel, downloadConditions) .addOnSuccessListener { // Success. }
Ява
DownloadConditions downloadConditions = new DownloadConditions.Builder() .requireWifi() .build(); RemoteModelManager.getInstance().download(remoteModel, downloadConditions) .addOnSuccessListener(new OnSuccessListener() { @Override public void onSuccess(@NonNull Task task) { // Success. } });
Многие приложения запускают задачу загрузки в своем коде инициализации, но вы можете сделать это в любой момент, прежде чем вам понадобится использовать модель.
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)
Ява
// 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);
Если у вас есть удаленно размещенная модель, вам придется убедиться, что она загружена, прежде чем запускать ее. Вы можете проверить статус задачи загрузки модели с помощью метода isModelDownloaded()
менеджера моделей.
Хотя вам нужно подтвердить это только перед запуском детектора, если у вас есть как удаленно размещенная модель, так и локально связанная модель, возможно, имеет смысл выполнить эту проверку при создании экземпляра детектора изображений: создайте детектор из удаленной модели, если оно скачано, а иначе из локальной модели.
Котлин
RemoteModelManager.getInstance().isModelDownloaded(remoteModel) .addOnSuccessListener { isDownloaded -> val optionsBuilder = if (isDownloaded) { CustomObjectDetectorOptions.Builder(remoteModel) } else { CustomObjectDetectorOptions.Builder(localModel) } val customObjectDetectorOptions = optionsBuilder .setDetectorMode(CustomObjectDetectorOptions.SINGLE_IMAGE_MODE) .enableClassification() .setClassificationConfidenceThreshold(0.5f) .setMaxPerObjectLabelCount(3) .build() val objectDetector = ObjectDetection.getClient(customObjectDetectorOptions) }
Ява
RemoteModelManager.getInstance().isModelDownloaded(remoteModel) .addOnSuccessListener(new OnSuccessListener() { @Override public void onSuccess(Boolean isDownloaded) { CustomObjectDetectorOptions.Builder optionsBuilder; if (isDownloaded) { optionsBuilder = new CustomObjectDetectorOptions.Builder(remoteModel); } else { optionsBuilder = new CustomObjectDetectorOptions.Builder(localModel); } CustomObjectDetectorOptions customObjectDetectorOptions = optionsBuilder .setDetectorMode(CustomObjectDetectorOptions.SINGLE_IMAGE_MODE) .enableClassification() .setClassificationConfidenceThreshold(0.5f) .setMaxPerObjectLabelCount(3) .build(); ObjectDetector objectDetector = ObjectDetection.getClient(customObjectDetectorOptions); } });
Если у вас есть только удаленно размещенная модель, вам следует отключить функции, связанные с моделью, например сделать их серыми или скрыть часть пользовательского интерфейса, пока вы не подтвердите, что модель загружена. Вы можете сделать это, присоединив прослушиватель к методу download()
менеджера моделей:
Котлин
RemoteModelManager.getInstance().download(remoteModel, conditions) .addOnSuccessListener { // Download complete. Depending on your app, you could enable the ML // feature, or switch from the local model to the remote model, etc. }
Ява
RemoteModelManager.getInstance().download(remoteModel, conditions) .addOnSuccessListener(new OnSuccessListener() { @Override public void onSuccess(Void v) { // Download complete. Depending on your app, you could enable // the ML feature, or switch from the local model to the remote // model, etc. } });
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 // ... } } }
Ява
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
вместе с градусами поворота.
4. Запустите детектор объектов
Котлин
objectDetector .process(image) .addOnFailureListener(e -> {...}) .addOnSuccessListener(results -> { for (detectedObject in results) { // ... } });
Ява
objectDetector .process(image) .addOnFailureListener(e -> {...}) .addOnSuccessListener(results -> { for (DetectedObject detectedObject : results) { // ... } });
5. Получить информацию о помеченных объектах
Если вызов process()
успешен, список DetectedObject
передается прослушивателю успеха.
Каждый DetectedObject
содержит следующие свойства:
Ограничительная рамка | Rect , указывающий положение объекта на изображении. | ||||||
Идентификатор отслеживания | Целое число, которое идентифицирует объект на изображениях. Значение NULL в SINGLE_IMAGE_MODE. | ||||||
Этикетки |
|
Котлин
// 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 } }
Ява
// 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 камеры, захватывайте изображения в форматеImageFormat.NV21
.