É possível usar o Kit de ML para reconhecer entidades em uma imagem e rotulá-las. Essa API é compatível com uma ampla variedade de modelos personalizados de classificação de imagens. Consulte Modelos personalizados com Kit de ML para ver orientações sobre requisitos de compatibilidade de modelo, onde encontrar modelos pré-treinados e como treinar seus próprios modelos.
Há duas maneiras de integrar a rotulagem de imagens com modelos personalizados: agrupando o pipeline como parte do app ou usando um pipeline desagrupado que depende do Google Play Services. Se você selecionar o pipeline desagrupado, o app será menor. Veja mais detalhes na tabela abaixo.
Agrupadas | Desagrupado | |
---|---|---|
Nome da biblioteca | com.google.mlkit:image-labeling-custom | com.google.android.gms:play-services-mlkit-image-labeling-custom |
Implementação | O pipeline é vinculado estaticamente ao seu aplicativo no tempo de compilação. | O download do pipeline é feito dinamicamente pelo Google Play Services. |
Tamanho do app | Aumento de cerca de 3,8 MB. | Aumento de aproximadamente 200 KB. |
Tempo de inicialização | O pipeline está disponível imediatamente. | Talvez seja preciso aguardar o download do pipeline para usá-lo pela primeira vez. |
Estágio do ciclo de vida da API | Disponibilidade geral (GA) | Beta |
Há duas maneiras de integrar um modelo personalizado: agrupar o modelo colocando-o na pasta de recursos do app ou fazer o download dele dinamicamente do Firebase. A tabela a seguir compara essas duas opções.
Modelo agrupado | Modelo hospedado |
---|---|
O modelo faz parte do APK do seu app, o que aumenta o tamanho dele. | O modelo não faz parte do APK. Ele é hospedado pelo upload para o Firebase Machine Learning. |
O modelo estará disponível imediatamente, mesmo quando o dispositivo Android estiver off-line | O download do modelo é feito sob demanda |
Não é necessário ter um projeto do Firebase | Requer um projeto do Firebase |
É necessário publicar o app novamente para atualizar o modelo | Enviar atualizações do modelo sem republicar o app |
Nenhum teste A/B integrado | Teste A/B fácil com a Configuração remota do Firebase. |
Faça um teste
- Consulte o app de início rápido do Vision para ver um exemplo de uso do modelo em pacote e o app de início rápido do Automl para um exemplo de uso do modelo hospedado.
Antes de começar
No arquivo
build.gradle
no nível do projeto, inclua o repositório Maven do Google nas seçõesbuildscript
eallprojects
.Adicione as dependências das bibliotecas do Android do Kit de ML ao arquivo Gradle do módulo no nível do app, que geralmente é
app/build.gradle
. Escolha uma das dependências abaixo de acordo com suas necessidades:Para empacotar o pipeline com seu app:
dependencies { // ... // Use this dependency to bundle the pipeline with your app implementation 'com.google.mlkit:image-labeling-custom:17.0.1' }
Para usar o pipeline no Google Play Services:
dependencies { // ... // Use this dependency to use the dynamically downloaded pipeline in Google Play Services implementation 'com.google.android.gms:play-services-mlkit-image-labeling-custom:16.0.0-beta4' }
Se você escolher usar o pipeline no Google Play Services, poderá configurar seu app para fazer o download automático do pipeline no dispositivo após a instalação do app pela Play Store. Para fazer isso, adicione a seguinte declaração ao arquivo
AndroidManifest.xml
do app:<application ...> ... <meta-data android:name="com.google.mlkit.vision.DEPENDENCIES" android:value="custom_ica" /> <!-- To use multiple downloads: android:value="custom_ica,download2,download3" --> </application>
Também é possível conferir explicitamente a disponibilidade do pipeline e solicitar o download pela API ModuleInstallClient do Google Play Services.
Se você não ativar os downloads do pipeline no momento da instalação ou solicitar o download explícito, o pipeline será transferido na primeira vez que você executar o rotulador. As solicitações feitas antes da conclusão do download não geram resultados.
Adicione a dependência
linkFirebase
se quiser fazer o download dinâmico de um modelo do Firebase:Para fazer o download dinâmico de um modelo do Firebase, adicione a dependência
linkFirebase
:dependencies { // ... // Image labeling feature with model downloaded from Firebase implementation 'com.google.mlkit:image-labeling-custom:17.0.1' // Or use the dynamically downloaded pipeline in Google Play Services // implementation 'com.google.android.gms:play-services-mlkit-image-labeling-custom:16.0.0-beta4' implementation 'com.google.mlkit:linkfirebase:17.0.0' }
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 agrupar o modelo.
1. Carregar o modelo
Configurar uma fonte de modelo local
Para agrupar o modelo e o aplicativo, siga estas etapas:
Copie o arquivo de modelo (geralmente terminando em
.tflite
ou.lite
) para a pastaassets/
do app. Talvez seja necessário criar a pasta primeiro clicando com o botão direito do mouse na pastaapp/
e, em seguida, em New > Folder > Assets Folder.Em seguida, adicione o seguinte ao arquivo
build.gradle
do seu app para garantir que o Gradle não compacte o arquivo de modelo ao criar o app:android { // ... aaptOptions { noCompress "tflite" // or noCompress "lite" } }
O arquivo do modelo será incluído no pacote do app e vai estar disponível para o Kit de ML como um recurso bruto.
Crie o objeto
LocalModel
, especificando o caminho para o arquivo de modelo:Kotlin
val localModel = LocalModel.Builder() .setAssetFilePath("model.tflite") // or .setAbsoluteFilePath(absolute file path to model file) // or .setUri(URI to model file) .build()
Java
LocalModel localModel = new LocalModel.Builder() .setAssetFilePath("model.tflite") // or .setAbsoluteFilePath(absolute file path to model file) // or .setUri(URI to model file) .build();
Configurar uma fonte de modelos hospedada no Firebase
Para usar o modelo hospedado remotamente, crie um objeto RemoteModel
até FirebaseModelSource
, especificando o nome que você atribuiu ao modelo quando o publicou:
Kotlin
// Specify the name you assigned in the Firebase console. val remoteModel = CustomRemoteModel .Builder(FirebaseModelSource.Builder("your_model_name").build()) .build()
Java
// Specify the name you assigned in the Firebase console. CustomRemoteModel remoteModel = new CustomRemoteModel .Builder(new FirebaseModelSource.Builder("your_model_name").build()) .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.
Configurar o rotulador de imagens
Depois de configurar as origens do modelo, crie um objeto ImageLabeler
usando
uma delas.
As seguintes opções estão disponíveis:
Opções | |
---|---|
confidenceThreshold
|
Pontuação de confiança mínima dos rótulos detectados. Se não for definido, o limite do classificador especificado pelos metadados do modelo será usado. Se o modelo não contiver metadados ou os metadados não especificarem um limite de classificador, será usado um limite padrão de 0,0. |
maxResultCount
|
Número máximo de rótulos a serem retornados. Se não for definido, o valor padrão de 10 será usado. |
Se você tiver apenas um modelo agrupado localmente, basta criar um rotulador usando o objeto LocalModel
:
Kotlin
val customImageLabelerOptions = CustomImageLabelerOptions.Builder(localModel) .setConfidenceThreshold(0.5f) .setMaxResultCount(5) .build() val labeler = ImageLabeling.getClient(customImageLabelerOptions)
Java
CustomImageLabelerOptions customImageLabelerOptions = new CustomImageLabelerOptions.Builder(localModel) .setConfidenceThreshold(0.5f) .setMaxResultCount(5) .build(); ImageLabeler labeler = ImageLabeling.getClient(customImageLabelerOptions);
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 da execução do rotulador, se você tiver um modelo hospedado remotamente e um modelo empacotado localmente, talvez faça 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. Caso contrário, use o modelo local.
Kotlin
RemoteModelManager.getInstance().isModelDownloaded(remoteModel) .addOnSuccessListener { isDownloaded -> val optionsBuilder = if (isDownloaded) { CustomImageLabelerOptions.Builder(remoteModel) } else { CustomImageLabelerOptions.Builder(localModel) } val options = optionsBuilder .setConfidenceThreshold(0.5f) .setMaxResultCount(5) .build() val labeler = ImageLabeling.getClient(options) }
Java
RemoteModelManager.getInstance().isModelDownloaded(remoteModel) .addOnSuccessListener(new OnSuccessListener() { @Override public void onSuccess(Boolean isDownloaded) { CustomImageLabelerOptions.Builder optionsBuilder; if (isDownloaded) { optionsBuilder = new CustomImageLabelerOptions.Builder(remoteModel); } else { optionsBuilder = new CustomImageLabelerOptions.Builder(localModel); } CustomImageLabelerOptions options = optionsBuilder .setConfidenceThreshold(0.5f) .setMaxResultCount(5) .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 IU) 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 objetoInputImage
a partir da 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.
É possível criar um objeto InputImage
usando diferentes origens, cada uma explicada abaixo.
Como usar um media.Image
Para criar um objeto InputImage
com base em 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
com base em um URI de 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 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 objeto InputImage
com base em 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 objeto image
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. Receber informações sobre entidades rotuladas
Se a operação de rotulagem de imagem for bem-sucedida, uma lista de objetosImageLabel
será transmitida ao listener de êxito. Cada objeto ImageLabel
representa algo que foi rotulado na imagem. Você pode ver a descrição de texto de cada rótulo (se disponível nos metadados do arquivo de modelo do TensorFlow Lite), a pontuação de confiança e o índice. 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 diretrizes para ter os melhores frame rates:
- Se você usar a API
Camera
oucamera2
, o limitar as chamadas para o rotulador de imagens. Se um novo frame de vídeo for disponibilizado 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 ver um exemplo. - Se você usar a API
CameraX
, verifique se a estratégia de pressão está definida para o valor padrãoImageAnalysis.STRATEGY_KEEP_ONLY_LATEST
. Isso garante que apenas uma imagem seja enviada por vez para análise. Se mais imagens forem produzidas quando o analisador estiver ocupado, elas serão descartadas automaticamente e não ficarão na fila para entrega. Quando a imagem que está sendo analisada é fechada chamando ImageProxy.close(), a próxima imagem mais recente é enviada. - 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. Ela será renderizada para a 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 ver 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
.