Join us for Firebase Summit on November 10, 2021. Tune in to learn how Firebase can help you accelerate app development, release with confidence, and scale with ease. Register

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

После того, как вы дрессировать свою собственную модель с помощью AutoML видения Грани , вы можете использовать его в приложении к изображениям этикетки.

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

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

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

  1. Добавьте зависимости для библиотек ML Kit для Android в файл приложения уровня Gradle вашего модуля, который обычно является app/build.gradle :

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

    dependencies {
      // ...
      // Image labeling feature with bundled automl model
      implementation 'com.google.mlkit:image-labeling-custom:16.3.1'
    }
    

    Для динамически загружать модели из Firebase, добавьте linkFirebase зависимость:

    dependencies {
      // ...
      // Image labeling feature with automl model downloaded
      // from firebase
      implementation 'com.google.mlkit:image-labeling-custom:16.3.1'
      implementation 'com.google.mlkit:linkfirebase:16.1.0'
    }
    
  2. Если вы хотите , чтобы загрузить модель, убедитесь , что вы добавить Firebase в свой Android проекта , если вы еще не сделали этого. При комплектации модели этого не требуется.

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

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

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

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

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

    1. Если вы не имеете активов папку в вашем проекте, создать , щелкнув правой кнопкой мыши на app/ папку, затем щелкните Создать> Папка> Активы папки.
    2. Создайте подпапку под папкой ресурсов, чтобы содержать файлы модели.
    3. Скопируйте файлы model.tflite , dict.txt и manifest.json к подпапке (все три файла должны находиться в одной и той же папке).
  3. Добавьте следующие строки в приложениях build.gradle файл , чтобы обеспечить Gradle не сжимает файл модель при создании приложения:

    android {
        // ...
        aaptOptions {
            noCompress "tflite"
        }
    }
    

    Файл модели будет включен в пакет приложения и доступен для ML Kit в качестве исходного ресурса.

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

    Джава

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

    Котлин

    val localModel = LocalModel.Builder()
        .setAssetManifestFilePath("manifest.json")
        // or .setAbsoluteManifestFilePath(absolute file path to manifest file)
        .build()
    

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

Для того, чтобы использовать дистанционно размещаемую модель, создать CustomRemoteModel объект, указав имя присвоенного модель , когда вы опубликовали его:

Джава

// Specify the name you assigned in the Firebase console.
FirebaseModelSource firebaseModelSource =
    new FirebaseModelSource.Builder("your_model_name").build();
CustomRemoteModel remoteModel =
    new CustomRemoteModel.Builder(firebaseModelSource).build();

Котлин

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

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

Джава

DownloadConditions downloadConditions = new DownloadConditions.Builder()
        .requireWifi()
        .build();
RemoteModelManager.getInstance().download(remoteModel, downloadConditions)
        .addOnSuccessListener(new OnSuccessListener<Void>() {
            @Override
            public void onSuccess(@NonNull Task<Void> task) {
                // Success.
            }
        });

Котлин

val downloadConditions = DownloadConditions.Builder()
    .requireWifi()
    .build()
RemoteModelManager.getInstance().download(remoteModel, downloadConditions)
    .addOnSuccessListener {
        // Success.
    }

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

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

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

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

Джава

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

Котлин

val customImageLabelerOptions = CustomImageLabelerOptions.Builder(localModel)
    .setConfidenceThreshold(0.0f)  // Evaluate your model in the Cloud console
                                   // to determine an appropriate value.
    .build()
val labeler = ImageLabeling.getClient(customImageLabelerOptions)

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

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

Джава

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

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

Котлин

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

Если у вас есть только модель, размещенная на удаленном хосте, вам следует отключить связанные с моделью функции - например, выделение серым цветом или скрытие части вашего пользовательского интерфейса - до тех пор, пока вы не подтвердите, что модель была загружена. Вы можете сделать это путем присоединения слушателя к модельному менеджера download() метод:

Джава

RemoteModelManager.getInstance().download(remoteModel, conditions)
        .addOnSuccessListener(new OnSuccessListener<Void>() {
            @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.
            }
        });

Котлин

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.
    }

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

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

Вы можете создать InputImage из разных источников, каждый из них описан ниже.

Использование media.Image

Чтобы создать InputImage объект из media.Image объекта, например, при съемке с камеры устройства, передать media.Image объект и вращение изображения к InputImage.fromMediaImage() .

Если вы используете CameraX библиотеку, OnImageCapturedListener и ImageAnalysis.Analyzer классов вычислить значение вращения для вас.

Джава

private class YourAnalyzer implements ImageAnalysis.Analyzer {

    @Override
    public void analyze(ImageProxy imageProxy) {
        if (imageProxy == null || imageProxy.getImage() == null) {
            return;
        }
        Image mediaImage = imageProxy.getImage();
        InputImage image =
                InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees);
        // Pass image to an ML Kit Vision API
        // ...
    }
}

Котлин + KTX

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 static final SparseIntArray ORIENTATIONS = new SparseIntArray();
static {
    ORIENTATIONS.append(Surface.ROTATION_0, 90);
    ORIENTATIONS.append(Surface.ROTATION_90, 0);
    ORIENTATIONS.append(Surface.ROTATION_180, 270);
    ORIENTATIONS.append(Surface.ROTATION_270, 180);
}

/**
 * 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, Context context)
        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);

    // On most devices, the sensor orientation is 90 degrees, but for some
    // devices it is 270 degrees. For devices with a sensor orientation of
    // 270, rotate the image an additional 180 ((270 + 270) % 360) degrees.
    CameraManager cameraManager = (CameraManager) context.getSystemService(CAMERA_SERVICE);
    int sensorOrientation = cameraManager
            .getCameraCharacteristics(cameraId)
            .get(CameraCharacteristics.SENSOR_ORIENTATION);
    rotationCompensation = (rotationCompensation + sensorOrientation + 270) % 360;

    // Return the corresponding FirebaseVisionImageMetadata rotation value.
    int result;
    switch (rotationCompensation) {
        case 0:
            result = FirebaseVisionImageMetadata.ROTATION_0;
            break;
        case 90:
            result = FirebaseVisionImageMetadata.ROTATION_90;
            break;
        case 180:
            result = FirebaseVisionImageMetadata.ROTATION_180;
            break;
        case 270:
            result = FirebaseVisionImageMetadata.ROTATION_270;
            break;
        default:
            result = FirebaseVisionImageMetadata.ROTATION_0;
            Log.e(TAG, "Bad rotation value: " + rotationCompensation);
    }
    return result;
}

Котлин + KTX

private val ORIENTATIONS = SparseIntArray()

init {
    ORIENTATIONS.append(Surface.ROTATION_0, 90)
    ORIENTATIONS.append(Surface.ROTATION_90, 0)
    ORIENTATIONS.append(Surface.ROTATION_180, 270)
    ORIENTATIONS.append(Surface.ROTATION_270, 180)
}
/**
 * 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, context: Context): 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)

    // On most devices, the sensor orientation is 90 degrees, but for some
    // devices it is 270 degrees. For devices with a sensor orientation of
    // 270, rotate the image an additional 180 ((270 + 270) % 360) degrees.
    val cameraManager = context.getSystemService(CAMERA_SERVICE) as CameraManager
    val sensorOrientation = cameraManager
            .getCameraCharacteristics(cameraId)
            .get(CameraCharacteristics.SENSOR_ORIENTATION)!!
    rotationCompensation = (rotationCompensation + sensorOrientation + 270) % 360

    // Return the corresponding FirebaseVisionImageMetadata rotation value.
    val result: Int
    when (rotationCompensation) {
        0 -> result = FirebaseVisionImageMetadata.ROTATION_0
        90 -> result = FirebaseVisionImageMetadata.ROTATION_90
        180 -> result = FirebaseVisionImageMetadata.ROTATION_180
        270 -> result = FirebaseVisionImageMetadata.ROTATION_270
        else -> {
            result = FirebaseVisionImageMetadata.ROTATION_0
            Log.e(TAG, "Bad rotation value: $rotationCompensation")
        }
    }
    return result
}

Затем передать media.Image объект и значение степени поворота к InputImage.fromMediaImage() :

Джава

InputImage image = InputImage.fromMediaImage(mediaImage, rotation);

Котлин + KTX

val image = InputImage.fromMediaImage(mediaImage, rotation)

Использование URI файла

Чтобы создать InputImage объект из файла URI, передать контекст приложения и файл URI в InputImage.fromFilePath() . Это полезно , когда вы используете ACTION_GET_CONTENT намерение предложить пользователю выбрать изображение из галереи их приложения.

Джава

InputImage image;
try {
    image = InputImage.fromFilePath(context, uri);
} catch (IOException e) {
    e.printStackTrace();
}

Котлин + KTX

val image: InputImage
try {
    image = InputImage.fromFilePath(context, uri)
} catch (e: IOException) {
    e.printStackTrace()
}

Использование ByteBuffer или ByteArray

Чтобы создать InputImage объект из ByteBuffer или ByteArray , сначала рассчитать степень поворота изображения , как описано ранее для media.Image ввода. Затем создать InputImage объект с буфером или массивом, вместе с высотой изображения по ширине, кодирование формата цвета, и степени вращения:

Джава

InputImage image = InputImage.fromByteBuffer(byteBuffer,
        /* image width */ 480,
        /* image height */ 360,
        rotationDegrees,
        InputImage.IMAGE_FORMAT_NV21 // or IMAGE_FORMAT_YV12
);

Котлин + KTX

val image = InputImage.fromByteBuffer(
        byteBuffer,
        /* image width */ 480,
        /* image height */ 360,
        rotationDegrees,
        InputImage.IMAGE_FORMAT_NV21 // or IMAGE_FORMAT_YV12
)

Использование Bitmap

Чтобы создать InputImage объект из Bitmap объекта, сделать следующее заявление:

Джава

InputImage image = InputImage.fromBitmap(bitmap, rotationDegree);

Котлин + KTX

val image = InputImage.fromBitmap(bitmap, 0)

Изображение представляются в Bitmap объект вместе с вращением градусов.

3. Запустите средство создания этикеток для изображений.

Для того, чтобы этикетка объектов в изображении, передать image объекта к ImageLabeler «ы process() метода.

Джава

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
                // ...
            }
        });

Котлин

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

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

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

Джава

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

Котлин

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

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

Если вы хотите маркировать изображения в приложении реального времени, следуйте этим рекомендациям для достижения наилучшей частоты кадров:
  • Дросселирует вызовы этикетировщика изображений. Если новый видеокадр становится доступным во время работы средства маркировки изображений, отбросьте этот кадр. Смотрите VisionProcessorBase класс в примере приложения для быстрого старта. Например ,
  • Если вы используете выходные данные средства этикетирования изображений для наложения графики на входное изображение, сначала получите результат, а затем визуализируйте изображение и наложение за один шаг. Таким образом вы визуализируете поверхность отображения только один раз для каждого входного кадра. См CameraSourcePreview и GraphicOverlay классов в приложении Quickstart образца для примера.
  • Если вы используете Camera2 API, захват изображения в ImageFormat.YUV_420_888 формате.

    Если вы используете старую API камеры, захватывать изображения в ImageFormat.NV21 формате.