Rotular imagens com um modelo treinado pelo AutoML no Android

Após treinar seu próprio modelo com o AutoML Vision Edge, use esse modelo no seu app para rotular imagens.

Antes de começar

  1. Adicione o Firebase ao seu projeto para Android, caso ainda não tenha feito isso.
  2. Adicione as dependências das bibliotecas do Kit de ML para Android ao arquivo Gradle (geralmente app/build.gradle) do módulo (nível do aplicativo):
    apply plugin: 'com.android.application'
    apply plugin: 'com.google.gms.google-services'
    
    dependencies {
      // ...
    
      implementation 'com.google.firebase:firebase-ml-vision:24.0.3'
      implementation 'com.google.firebase:firebase-ml-vision-automl:18.0.5'
    }

1. Carregar o modelo

O Kit de ML executa seus modelos gerados pelo AutoML no dispositivo. No entanto, é possível configurar o Kit para carregar seu modelo remotamente a partir do Firebase, do armazenamento local ou de ambos.

Com a hospedagem do modelo no Firebase, você pode atualizar o modelo sem liberar uma nova versão do app e usar Remote Config e A/B Testing para exibir dinamicamente diferentes modelos para diferentes conjuntos de usuários.

Se você optar por fornecer o modelo hospedando-o apenas com o Firebase, sem agrupá-lo com o app, é possível reduzir o tamanho do download inicial do aplicativo. No entanto, se o modelo não estiver agrupado com o app, nenhuma função relacionada a ele vai estar disponível até que o app faça o download do modelo pela primeira vez.

Ao agrupar o modelo e o aplicativo, é possível garantir que os recursos de ML do aplicativo ainda funcionem quando o modelo hospedado pelo Firebase não estiver disponível.

Configurar uma fonte de modelo hospedada no Firebase

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

// Specify the name you assigned in the Firebase console.
FirebaseAutoMLRemoteModel remoteModel =
    new FirebaseAutoMLRemoteModel.Builder("your_remote_model").build();
// Specify the name you assigned in the Firebase console.
val remoteModel = FirebaseAutoMLRemoteModel.Builder("your_remote_model").build()

Depois inicie a tarefa de download do modelo, especificando as condições de acordo com 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 maneira assíncrona no Firebase:

FirebaseModelDownloadConditions conditions = new FirebaseModelDownloadConditions.Builder()
        .requireWifi()
        .build();
FirebaseModelManager.getInstance().download(remoteModel, conditions)
        .addOnCompleteListener(new OnCompleteListener<Void>() {
            @Override
            public void onComplete(@NonNull Task<Void> task) {
                // Success.
            }
        });
val conditions = FirebaseModelDownloadConditions.Builder()
    .requireWifi()
    .build()
FirebaseModelManager.getInstance().download(remoteModel, conditions)
    .addOnCompleteListener {
        // Success.
    }

Muitos apps iniciam a tarefa de download no código de inicialização, mas é possível fazer isso a qualquer momento antes de precisar usar o modelo.

Configurar uma fonte de modelo local

Para empacotar o modelo e o aplicativo, siga estas etapas:

  1. Extraia o modelo e os metadados dele do arquivo ZIP que você salvou usando o console do Firebase. Recomendamos usar os arquivos da maneira como foram salvos no download, sem fazer alterações neles, inclusive nos nomes.
  2. Inclua seu modelo e os arquivos de metadados dele no pacote do aplicativo:

    1. 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.
    2. Crie uma subpasta dentro da pasta de recursos para armazenar os arquivos do modelo.
    3. Copie os arquivos model.tflite, dict.txt e manifest.json para a subpasta (todos os três arquivos precisam 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 do modelo será incluído no pacote do app e vai estar disponível para o Kit de ML como um recurso bruto.
  4. Crie um objeto FirebaseAutoMLLocalModel, especificando o caminho para o arquivo de manifesto do modelo:
    FirebaseAutoMLLocalModel localModel = new FirebaseAutoMLLocalModel.Builder()
            .setAssetFilePath("manifest.json")
            .build();
    
    val localModel = FirebaseAutoMLLocalModel.Builder()
            .setAssetFilePath("manifest.json")
            .build()
    

Criar um rotulador de imagens usando o modelo

Depois de configurar as origens do modelo, crie um objeto FirebaseVisionImageLabeler a partir de uma delas.

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

FirebaseVisionImageLabeler labeler;
try {
    FirebaseVisionOnDeviceAutoMLImageLabelerOptions options =
            new FirebaseVisionOnDeviceAutoMLImageLabelerOptions.Builder(localModel)
                    .setConfidenceThreshold(0.0f)  // Evaluate your model in the Firebase console
                                                   // to determine an appropriate value.
                    .build();
    labeler = FirebaseVision.getInstance().getOnDeviceAutoMLImageLabeler(options);
} catch (FirebaseMLException e) {
    // ...
}
val options = FirebaseVisionOnDeviceAutoMLImageLabelerOptions.Builder(localModel)
    .setConfidenceThreshold(0)  // Evaluate your model in the Firebase console
                                // to determine an appropriate value.
    .build()
val labeler = FirebaseVision.getInstance().getOnDeviceAutoMLImageLabeler(options)

Se você tem um modelo hospedado remotamente, confira se foi feito o download dele antes da execução. É possível verificar o status da tarefa de download do modelo usando o método isModelDownloaded() do gerenciador de modelos.

Embora essa confirmação seja necessária apenas antes de executar o rotulador, se você tem um modelo hospedado remotamente e um modelo empacotado localmente, o ideal é fazer a verificação ao instanciar o rotulador de imagens: crie um rotulador usando o modelo remoto se ele tiver sido transferido por download. Caso contrário, use o modelo local.

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

                FirebaseVisionImageLabeler labeler;
                try {
                    labeler = FirebaseVision.getInstance().getOnDeviceAutoMLImageLabeler(options);
                } catch (FirebaseMLException e) {
                    // Error.
                }
            }
        });
FirebaseModelManager.getInstance().isModelDownloaded(remoteModel)
    .addOnSuccessListener { isDownloaded -> 
    val optionsBuilder =
        if (isDownloaded) {
            FirebaseVisionOnDeviceAutoMLImageLabelerOptions.Builder(remoteModel)
        } else {
            FirebaseVisionOnDeviceAutoMLImageLabelerOptions.Builder(localModel)
        }
    // Evaluate your model in the Firebase console to determine an appropriate threshold.
    val options = optionsBuilder.setConfidenceThreshold(0.0f).build()
    val labeler = FirebaseVision.getInstance().getOnDeviceAutoMLImageLabeler(options)
}

Se você tem apenas um modelo hospedado remotamente, desative a função relacionada ao modelo (por exemplo, ocultando ou esmaecendo parte da IU) até confirmar que o download do modelo foi concluído. Para fazer isso, anexe um listener ao método download() do gerenciador de modelos:

FirebaseModelManager.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.
            }
        });
FirebaseModelManager.getInstance().download(remoteModel, conditions)
    .addOnCompleteListener {
        // 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 FirebaseVisionImage usando uma das opções descritas nesta seção e transmita-o para uma instância de FirebaseVisionImageLabeler (descrita na próxima seção).

É possível criar um objeto FirebaseVisionImage a partir de um objeto media.Image, um arquivo no dispositivo, uma matriz de bytes ou um objeto Bitmap:

  • Para criar um objeto FirebaseVisionImage com base em um objeto media.Image, como ao capturar uma imagem da câmera de um dispositivo, transmita o objeto media.Image e a rotação da imagem para FirebaseVisionImage.fromMediaImage().

    Se você usa a biblioteca CameraX, as classes OnImageCapturedListener e ImageAnalysis.Analyzer calculam o valor de rotação para você. Basta converter a rotação em uma das constantes ROTATION_ do Kit de ML antes de chamar FirebaseVisionImage.fromMediaImage():

    private class YourAnalyzer implements ImageAnalysis.Analyzer {
    
        private int degreesToFirebaseRotation(int degrees) {
            switch (degrees) {
                case 0:
                    return FirebaseVisionImageMetadata.ROTATION_0;
                case 90:
                    return FirebaseVisionImageMetadata.ROTATION_90;
                case 180:
                    return FirebaseVisionImageMetadata.ROTATION_180;
                case 270:
                    return FirebaseVisionImageMetadata.ROTATION_270;
                default:
                    throw new IllegalArgumentException(
                            "Rotation must be 0, 90, 180, or 270.");
            }
        }
    
        @Override
        public void analyze(ImageProxy imageProxy, int degrees) {
            if (imageProxy == null || imageProxy.getImage() == null) {
                return;
            }
            Image mediaImage = imageProxy.getImage();
            int rotation = degreesToFirebaseRotation(degrees);
            FirebaseVisionImage image =
                    FirebaseVisionImage.fromMediaImage(mediaImage, rotation);
            // Pass image to an ML Kit Vision API
            // ...
        }
    }
    private class YourImageAnalyzer : ImageAnalysis.Analyzer {
        private fun degreesToFirebaseRotation(degrees: Int): Int = when(degrees) {
            0 -> FirebaseVisionImageMetadata.ROTATION_0
            90 -> FirebaseVisionImageMetadata.ROTATION_90
            180 -> FirebaseVisionImageMetadata.ROTATION_180
            270 -> FirebaseVisionImageMetadata.ROTATION_270
            else -> throw Exception("Rotation must be 0, 90, 180, or 270.")
        }
    
        override fun analyze(imageProxy: ImageProxy?, degrees: Int) {
            val mediaImage = imageProxy?.image
            val imageRotation = degreesToFirebaseRotation(degrees)
            if (mediaImage != null) {
                val image = FirebaseVisionImage.fromMediaImage(mediaImage, imageRotation)
                // Pass image to an ML Kit Vision API
                // ...
            }
        }
    }

    Se você não usa uma biblioteca de câmera que ofereça a rotação da imagem, faça o cálculo usando a rotação do dispositivo e a orientação do sensor da câmera:

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

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

    FirebaseVisionImage image = FirebaseVisionImage.fromMediaImage(mediaImage, rotation);
    val image = FirebaseVisionImage.fromMediaImage(mediaImage, rotation)
  • Para criar um objeto FirebaseVisionImage com base no URI de um arquivo, transmita o contexto do aplicativo e o URI do arquivo para FirebaseVisionImage.fromFilePath(). Isso é útil ao usar uma intent ACTION_GET_CONTENT para solicitar que o usuário selecione uma imagem no app de galeria dele.
    FirebaseVisionImage image;
    try {
        image = FirebaseVisionImage.fromFilePath(context, uri);
    } catch (IOException e) {
        e.printStackTrace();
    }
    val image: FirebaseVisionImage
    try {
        image = FirebaseVisionImage.fromFilePath(context, uri)
    } catch (e: IOException) {
        e.printStackTrace()
    }
  • Para criar um objeto FirebaseVisionImage com base em um ByteBuffer ou uma matriz de bytes, primeiro calcule a rotação da imagem conforme descrito acima para a entrada media.Image.

    Em seguida, crie um objeto FirebaseVisionImageMetadata que contenha a altura, a largura, o formato de codificação de cores e a rotação da imagem:

    FirebaseVisionImageMetadata metadata = new FirebaseVisionImageMetadata.Builder()
            .setWidth(480)   // 480x360 is typically sufficient for
            .setHeight(360)  // image recognition
            .setFormat(FirebaseVisionImageMetadata.IMAGE_FORMAT_NV21)
            .setRotation(rotation)
            .build();
    val metadata = FirebaseVisionImageMetadata.Builder()
            .setWidth(480) // 480x360 is typically sufficient for
            .setHeight(360) // image recognition
            .setFormat(FirebaseVisionImageMetadata.IMAGE_FORMAT_NV21)
            .setRotation(rotation)
            .build()

    Use o buffer ou a matriz e o objeto de metadados para criar um objeto FirebaseVisionImage:

    FirebaseVisionImage image = FirebaseVisionImage.fromByteBuffer(buffer, metadata);
    // Or: FirebaseVisionImage image = FirebaseVisionImage.fromByteArray(byteArray, metadata);
    val image = FirebaseVisionImage.fromByteBuffer(buffer, metadata)
    // Or: val image = FirebaseVisionImage.fromByteArray(byteArray, metadata)
  • Para criar um objeto FirebaseVisionImage com base em um objeto Bitmap:
    FirebaseVisionImage image = FirebaseVisionImage.fromBitmap(bitmap);
    val image = FirebaseVisionImage.fromBitmap(bitmap)
    A imagem representada pelo objeto Bitmap precisa estar na posição vertical, sem a necessidade de ser girada novamente.

3. Executar o rotulador de imagens

Para rotular objetos em uma imagem, transmita o objeto FirebaseVisionImage ao método processImage() do FirebaseVisionImageLabeler.

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

Quando a rotulação de imagem é concluída, uma matriz de objetos FirebaseVisionImageLabel é transmitida para o listener de êxito. É possível obter informações sobre um atributo reconhecido na imagem em cada objeto.

Por exemplo:

for (FirebaseVisionImageLabel label: labels) {
    String text = label.getText();
    float confidence = label.getConfidence();
}
for (label in labels) {
    val text = label.text
    val confidence = label.confidence
}

Dicas para melhorar o desempenho em tempo real

  • Limite as chamadas ao detector. Se um novo frame de vídeo ficar disponível durante a execução do detector, descarte esse frame.
  • Se você estiver usando a saída do detector para sobrepor elementos gráficos na imagem de entrada, primeiro acesse o resultado do Kit de ML. Em seguida, renderize a imagem e faça a sobreposição de uma só vez. Ao fazer isso, você renderiza a superfície de exibição apenas uma vez para cada frame de entrada.
  • 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.

O Firebase ML permite adicionar recursos avançados de machine learning ao seu app com APIs prontas para uso e suporte para implantação de modelos personalizados.

Atualização: Feb 28, 2025

O Firebase ML permite adicionar recursos avançados de machine learning ao seu app com APIs prontas para uso e suporte para implantação de modelos personalizados.

Atualização: Feb 28, 2025

O Kit de ML para Firebase oferece soluções de ML prontas para uso aos desenvolvedores de apps. Os novos apps precisam usar a biblioteca independente do Kit de ML em caso de ML no dispositivo e o Firebase ML para ML baseado na nuvem.

Atualização: Feb 28, 2025