Помечайте изображения с помощью модели, обученной AutoML, на Android

После обучения собственной модели с помощью AutoML Vision Edge вы можете использовать ее в своем приложении для маркировки изображений. Существует два способа интеграции моделей, обученных с помощью AutoML Vision Edge: вы можете объединить модель, поместив ее в папку ресурсов вашего приложения, или вы можете динамически загрузить ее из Firebase.
Варианты комплектации модели
Включено в ваше приложение
  • Модель является частью APK вашего приложения.
  • Модель доступна сразу, даже когда Android-устройство находится в автономном режиме.
  • Нет необходимости в проекте Firebase
Размещено на Firebase
  • Разместите модель, загрузив ее в Firebase Machine Learning.
  • Уменьшает размер APK
  • Модель скачивается по запросу.
  • Отправляйте обновления модели без повторной публикации приложения.
  • Простое A/B-тестирование с помощью Firebase Remote Config
  • Требуется проект Firebase

Попробуйте это

Прежде чем вы начнете

1. В файле build.gradle уровня проекта обязательно включите репозиторий Google Maven как в разделы buildscript , так и в разделы allprojects .

2. Добавьте зависимости для библиотек Android ML Kit в файл Gradle уровня приложения вашего модуля, который обычно имеет app/build.gradle . Для объединения модели с вашим приложением:
    dependencies {
      // ...
      // Image labeling feature with bundled automl model
      implementation 'com.google.mlkit:image-labeling-automl:16.2.1'
    }
    
Для динамической загрузки модели из Firebase добавьте linkFirebase зависимость:
    dependencies {
      // ...
      // Image labeling feature with automl model downloaded
      // from firebase
      implementation 'com.google.mlkit:image-labeling-automl:16.2.1'
      implementation 'com.google.mlkit:linkfirebase:16.0.1'
    }
    
3. Если вы хотите загрузить модель , обязательно добавьте Firebase в свой проект Android , если вы еще этого не сделали. Это не требуется при объединении модели.

1. Загрузите модель

Настройте источник локальной модели

Чтобы связать модель с вашим приложением:

1. Извлеките модель и ее метаданные из zip-архива, который вы скачали с консоли Firebase. Мы рекомендуем использовать файлы в том виде, в котором вы их скачали, без изменений (включая имена файлов).

2. Включите свою модель и ее файлы метаданных в пакет приложения:

а. Если в вашем проекте нет папки ресурсов, создайте ее, щелкнув правой кнопкой мыши app/ папку, а затем выбрав «Создать» > «Папка» > «Папка ресурсов» .

б. Создайте подпапку в папке ресурсов, в которой будут храниться файлы модели.

в. Скопируйте файлы model.tflite , dict.txt и manifest.json в подпапку (все три файла должны находиться в одной папке).

3. Добавьте следующее в файл build.gradle вашего приложения, чтобы Gradle не сжимал файл модели при сборке приложения:
    android {
        // ...
        aaptOptions {
            noCompress "tflite"
        }
    }
    
Файл модели будет включен в пакет приложения и доступен ML Kit как необработанный ресурс.

Примечание. Начиная с версии 4.1 плагина Android Gradle, .tflite будет добавлен в список noCompress по умолчанию, и вышеуказанное больше не понадобится.

4. Создайте объект LocalModel , указав путь к файлу манифеста модели:

Котлин

val localModel = AutoMLImageLabelerLocalModel.Builder()
        .setAssetFilePath("manifest.json")
        // or .setAbsoluteFilePath(absolute file path to manifest file)
        .build()

Джава

AutoMLImageLabelerLocalModel localModel =
    new AutoMLImageLabelerLocalModel.Builder()
        .setAssetFilePath("manifest.json")
        // or .setAbsoluteFilePath(absolute file path to manifest file)
        .build();

Настройте источник модели, размещенный в Firebase

Чтобы использовать удаленно размещенную модель, создайте объект RemoteModel , указав имя, которое вы присвоили модели при ее публикации:

Котлин

// Specify the name you assigned in the Firebase console.
val remoteModel =
    AutoMLImageLabelerRemoteModel.Builder("your_model_name").build()

Джава

// Specify the name you assigned in the Firebase console.
AutoMLImageLabelerRemoteModel remoteModel =
    new AutoMLImageLabelerRemoteModel.Builder("your_model_name").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.
            }
        });

Многие приложения запускают задачу загрузки в своем коде инициализации, но вы можете сделать это в любой момент, прежде чем вам понадобится использовать модель.

Создайте маркировщик изображений на основе своей модели.

После настройки источников модели создайте объект ImageLabeler на основе одного из них.

Если у вас есть только локально связанная модель, просто создайте метку из объекта AutoMLImageLabelerLocalModel и настройте требуемый порог оценки достоверности (см. Оценка вашей модели ):

Котлин

val autoMLImageLabelerOptions = AutoMLImageLabelerOptions.Builder(localModel)
    .setConfidenceThreshold(0)  // Evaluate your model in the Firebase console
                                // to determine an appropriate value.
    .build()
val labeler = ImageLabeling.getClient(autoMLImageLabelerOptions)

Джава

AutoMLImageLabelerOptions autoMLImageLabelerOptions =
        new AutoMLImageLabelerOptions.Builder(localModel)
                .setConfidenceThreshold(0.0f)  // Evaluate your model in the Firebase console
                                               // to determine an appropriate value.
                .build();
ImageLabeler labeler = ImageLabeling.getClient(autoMLImageLabelerOptions)

Если у вас есть удаленно размещенная модель, вам придется убедиться, что она загружена, прежде чем запускать ее. Вы можете проверить статус задачи загрузки модели, используя метод isModelDownloaded() менеджера моделей.

Хотя вам нужно подтвердить это только перед запуском средства разметки, если у вас есть как удаленно размещенная модель, так и локально связанная модель, возможно, имеет смысл выполнить эту проверку при создании экземпляра средства разметки изображений: создайте средство разметки из удаленной модели, если оно скачано, а иначе из локальной модели.

Котлин

RemoteModelManager.getInstance().isModelDownloaded(remoteModel)
    .addOnSuccessListener { isDownloaded -> 
    val optionsBuilder =
        if (isDownloaded) {
            AutoMLImageLabelerOptions.Builder(remoteModel)
        } else {
            AutoMLImageLabelerOptions.Builder(localModel)
        }
    // Evaluate your model in the Firebase console to determine an appropriate threshold.
    val options = optionsBuilder.setConfidenceThreshold(0.0f).build()
    val labeler = ImageLabeling.getClient(options)
}

Джава

RemoteModelManager.getInstance().isModelDownloaded(remoteModel)
        .addOnSuccessListener(new OnSuccessListener() {
            @Override
            public void onSuccess(Boolean isDownloaded) {
                AutoMLImageLabelerOptions.Builder optionsBuilder;
                if (isDownloaded) {
                    optionsBuilder = new AutoMLImageLabelerOptions.Builder(remoteModel);
                } else {
                    optionsBuilder = new AutoMLImageLabelerOptions.Builder(localModel);
                }
                AutoMLImageLabelerOptions options = optionsBuilder
                        .setConfidenceThreshold(0.0f)  // Evaluate your model in the Firebase console
                                                       // to determine an appropriate threshold.
                        .build();

                ImageLabeler labeler = ImageLabeling.getClient(options);
            }
        });

Если у вас есть только удаленно размещенная модель, вам следует отключить функции, связанные с моделью, например сделать их серыми или скрыть часть пользовательского интерфейса, пока вы не подтвердите, что модель загружена. Вы можете сделать это, присоединив прослушиватель к методу 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.
            }
        });

2. Подготовьте входное изображение

Затем для каждого изображения, которое вы хотите пометить, создайте объект InputImage из вашего изображения. Средство разметки изображений работает быстрее всего, если вы используете Bitmap или, если вы используете API camera2, YUV_420_888 media.Image , которые рекомендуется использовать, когда это возможно.

Вы можете создать объект 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. Запустите программу разметки изображений.

Чтобы пометить объекты на изображении, передайте объект image в метод process() ImageLabeler .

Котлин

labeler.process(image)
        .addOnSuccessListener { labels ->
            // Task completed successfully
            // ...
        }
        .addOnFailureListener { e ->
            // Task failed with an exception
            // ...
        }

Джава

labeler.process(image)
        .addOnSuccessListener(new OnSuccessListener<List<ImageLabel>>() {
            @Override
            public void onSuccess(List<ImageLabel> labels) {
                // Task completed successfully
                // ...
            }
        })
        .addOnFailureListener(new OnFailureListener() {
            @Override
            public void onFailure(@NonNull Exception e) {
                // Task failed with an exception
                // ...
            }
        });

4. Получить информацию о помеченных объектах

Если операция маркировки изображения завершается успешно, список объектов ImageLabel передается прослушивателю успеха. Каждый объект ImageLabel представляет собой что-то, что было помечено на изображении. Вы можете получить текстовое описание каждой метки, степень достоверности совпадения и индекс совпадения. Например:

Котлин

for (label in labels) {
    val text = label.text
    val confidence = label.confidence
    val index = label.index
}

Джава

for (ImageLabel label : labels) {
    String text = label.getText();
    float confidence = label.getConfidence();
    int index = label.getIndex();
}

Советы по повышению производительности в реальном времени

Если вы хотите маркировать изображения в приложении реального времени, следуйте этим рекомендациям для достижения наилучшей частоты кадров:

  • Если вы используете 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 .