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 |
|
Hospedado com o Firebase |
|
Faça um teste
- Teste o app de exemplo para conferir um exemplo de uso dessa API.
Antes de começar
1. No arquivobuild.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 objetoimage
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 APIcamera2
, 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 classeVisionProcessorBase
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ãoImageAnalysis.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
eGraphicOverlay
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 formatoImageFormat.NV21
.