Rotular imagens com kit de ML no Android

Você pode usar o Kit de ML para rotular objetos reconhecidos em uma imagem, usando um modelo no dispositivo ou um modelo na nuvem. Consulte a visão geral para saber mais sobre os benefícios de cada abordagem.

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-image-label-model:20.0.1'

    }
  3. Opcional, mas recomendado : se você usar a API no dispositivo, configure seu aplicativo para baixar automaticamente o modelo de ML para o dispositivo depois que seu aplicativo for instalado na Play Store.

    Para fazer isso, adicione a seguinte declaração ao arquivo AndroidManifest.xml do seu aplicativo:

    <application ...>
      ...
     
    <meta-data
         
    android:name="com.google.firebase.ml.vision.DEPENDENCIES"
         
    android:value="label" />
     
    <!-- To use multiple models: android:value="label,model2,model3" -->
    </application>
    Se você não ativar os downloads do modelo no momento da instalação, o modelo será baixado na primeira vez que você executar o detector no dispositivo. As solicitações feitas antes da conclusão do download não produzirão resultados.
  4. Se você quiser usar o modelo baseado em nuvem e ainda não tiver habilitado as APIs baseadas em nuvem para o seu projeto, faça-o agora:

    1. Abra a página APIs do kit de ML do console do Firebase.
    2. Se você ainda não atualizou seu projeto para um plano de preços Blaze, clique em Atualizar para fazer isso. (Você será solicitado a atualizar somente se o seu projeto não estiver no plano Blaze.)

      Somente projetos no nível Blaze podem usar APIs baseadas em nuvem.

    3. Se as APIs baseadas em nuvem ainda não estiverem habilitadas, clique em Habilitar APIs baseadas em nuvem .

    Se quiser usar apenas o modelo no dispositivo, você pode pular esta etapa.

Agora você está pronto para rotular imagens usando um modelo no dispositivo ou um modelo baseado em nuvem.

1. Prepare a imagem de entrada

Crie um objeto FirebaseVisionImage a partir da sua imagem. O rotulador de imagem é executado mais rapidamente quando você usa um Bitmap ou, se você usa a API camera2, um media.Image formatado em JPEG, que são recomendados quando possível.

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

2. Configure e execute o rotulador de imagens

Para rotular objetos em uma imagem, passe o objeto FirebaseVisionImage para o método processImage do FirebaseVisionImageLabeler .

  1. Primeiro, obtenha uma instância de FirebaseVisionImageLabeler .

    Se você quiser usar o rotulador de imagens no dispositivo:

    FirebaseVisionImageLabeler labeler = FirebaseVision.getInstance()
       
    .getOnDeviceImageLabeler();

    // Or, to set the minimum confidence required:
    // FirebaseVisionOnDeviceImageLabelerOptions options =
    //     new FirebaseVisionOnDeviceImageLabelerOptions.Builder()
    //         .setConfidenceThreshold(0.7f)
    //         .build();
    // FirebaseVisionImageLabeler labeler = FirebaseVision.getInstance()
    //     .getOnDeviceImageLabeler(options);

    val labeler = FirebaseVision.getInstance().getOnDeviceImageLabeler()

    // Or, to set the minimum confidence required:
    // val options = FirebaseVisionOnDeviceImageLabelerOptions.Builder()
    //     .setConfidenceThreshold(0.7f)
    //     .build()
    // val labeler = FirebaseVision.getInstance().getOnDeviceImageLabeler(options)

    Se você quiser usar o rotulador de imagens em nuvem:

    FirebaseVisionImageLabeler labeler = FirebaseVision.getInstance()
       
    .getCloudImageLabeler();

    // Or, to set the minimum confidence required:
    // FirebaseVisionCloudImageLabelerOptions options =
    //     new FirebaseVisionCloudImageLabelerOptions.Builder()
    //         .setConfidenceThreshold(0.7f)
    //         .build();
    // FirebaseVisionImageLabeler labeler = FirebaseVision.getInstance()
    //     .getCloudImageLabeler(options);

    val labeler = FirebaseVision.getInstance().getCloudImageLabeler()

    // Or, to set the minimum confidence required:
    // val options = FirebaseVisionCloudImageLabelerOptions.Builder()
    //     .setConfidenceThreshold(0.7f)
    //     .build()
    // val labeler = FirebaseVision.getInstance().getCloudImageLabeler(options)

  2. Em seguida, passe a imagem para o método processImage() :

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

3. Obtenha informações sobre objetos rotulados

Se a operação de rotulagem da imagem for bem-sucedida, uma lista de objetos FirebaseVisionImageLabel será transmitida ao listener de sucesso. Cada objeto FirebaseVisionImageLabel representa algo que foi rotulado na imagem. Para cada rótulo, você pode obter a descrição do texto do rótulo, seu ID de entidade do Knowledge Graph (se disponível) e a pontuação de confiança da correspondência. Por exemplo:

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

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

Dicas para melhorar o desempenho em tempo real

Se você deseja rotular imagens em um aplicativo em tempo real, siga estas diretrizes para obter as melhores taxas de quadros:

  • Limite as chamadas para o rotulador de imagens. Se um novo quadro de vídeo ficar disponível enquanto o rotulador de imagens estiver em execução, descarte o quadro.
  • Se você estiver usando a saída do rotulador de imagem 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 .

Próximos passos