Rotular imagens com um modelo treinado pelo AutoML no Android
Depois de treinar seu próprio modelo usando o AutoML Vision Edge, será possível usá-lo no app para rotular imagens. Há duas maneiras de integrar modelos treinados usando o 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 |
|
Testar
- 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 Android do Kit de ML ao arquivo Gradle no nível do app do módulo, que geralmente é
app/build.gradle
:
Para agrupar um modelo e seu 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 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 e o app:1. Extraia o modelo e os metadados dele do arquivo ZIP que você salvou usando o Console do Firebase. Recomendamos usar os arquivos da forma como foram transferidos por download, sem modificações, incluindo os 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 os arquivos do modelo.
c. 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 de modelo será incluído no pacote do app e vai estar disponível 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 opçã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
delas.
Se você tiver apenas um modelo empacotado localmente, basta criar um rotulador usando o objeto AutoMLImageLabelerLocalModel
e configurar o limite de pontuação de confiança que você quer exigir. 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 foi feito
o download dele antes de executá-lo. É possível verificar o status da tarefa de download
do modelo usando o método isModelDownloaded()
do gerenciador de modelos.
Embora isso só precise ser confirmado antes de executar o rotulador, se você tiver um modelo hospedado remotamente e um modelo empacotado localmente, pode fazer sentido realizar essa verificação ao instanciar o rotulador de imagens: crie um rotulador usando o modelo remoto se ele tiver sido transferido por download e, caso contrário, usando o modelo local.
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 relacionado
ao modelo (por exemplo, ocultando ou esmaecendo parte da interface) até
confirmar que 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 Bitmap
ou, se você usa a API Camera2, um YUV_420_888 media.Image
, que são
recomendados quando possível.
Você pode criar um objeto InputImage
de diferentes origens, cada uma explicada abaixo.
Como usar um media.Image
Para criar um objeto InputImage
usando um objeto media.Image
, como quando você captura uma imagem da
câmera de um dispositivo, transmita o objeto media.Image
e a rotação
da imagem para InputImage.fromMediaImage()
.
Se você usar a biblioteca
CameraX, as classes OnImageCapturedListener
e
ImageAnalysis.Analyzer
vão calcular o valor de rotação
automaticamente.
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 ofereça o grau de rotação da imagem, será possível calculá-lo usando o grau de rotação do dispositivo e a orientação do sensor da câmera:
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
valor do grau de rotação para InputImage.fromMediaImage()
:
Kotlin
val image = InputImage.fromMediaImage(mediaImage, rotation)
Java
InputImage image = InputImage.fromMediaImage(mediaImage, rotation);
Como usar um URI de arquivo
Para criar um objeto InputImage
usando o URI de um arquivo, transmita o contexto do app e o URI do arquivo para
InputImage.fromFilePath()
. Isso é útil ao usar
uma intent ACTION_GET_CONTENT
para solicitar que o usuário selecione
uma imagem do app de galeria dele.
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 ByteArray
, primeiro calcule o grau de rotação da imagem, conforme descrito anteriormente para a entrada 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 objeto InputImage
usando 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 método process()
do ImageLabeler
.
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. Conseguir informações sobre os objetos rotulados
Se a operação de rotulagem de imagem for bem-sucedida, uma lista de objetos ImageLabel
será transmitida ao listener de êxito. Cada objeto ImageLabel
representa
algo que foi rotulado na imagem. É possível conferir a descrição de texto
de cada rótulo, 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
Caso você queira rotular imagens em um aplicativo em tempo real, siga estas diretrizes para ter as melhores taxas de frames:
- Se você usa a API
Camera
oucamera2
, limite as chamadas para o rotulador de imagens. Se um novo frame de vídeo ficar disponível enquanto o rotulador de imagens estiver 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 pressão de retorno está definida com o valor padrãoImageAnalysis.STRATEGY_KEEP_ONLY_LATEST
. Isso garante que somente uma imagem seja 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. Depois que a imagem em análise for 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 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 de exibição
apenas uma vez para cada frame de entrada. Consulte as classes
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
.