Depois de treinar seu próprio modelo usando o AutoML Vision Edge , você poderá usá-lo em seu aplicativo para rotular imagens.
Antes de você começar
- Adicione o Firebase ao seu projeto Android , caso ainda não o tenha feito.
- 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-automl:18.0.5' }
1. Carregue o modelo
O ML Kit executa seus modelos gerados pelo AutoML no dispositivo. No entanto, você pode configurar o kit de ML para carregar seu modelo remotamente do Firebase, do armazenamento local ou de ambos.
Ao hospedar o modelo no Firebase, você pode atualizá-lo sem lançar uma nova versão do aplicativo e pode usar a Configuração remota e o teste A/B para veicular dinamicamente diferentes modelos para diferentes conjuntos de usuários.
Se você optar por fornecer o modelo apenas hospedando-o no Firebase e não agrupá-lo ao seu aplicativo, poderá reduzir o tamanho inicial do download do seu aplicativo. Tenha em mente, porém, que se o modelo não estiver incluído no seu aplicativo, qualquer funcionalidade relacionada ao modelo não estará disponível até que seu aplicativo baixe o modelo pela primeira vez.
Ao agrupar seu modelo com seu aplicativo, você pode garantir que os recursos de ML do seu aplicativo ainda funcionem quando o modelo hospedado pelo Firebase não estiver disponível.
Configurar uma origem de modelo hospedado no Firebase
Para usar o modelo hospedado remotamente, crie um objeto FirebaseAutoMLRemoteModel
especificando o nome que você atribuiu ao modelo quando o publicou:
Java
// Specify the name you assigned in the Firebase console.
FirebaseAutoMLRemoteModel remoteModel =
new FirebaseAutoMLRemoteModel.Builder("your_remote_model").build();
Kotlin+KTX
// Specify the name you assigned in the Firebase console.
val remoteModel = FirebaseAutoMLRemoteModel.Builder("your_remote_model").build()
Em seguida, inicie a tarefa de download do modelo, especificando as condições sob as quais deseja 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 assíncrono do modelo do Firebase:
Java
FirebaseModelDownloadConditions conditions = new FirebaseModelDownloadConditions.Builder()
.requireWifi()
.build();
FirebaseModelManager.getInstance().download(remoteModel, conditions)
.addOnCompleteListener(new OnCompleteListener<Void>() {
@Override
public void onComplete(@NonNull Task<Void> task) {
// Success.
}
});
Kotlin+KTX
val conditions = FirebaseModelDownloadConditions.Builder()
.requireWifi()
.build()
FirebaseModelManager.getInstance().download(remoteModel, conditions)
.addOnCompleteListener {
// Success.
}
Muitos aplicativos iniciam a tarefa de download em seu código de inicialização, mas você pode fazer isso a qualquer momento antes de usar o modelo.
Configurar uma origem de modelo local
Para agrupar o modelo com seu aplicativo:
- Extraia o modelo e seus metadados do arquivo zip baixado do console do Firebase. Recomendamos que você use os arquivos conforme os baixou, sem modificação (incluindo os nomes dos arquivos).
Inclua seu modelo e seus arquivos de metadados no pacote do seu aplicativo:
- Se você não tiver uma pasta de ativos em seu projeto, crie uma clicando com o botão direito na pasta
app/
e clicando em Novo > Pasta > Pasta de ativos . - Crie uma subpasta na pasta de ativos para conter os arquivos de modelo.
- Copie os arquivos
model.tflite
,dict.txt
emanifest.json
para a subpasta (todos os três arquivos devem estar na mesma pasta).
- Se você não tiver uma pasta de ativos em seu projeto, crie uma clicando com o botão direito na pasta
- Adicione o seguinte ao arquivo
build.gradle
do seu aplicativo para garantir que o Gradle não compacte o arquivo de modelo ao criar o aplicativo:android { // ... aaptOptions { noCompress "tflite" } }
O arquivo de modelo será incluído no pacote do aplicativo e disponível para o Kit de ML como um ativo bruto. - Crie um objeto
FirebaseAutoMLLocalModel
especificando o caminho para o arquivo de manifesto do modelo:Java
FirebaseAutoMLLocalModel localModel = new FirebaseAutoMLLocalModel.Builder() .setAssetFilePath("manifest.json") .build();
Kotlin+KTX
val localModel = FirebaseAutoMLLocalModel.Builder() .setAssetFilePath("manifest.json") .build()
Crie um rotulador de imagem a partir do seu modelo
Depois de configurar as fontes do modelo, crie um objeto FirebaseVisionImageLabeler
a partir de uma delas.
Se você tiver apenas um modelo agrupado localmente, basta criar um rotulador a partir do objeto FirebaseAutoMLLocalModel
e configurar o limite de pontuação de confiança que deseja exigir (consulte Avaliar seu modelo ):
Java
FirebaseVisionImageLabeler labeler;
try {
FirebaseVisionOnDeviceAutoMLImageLabelerOptions options =
new FirebaseVisionOnDeviceAutoMLImageLabelerOptions.Builder(localModel)
.setConfidenceThreshold(0.0f) // Evaluate your model in the Firebase console
// to determine an appropriate value.
.build();
labeler = FirebaseVision.getInstance().getOnDeviceAutoMLImageLabeler(options);
} catch (FirebaseMLException e) {
// ...
}
Kotlin+KTX
val options = FirebaseVisionOnDeviceAutoMLImageLabelerOptions.Builder(localModel)
.setConfidenceThreshold(0) // Evaluate your model in the Firebase console
// to determine an appropriate value.
.build()
val labeler = FirebaseVision.getInstance().getOnDeviceAutoMLImageLabeler(options)
Se você tiver um modelo hospedado remotamente, deverá verificar se ele foi baixado antes de executá-lo. Você pode verificar o status da tarefa de download do modelo usando o método isModelDownloaded()
do gerenciador de modelo.
Embora você só precise confirmar isso antes de executar o rotulador, se você tiver um modelo hospedado remotamente e um modelo empacotado localmente, pode fazer sentido realizar esta verificação ao instanciar o rotulador de imagem: crie um rotulador a partir do modelo remoto se ele foi baixado e do modelo local, caso contrário.
Java
FirebaseModelManager.getInstance().isModelDownloaded(remoteModel)
.addOnSuccessListener(new OnSuccessListener<Boolean>() {
@Override
public void onSuccess(Boolean isDownloaded) {
FirebaseVisionOnDeviceAutoMLImageLabelerOptions.Builder optionsBuilder;
if (isDownloaded) {
optionsBuilder = new FirebaseVisionOnDeviceAutoMLImageLabelerOptions.Builder(remoteModel);
} else {
optionsBuilder = new FirebaseVisionOnDeviceAutoMLImageLabelerOptions.Builder(localModel);
}
FirebaseVisionOnDeviceAutoMLImageLabelerOptions options = optionsBuilder
.setConfidenceThreshold(0.0f) // Evaluate your model in the Firebase console
// to determine an appropriate threshold.
.build();
FirebaseVisionImageLabeler labeler;
try {
labeler = FirebaseVision.getInstance().getOnDeviceAutoMLImageLabeler(options);
} catch (FirebaseMLException e) {
// Error.
}
}
});
Kotlin+KTX
FirebaseModelManager.getInstance().isModelDownloaded(remoteModel)
.addOnSuccessListener { isDownloaded ->
val optionsBuilder =
if (isDownloaded) {
FirebaseVisionOnDeviceAutoMLImageLabelerOptions.Builder(remoteModel)
} else {
FirebaseVisionOnDeviceAutoMLImageLabelerOptions.Builder(localModel)
}
// Evaluate your model in the Firebase console to determine an appropriate threshold.
val options = optionsBuilder.setConfidenceThreshold(0.0f).build()
val labeler = FirebaseVision.getInstance().getOnDeviceAutoMLImageLabeler(options)
}
Se você tiver apenas um modelo hospedado remotamente, desative a funcionalidade relacionada ao modelo (por exemplo, esmaecer ou ocultar parte da interface do usuário) até confirmar que o download do modelo foi feito. Você pode fazer isso anexando um ouvinte ao método download()
do gerenciador de modelo:
Java
FirebaseModelManager.getInstance().download(remoteModel, conditions)
.addOnSuccessListener(new OnSuccessListener<Void>() {
@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.
}
});
Kotlin+KTX
FirebaseModelManager.getInstance().download(remoteModel, conditions)
.addOnCompleteListener {
// Download complete. Depending on your app, you could enable the ML
// feature, or switch from the local model to the remote model, etc.
}
2. Prepare a imagem de entrada
Em seguida, para cada imagem que você deseja rotular, crie um objeto FirebaseVisionImage
usando uma das opções descritas nesta seção e passe-o para uma instância de FirebaseVisionImageLabeler
(descrita na próxima seção).
Você pode criar um objeto FirebaseVisionImage
a partir de um objeto media.Image
, um arquivo no dispositivo, uma matriz de bytes ou um objeto Bitmap
:
Para criar um objeto
FirebaseVisionImage
a partir de um objetomedia.Image
, como ao capturar uma imagem da câmera de um dispositivo, passe o objetomedia.Image
e a rotação da imagem paraFirebaseVisionImage.fromMediaImage()
.Se você usar a biblioteca CameraX , as classes
OnImageCapturedListener
eImageAnalysis.Analyzer
calcularão o valor de rotação para você, então você só precisa converter a rotação em uma das constantesROTATION_
do kit de ML antes de chamarFirebaseVisionImage.fromMediaImage()
:Java
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 // ... } }
Kotlin+KTX
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:
Java
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; }
Kotlin+KTX
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 paraFirebaseVisionImage.fromMediaImage()
:Java
FirebaseVisionImage image = FirebaseVisionImage.fromMediaImage(mediaImage, rotation);
Kotlin+KTX
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 paraFirebaseVisionImage.fromFilePath()
. Isso é útil quando você usa uma intentACTION_GET_CONTENT
para solicitar que o usuário selecione uma imagem do aplicativo de galeria.Java
FirebaseVisionImage image; try { image = FirebaseVisionImage.fromFilePath(context, uri); } catch (IOException e) { e.printStackTrace(); }
Kotlin+KTX
val image: FirebaseVisionImage try { image = FirebaseVisionImage.fromFilePath(context, uri) } catch (e: IOException) { e.printStackTrace() }
- Para criar um objeto
FirebaseVisionImage
a partir de umByteBuffer
ou de uma matriz de bytes, primeiro calcule a rotação da imagem conforme descrito acima para a entradamedia.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:Java
FirebaseVisionImageMetadata metadata = new FirebaseVisionImageMetadata.Builder() .setWidth(480) // 480x360 is typically sufficient for .setHeight(360) // image recognition .setFormat(FirebaseVisionImageMetadata.IMAGE_FORMAT_NV21) .setRotation(rotation) .build();
Kotlin+KTX
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
:Java
FirebaseVisionImage image = FirebaseVisionImage.fromByteBuffer(buffer, metadata); // Or: FirebaseVisionImage image = FirebaseVisionImage.fromByteArray(byteArray, metadata);
Kotlin+KTX
val image = FirebaseVisionImage.fromByteBuffer(buffer, metadata) // Or: val image = FirebaseVisionImage.fromByteArray(byteArray, metadata)
- Para criar um objeto
FirebaseVisionImage
a partir de um objetoBitmap
:A imagem representada pelo objetoJava
FirebaseVisionImage image = FirebaseVisionImage.fromBitmap(bitmap);
Kotlin+KTX
val image = FirebaseVisionImage.fromBitmap(bitmap)
Bitmap
deve estar na vertical, sem necessidade de rotação adicional.
3. Execute o rotulador de imagens
Para rotular objetos em uma imagem, passe o objeto FirebaseVisionImage
para o método processImage()
do FirebaseVisionImageLabeler
.
Java
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
// ...
}
});
Kotlin+KTX
labeler.processImage(image)
.addOnSuccessListener { labels ->
// Task completed successfully
// ...
}
.addOnFailureListener { e ->
// Task failed with an exception
// ...
}
Se a rotulagem da imagem for bem-sucedida, uma matriz de objetos FirebaseVisionImageLabel
será transmitida ao ouvinte de sucesso. De cada objeto você pode obter informações sobre uma característica reconhecida na imagem.
Por exemplo:
Java
for (FirebaseVisionImageLabel label: labels) {
String text = label.getText();
float confidence = label.getConfidence();
}
Kotlin+KTX
for (label in labels) {
val text = label.text
val confidence = label.confidence
}
Dicas para melhorar o desempenho em tempo real
- Limite as chamadas para o detector. Se um novo quadro de vídeo ficar disponível enquanto o detector estiver em execução, elimine o quadro.
- Se você estiver usando a saída do detector 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
.