Rotular imagens com um modelo treinado em AutoML no Android

Depois de treinar seu próprio modelo usando o AutoML Vision Edge , você poderá usá-lo em seu aplicativo para rotular imagens.

Antes de você começar

  1. Adicione o Firebase ao seu projeto Android , caso ainda não o tenha feito.
  2. Adicione as dependências das bibliotecas Android do ML Kit ao arquivo Gradle do módulo (nível do aplicativo) (geralmente app/build.gradle ):
    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. Carregue o modelo

O ML Kit executa seus modelos gerados pelo AutoML no dispositivo. No entanto, você pode configurar o kit de ML para carregar seu modelo remotamente do Firebase, do armazenamento local ou de ambos.

Ao hospedar o modelo no Firebase, você pode atualizá-lo sem lançar uma nova versão do aplicativo e pode usar a Configuração remota e o teste A/B para veicular dinamicamente diferentes modelos para diferentes conjuntos de usuários.

Se você optar por fornecer o modelo apenas hospedando-o no Firebase e não agrupá-lo ao seu aplicativo, poderá reduzir o tamanho inicial do download do seu aplicativo. Tenha em mente, porém, que se o modelo não estiver incluído no seu aplicativo, qualquer funcionalidade relacionada ao modelo não estará disponível até que seu aplicativo baixe o modelo pela primeira vez.

Ao agrupar seu modelo com seu aplicativo, você pode garantir que os recursos de ML do seu aplicativo ainda funcionem quando o modelo hospedado pelo Firebase não estiver disponível.

Configurar uma origem de modelo hospedado 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()

Em seguida, inicie a tarefa de download do modelo, especificando as condições sob as quais deseja 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 assíncrono do modelo do 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 aplicativos iniciam a tarefa de download em seu código de inicialização, mas você pode fazer isso a qualquer momento antes de usar o modelo.

Configurar uma origem de modelo local

Para agrupar o modelo com seu aplicativo:

  1. Extraia o modelo e seus metadados do arquivo zip baixado do console do Firebase. Recomendamos que você use os arquivos conforme os baixou, sem modificação (incluindo os nomes dos arquivos).
  2. Inclua seu modelo e seus arquivos de metadados no pacote do seu aplicativo:

    1. Se você não tiver uma pasta de ativos em seu projeto, crie uma clicando com o botão direito na pasta app/ e clicando em Novo > Pasta > Pasta de ativos .
    2. Crie uma subpasta na pasta de ativos para conter os arquivos de modelo.
    3. 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 seu aplicativo para garantir que o Gradle não compacte o arquivo de modelo ao criar o aplicativo:
    android {
        // ...
        aaptOptions {
            noCompress "tflite"
        }
    }
    
    O arquivo de modelo será incluído no pacote do aplicativo e disponível para o Kit de ML como um ativo 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()

Crie um rotulador de imagem a partir do seu modelo

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

Se você tiver apenas um modelo agrupado localmente, basta criar um rotulador a partir do objeto FirebaseAutoMLLocalModel e configurar o limite de pontuação de confiança que deseja exigir (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ê tiver um modelo hospedado remotamente, deverá verificar se ele foi baixado antes de executá-lo. Você pode verificar o status da tarefa de download do modelo usando o método isModelDownloaded() do gerenciador de modelo.

Embora você só precise confirmar isso antes de executar o rotulador, se você tiver um modelo hospedado remotamente e um modelo empacotado localmente, pode fazer sentido realizar esta verificação ao instanciar o rotulador de imagem: crie um rotulador a partir do modelo remoto se ele foi baixado e do modelo local, caso contrário.

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ê tiver apenas um modelo hospedado remotamente, desative a funcionalidade relacionada ao modelo (por exemplo, esmaecer ou ocultar parte da interface do usuário) até confirmar que o download do modelo foi feito. Você pode fazer isso anexando um ouvinte ao método download() do gerenciador de modelo:

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

Em seguida, para cada imagem que você deseja rotular, crie um objeto FirebaseVisionImage usando uma das opções descritas nesta seção e passe-o para uma instância de FirebaseVisionImageLabeler (descrita na próxima seção).

Você pode 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 a partir de um objeto media.Image , como ao capturar uma imagem da câmera de um dispositivo, passe o objeto media.Image e a rotação da imagem para FirebaseVisionImage.fromMediaImage() .

    Se você usar a biblioteca CameraX , as classes OnImageCapturedListener e ImageAnalysis.Analyzer calcularão o valor de rotação para você, então você só precisa 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âmeras que fornece a rotação da imagem, você pode calculá-la a partir da rotação do dispositivo e da orientação do sensor da câmera no dispositivo:

    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, passe 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 a partir de um URI de arquivo, passe o contexto do aplicativo e o URI do arquivo para FirebaseVisionImage.fromFilePath() . Isso é útil quando você usa uma intent ACTION_GET_CONTENT para solicitar que o usuário selecione uma imagem do aplicativo de galeria.
    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 a partir de um ByteBuffer ou de 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 array 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 a partir de um objeto Bitmap :
    FirebaseVisionImage image = FirebaseVisionImage.fromBitmap(bitmap);
    val image = FirebaseVisionImage.fromBitmap(bitmap)
    A imagem representada pelo objeto Bitmap deve estar na vertical, sem necessidade de rotação adicional.

3. Execute o rotulador de imagens

Para rotular objetos em uma imagem, passe o objeto FirebaseVisionImage para o 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
           
// ...
       
}

Se a rotulagem da imagem for bem-sucedida, uma matriz de objetos FirebaseVisionImageLabel será transmitida ao ouvinte de sucesso. De cada objeto você pode obter informações sobre uma característica reconhecida na imagem.

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 para o detector. Se um novo quadro de vídeo ficar disponível enquanto o detector estiver em execução, elimine o quadro.
  • Se você estiver usando a saída do detector para sobrepor gráficos na imagem de entrada, primeiro obtenha o resultado do Kit de ML e, em seguida, renderize a imagem e a sobreposição em uma única etapa. Ao fazer isso, você renderiza na superfície de exibição apenas uma vez para cada quadro 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 .