Rotular imagens com um modelo treinado pelo AutoML no Android

Depois de treinar seu próprio modelo usando o AutoML Vision Edge, você pode usá-lo em seu aplicativo para rotular imagens. Há duas maneiras de integrar modelos treinados pelo AutoML Vision Edge: é possível agrupar o modelo colocando-o na pasta de recursos do app ou fazer o download dele dinamicamente usando o Firebase.
Opções de empacotamento de modelos
Incluído no seu app
  • O modelo faz parte do APK do seu app
  • O modelo estará disponível imediatamente, mesmo quando o dispositivo Android estiver off-line
  • Não é necessário ter um projeto do Firebase
Hospedado com o Firebase
  • A hospedagem do modelo é feita quando ele é enviado para o Firebase Machine Learning
  • Reduz o tamanho do APK
  • O download do modelo é feito sob demanda
  • Enviar atualizações do modelo sem republicar o app
  • Teste A/B fácil com a Configuração remota do Firebase
  • Requer um projeto do Firebase

Faça um teste

Antes de começar

1. No arquivo build.gradle no nível do projeto, inclua o repositório Maven do Google nas seções buildscript e allprojects.

2. Adicione as dependências das bibliotecas do Kit de ML para Android ao arquivo Gradle do módulo (nível do app), que geralmente é app/build.gradle: Para agrupar um modelo com o app:
    dependencies {
      // ...
      // Image labeling feature with bundled automl model
      implementation 'com.google.mlkit:image-labeling-automl:16.2.1'
    }
    
Para fazer o download dinâmico de um modelo do Firebase, adicione a dependência 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. Se você quiser fazer o download de um modelo, adicione o Firebase ao seu projeto do Android, caso ainda não tenha feito isso. Essa etapa não é necessária para empacotar o modelo.

1. Carregar o modelo

Configurar uma fonte de modelo local

Para agrupar o modelo com o app:

1. Extraia o modelo e os metadados dele do arquivo ZIP que você salvou do Console do Firebase. Recomendamos usar os arquivos da maneira como foram salvos no download, sem fazer alterações neles, inclusive nos nomes.

2: Inclua o modelo e os arquivos de metadados dele no pacote do app:

A) Se você não tiver uma pasta de recursos no projeto, crie uma clicando com o botão direito do mouse na pasta app/ e, em seguida, em Novo > Pasta > Pasta de recursos.

b. Crie uma subpasta dentro da pasta de recursos para armazenar o modelo .

c. Copie os arquivos model.tflite, dict.txt e manifest.json para a subpasta (todos os três arquivos devem estar na mesma pasta).

3. Adicione o seguinte ao arquivo build.gradle do app para garantir que o Gradle não compacte o arquivo de modelo ao criar o app:
    android {
        // ...
        aaptOptions {
            noCompress "tflite"
        }
    }
    
O arquivo de modelo será incluído no pacote do app e disponibilizado para o Kit de ML como um recurso bruto.

Observação: a partir da versão 4.1 do Plug-in do Android para Gradle, o .tflite será adicionado à lista noCompress por padrão, e a instrução acima não será mais necessária.

4) Crie o objeto LocalModel, especificando o caminho para o arquivo de manifesto do modelo:

Kotlin

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

Java

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

Configurar uma fonte de modelo hospedada no Firebase

Para usar o modelo hospedado remotamente, crie um objeto RemoteModel, especificando o nome que você atribuiu ao modelo quando o publicou:

Kotlin

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

Java

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

Em seguida, inicie a tarefa de download do modelo, especificando as condições sob as quais você quer permitir o download. Se o modelo não estiver no dispositivo ou se uma versão mais recente do modelo estiver disponível, a tarefa fará o download do modelo de forma assíncrona do Firebase:

Kotlin

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

Java

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

Muitos apps iniciam a tarefa de download no código de inicialização, mas você pode fazer isso a qualquer momento antes de precisar usar o modelo.

Criar um rotulador de imagens a partir do modelo

Depois de configurar as origens do modelo, crie um objeto ImageLabeler usando uma um deles.

Se você tiver apenas um modelo agrupado localmente, basta criar um rotulador a partir do objeto AutoMLImageLabelerLocalModel e configurar o limite de pontuação de confiança exigido. Consulte Avaliar seu modelo:

Kotlin

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)

Java

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)

Se você tiver um modelo hospedado remotamente, será necessário verificar se ele foi antes de executá-lo. É possível verificar o status do download do modelo tarefa usando o método isModelDownloaded() do gerenciador de modelos.

Embora você só precise confirmar isso antes de executar o rotulador, se você tiver um modelo hospedado remotamente e um modelo agrupado localmente, pode ser útil fazer essa verificação ao instanciar o rotulador de imagens: crie um rotulador do modelo remoto se ele tiver sido feito o download e do modelo local, caso contrário.

Kotlin

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

Java

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

Se você tiver apenas um modelo hospedado remotamente, desative o recurso da interface (por exemplo, usar o recurso esmaecer ou ocultar parte da interface) até confirme se o download do modelo foi concluído. Para fazer isso, anexe um listener ao método download() do gerenciador de modelos:

Kotlin

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

Java

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. Preparar a imagem de entrada

Em seguida, para cada imagem que você quer rotular, crie um objeto InputImage usando sua imagem. O rotulador de imagens é executado mais rapidamente quando você usa um Bitmap ou, se você usa a API camera2, um YUV_420_888 media.Image. Essas opções são recomendadas quando possível.

Você pode criar um InputImage de diferentes origens, cada uma explicada abaixo.

Como usar um media.Image

Para criar um InputImage de um objeto media.Image, como quando você captura uma imagem de um da câmera do dispositivo, transmita o objeto media.Image e o rotação para InputImage.fromMediaImage().

Se você usar o método CameraX, os recursos OnImageCapturedListener e As classes ImageAnalysis.Analyzer calculam o valor de rotação para você.

Kotlin

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

Se você não usar uma biblioteca de câmera que informe o grau de rotação da imagem, pode calculá-lo usando o grau de rotação do dispositivo e a orientação da câmera no dispositivo:

Kotlin

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

Em seguida, transmita o objeto media.Image e o grau de rotação para InputImage.fromMediaImage():

Kotlin

val image = InputImage.fromMediaImage(mediaImage, rotation)

Java

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

Usar um URI de arquivo

Para criar um InputImage de um URI de arquivo, transmita o contexto do aplicativo e o URI do arquivo para InputImage.fromFilePath(). Isso é útil quando você usar uma intent ACTION_GET_CONTENT para solicitar que o usuário selecione uma imagem do app Galeria.

Kotlin

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();
}

Como usar ByteBuffer ou ByteArray

Para criar um objeto InputImage usando um ByteBuffer ou um ByteArray, primeiro calcule o grau de rotação da imagem conforme descrito anteriormente para a entrada de media.Image. Em seguida, crie o objeto InputImage com o buffer ou a matriz, com a altura, a largura, o formato de codificação de cores e o grau de rotação da imagem:

Kotlin

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
);

Como usar um Bitmap

Para criar um InputImage de um objeto Bitmap, faça a seguinte declaração:

Kotlin

val image = InputImage.fromBitmap(bitmap, 0)

Java

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

A imagem é representada por um objeto Bitmap com os graus de rotação.

3. Executar o rotulador de imagens

Para rotular objetos em uma imagem, transmita o objeto image para o objeto ImageLabeler. process().

Kotlin

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

Java

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. Receber informações sobre objetos rotulados

Se a operação de rotulagem de imagem for bem-sucedida, uma lista de ImageLabel são transmitidos para o listener de êxito. Cada objeto ImageLabel representa algo que estava rotulado na imagem. É possível receber o texto de cada rótulo descrição, a pontuação de confiança da correspondência e o índice da correspondência. Exemplo:

Kotlin

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

Java

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

Dicas para melhorar o desempenho em tempo real

Se você quiser rotular imagens em um aplicativo em tempo real, siga estas instruções diretrizes para obter as melhores taxas de quadros:

  • Se você usar o método Camera ou API camera2, de limitação para o rotulador de imagens. Se um novo vídeo fique disponível enquanto o rotulador de imagens está em execução, elimine o frame. Consulte a classe VisionProcessorBase no app de amostra do guia de início rápido para conferir um exemplo.
  • Se você usar a API CameraX, verifique se a estratégia de backpressure está definida como o valor padrão ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST. Isso garante que apenas uma imagem será enviada para análise por vez. Se mais imagens forem produzidas quando o analisador estiver ocupado, elas serão descartadas automaticamente e não serão colocadas na fila para envio. Quando a imagem analisada é fechada chamando ImageProxy.close(), a próxima imagem mais recente é entregue.
  • Se você usar a saída do rotulador de imagens para sobrepor elementos gráficos na imagem de entrada, primeiro acesse o resultado do Kit de ML e, em seguida, renderize a imagem e a sobreposição em uma única etapa. Isso é renderizado na superfície da tela. apenas uma vez para cada frame de entrada. Consulte a CameraSourcePreview e GraphicOverlay no app de amostra do guia de início rápido para conferir um exemplo.
  • Se você usar a API Camera2, capture imagens no formato ImageFormat.YUV_420_888. Se você usar a API Camera mais antiga, capture imagens no formato ImageFormat.NV21.