Rotular imagens com o Kit de ML no Android

É possível usar o Kit de ML para rotular objetos reconhecidos em uma imagem. O modelo padrão fornecido com O kit de ML oferece suporte a mais de 400 rótulos diferentes.

RecursoDesagrupadoAgrupadas
ImplementaçãoO download do modelo é feito dinamicamente pelo Google Play Services.O modelo é vinculado estaticamente ao seu no tempo de build.
Tamanho do appCerca de 200 KB de aumento.Aumento de tamanho de cerca de 5,7 MB.
Tempo de inicializaçãoTalvez seja necessário aguardar o download do modelo para usar o modelo pela primeira vez.O modelo está disponível imediatamente

Faça um teste

Antes de começar

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

  2. Adicione as dependências das bibliotecas do Android do Kit de ML ao arquivo arquivo do Gradle no nível do app, que geralmente é app/build.gradle. Escolha uma destas opções: as seguintes dependências com base nas suas necessidades:

    Para agrupar o modelo e o app:

    dependencies {
      // ...
      // Use this dependency to bundle the model with your app
      implementation 'com.google.mlkit:image-labeling:17.0.9'
    }
    

    Para usar o modelo no Google Play Services:

    dependencies {
      // ...
      // Use this dependency to use the dynamically downloaded model in Google Play Services
      implementation 'com.google.android.gms:play-services-mlkit-image-labeling:16.0.8'
    }
    
  3. Se você optar por usar o modelo no Google Play Services, poderá configurar que o app faça o download automático do modelo para o dispositivo depois que ele for instalado pela Play Store. Para isso, adicione a seguinte declaração ao arquivo arquivo AndroidManifest.xml do seu app:

    <application ...>
          ...
          <meta-data
              android:name="com.google.mlkit.vision.DEPENDENCIES"
              android:value="ica" >
          <!-- To use multiple models: android:value="ica,model2,model3" -->
    </application>
    

    Também é possível verificar explicitamente a disponibilidade do modelo e solicitar o download pelo API ModuleInstallClient do Google Play Services.

    Se você não ativar os downloads do modelo de tempo de instalação ou solicitar o download explícito, o download do modelo é feito na primeira vez que você executa o rotulador. Solicitações feitas por você antes da conclusão do download não produzem resultados.

Agora você já pode rotular imagens.

1. Preparar a imagem de entrada

Crie um objeto InputImage usando sua imagem. O rotulador de imagens é executado mais rapidamente quando você usa um Bitmap ou, se você usar o a API Camera2, uma media.Image YUV_420_888, que é recomendada ao sempre que 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 InputImage objeto de uma ByteBuffer ou ByteArray, primeiro calcule a imagem grau de rotação conforme descrito anteriormente para a entrada media.Image. Depois, crie o objeto InputImage com o buffer ou a matriz, junto com o altura, largura, formato de codificação de cores e grau de rotação:

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.

2. Configurar e executar o rotulador de imagens

Para rotular objetos em uma imagem, transmita o objeto InputImage para o Método process de ImageLabeler.

  1. Primeiro, receba uma instância do ImageLabeler

    Se você quiser usar o rotulador de imagens no dispositivo, faça o seguinte: declaração:

Kotlin

// To use default options:
val labeler = ImageLabeling.getClient(ImageLabelerOptions.DEFAULT_OPTIONS)

// Or, to set the minimum confidence required:
// val options = ImageLabelerOptions.Builder()
//     .setConfidenceThreshold(0.7f)
//     .build()
// val labeler = ImageLabeling.getClient(options)

Java

// To use default options:
ImageLabeler labeler = ImageLabeling.getClient(ImageLabelerOptions.DEFAULT_OPTIONS);

// Or, to set the minimum confidence required:
// ImageLabelerOptions options =
//     new ImageLabelerOptions.Builder()
//         .setConfidenceThreshold(0.7f)
//         .build();
// ImageLabeler labeler = ImageLabeling.getClient(options);
  1. Em seguida, transmita a imagem para o método 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
                // ...
            }
        });

3. Conseguir informações sobre os objetos rotulados

Se a operação de rotulagem de imagem for bem-sucedida, uma lista de Os objetos ImageLabel são transmitidos para o listener de êxito. Cada O objeto ImageLabel representa algo que foi rotulado na imagem. A base oferece suporte a mais de 400 rótulos diferentes. Você pode obter a descrição de texto de cada rótulo, indexar entre todos os rótulos suportados pelo do modelo e da pontuação de confiança 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 VisionProcessorBase no app de amostra do guia de início rápido para conferir um exemplo.
  • Se você usa a API CameraX, verificar se a estratégia de pressão de retorno está definida para 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 enfileiradas entrega. Depois que a imagem analisada é fechada, chamando ImageProxy.close(), a próxima imagem mais recente será entregue.
  • Se você usar a saída do rotulador de imagens para sobrepor elementos gráficos a imagem de entrada, primeiro acesse o resultado do Kit de ML e, em seguida, renderize a imagem e 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 ImageFormat.YUV_420_888. Se você usar a API Camera mais antiga, capture imagens no ImageFormat.NV21.