Depois de treinar seu próprio modelo usando o AutoML Vision Edge , você poderá usá-lo em seu aplicativo para rotular imagens.
Há duas maneiras de integrar modelos treinados no AutoML Vision Edge: você pode agrupar o modelo colocando-o na pasta de ativos do seu aplicativo ou baixá-lo dinamicamente do Firebase.
Opções de agrupamento de modelos | |
---|---|
Incluído em seu aplicativo |
|
Hospedado com Firebase |
|
Antes de você começar
Adicione as dependências das bibliotecas Android do ML Kit ao arquivo gradle no nível do aplicativo do seu módulo, que geralmente é
app/build.gradle
:Para agrupar um modelo com seu aplicativo:
dependencies {
// ...
// Image labeling feature with bundled automl model
implementation 'com.google.mlkit:image-labeling-custom:16.3.1'
}Para baixar dinamicamente 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-custom:16.3.1'
implementation 'com.google.mlkit:linkfirebase:16.1.0'
}Se você deseja baixar um modelo , certifique-se de adicionar o Firebase ao seu projeto Android , caso ainda não tenha feito isso. Isso não é necessário quando você agrupa o modelo.
1. Carregue 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 ML Kit como um ativo bruto.
Crie o objeto
LocalModel
, especificando o caminho para o arquivo de manifesto do modelo:AutoMLImageLabelerLocalModel localModel =
new AutoMLImageLabelerLocalModel.Builder()
.setAssetFilePath("manifest.json")
// or .setAbsoluteFilePath(absolute file path to manifest file)
.build();val localModel = LocalModel.Builder()
.setAssetManifestFilePath("manifest.json")
// or .setAbsoluteManifestFilePath(absolute file path to manifest file)
.build()
Configurar uma origem de modelo hospedado no Firebase
Para usar o modelo hospedado remotamente, crie um objeto CustomRemoteModel
especificando o nome que você atribuiu ao modelo quando o publicou:
// Specify the name you assigned in the Firebase console.
FirebaseModelSource firebaseModelSource =
new FirebaseModelSource.Builder("your_model_name").build();
CustomRemoteModel remoteModel =
new CustomRemoteModel.Builder(firebaseModelSource).build();
// Specify the name you assigned in the Firebase console.
val firebaseModelSource = FirebaseModelSource.Builder("your_model_name")
.build()
val remoteModel = CustomRemoteModel.Builder(firebaseModelSource).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:
DownloadConditions downloadConditions = new DownloadConditions.Builder()
.requireWifi()
.build();
RemoteModelManager.getInstance().download(remoteModel, downloadConditions)
.addOnSuccessListener(new OnSuccessListener<Void>() {
@Override
public void onSuccess(@NonNull Task<Void> task) {
// Success.
}
});
val downloadConditions = DownloadConditions.Builder()
.requireWifi()
.build()
RemoteModelManager.getInstance().download(remoteModel, downloadConditions)
.addOnSuccessListener {
// 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.
Crie um rotulador de imagem a partir do seu modelo
Depois de configurar as fontes do modelo, crie um objeto ImageLabeler
a partir de uma delas.
Se você tiver apenas um modelo empacotado localmente, basta criar um rotulador a partir do seu objeto CustomImageLabelerOptions
e configurar o limite de pontuação de confiança que deseja exigir (consulte Avaliar seu modelo ):
CustomImageLabelerOptions customImageLabelerOptions = new CustomImageLabelerOptions.Builder(localModel)
.setConfidenceThreshold(0.0f) // Evaluate your model in the Cloud console
// to determine an appropriate value.
.build();
ImageLabeler labeler = ImageLabeling.getClient(customImageLabelerOptions);
val customImageLabelerOptions = CustomImageLabelerOptions.Builder(localModel)
.setConfidenceThreshold(0.0f) // Evaluate your model in the Cloud console
// to determine an appropriate value.
.build()
val labeler = ImageLabeling.getClient(customImageLabelerOptions)
Se você tiver um modelo hospedado remotamente, precisará 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, caso contrário, do modelo local.
RemoteModelManager.getInstance().isModelDownloaded(remoteModel)
.addOnSuccessListener(new OnSuccessListener<Boolean>() {
@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.0f) // Evaluate your model in the Cloud console
// to determine an appropriate threshold.
.build();
ImageLabeler labeler = ImageLabeling.getClient(options);
}
});
RemoteModelManager.getInstance().isModelDownloaded(remoteModel)
.addOnSuccessListener { isDownloaded ->
val optionsBuilder =
if (isDownloaded) {
CustomImageLabelerOptions.Builder(remoteModel)
} else {
CustomImageLabelerOptions.Builder(localModel)
}
// Evaluate your model in the Cloud console to determine an appropriate threshold.
val options = optionsBuilder.setConfidenceThreshold(0.0f).build()
val labeler = ImageLabeling.getClient(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:
RemoteModelManager.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.
}
});
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.
}
2. Prepare a imagem de entrada
Então, para cada imagem que você deseja rotular, crie um objeto InputImage
a partir da sua imagem. O rotulador de imagem é executado mais rapidamente quando você usa um 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 InputImage
de diferentes fontes, cada uma delas explicada abaixo.
Usando uma media.Image
Para criar um objeto InputImage
a partir de um objeto media.Image
, como quando você captura uma imagem da câmera de um dispositivo, passe 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
calcularão o valor de rotação para você.
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
// ...
}
}
}
private class YourAnalyzer implements ImageAnalysis.Analyzer {
@Override
public void analyze(ImageProxy imageProxy) {
if (imageProxy == null || imageProxy.getImage() == null) {
return;
}
Image mediaImage = imageProxy.getImage();
InputImage image =
InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees);
// Pass image to an ML Kit Vision API
// ...
}
}
Se você não usar uma biblioteca de câmeras que forneça o grau de rotação da imagem, poderá calculá-lo a partir do grau de rotação do dispositivo e da orientação do sensor da câmera no dispositivo:
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
}
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;
}
Em seguida, passe o objeto media.Image
e o valor do grau de rotação para InputImage.fromMediaImage()
:
val image = InputImage.fromMediaImage(mediaImage, rotation)
InputImage image = InputImage.fromMediaImage(mediaImage, rotation);
Usando um URI de arquivo
Para criar um objeto InputImage
a partir de um URI de arquivo, passe o contexto do aplicativo e o URI do arquivo para InputImage.fromFilePath()
. Isso é útil quando você usa uma intent ACTION_GET_CONTENT
para solicitar que o usuário selecione uma imagem do aplicativo de galeria.
val image: InputImage
try {
image = InputImage.fromFilePath(context, uri)
} catch (e: IOException) {
e.printStackTrace()
}
InputImage image;
try {
image = InputImage.fromFilePath(context, uri);
} catch (IOException e) {
e.printStackTrace();
}
Usando um ByteBuffer
ou ByteArray
Para criar um objeto InputImage
a partir de 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 array, juntamente com a altura, largura, formato de codificação de cores e grau de rotação da imagem:
val image = InputImage.fromByteBuffer(
byteBuffer,
/* image width */ 480,
/* image height */ 360,
rotationDegrees,
InputImage.IMAGE_FORMAT_NV21 // or IMAGE_FORMAT_YV12
)
InputImage image = InputImage.fromByteBuffer(byteBuffer,
/* image width */ 480,
/* image height */ 360,
rotationDegrees,
InputImage.IMAGE_FORMAT_NV21 // or IMAGE_FORMAT_YV12
);
Usando um Bitmap
Para criar um objeto InputImage
a partir de um objeto Bitmap
, faça a seguinte declaração:
val image = InputImage.fromBitmap(bitmap, 0)
InputImage image = InputImage.fromBitmap(bitmap, rotationDegree);
A imagem é representada por um objeto Bitmap
juntamente com graus de rotação.
3. Execute o rotulador de imagens
Para rotular objetos em uma imagem, passe o objeto image
para o método process()
do ImageLabeler
.
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
// ...
}
});
labeler.process(image)
.addOnSuccessListener { labels ->
// Task completed successfully
// ...
}
.addOnFailureListener { e ->
// Task failed with an exception
// ...
}
4. Obtenha informações sobre objetos rotulados
Se a operação de rotulagem de imagem for bem-sucedida, uma lista de objetos ImageLabel
será passada para o ouvinte de sucesso. Cada objeto ImageLabel
representa algo que foi rotulado na imagem. Você pode obter a descrição de texto de cada rótulo, a pontuação de confiança da correspondência e o índice da correspondência. Por exemplo:
for (ImageLabel label : labels) {
String text = label.getText();
float confidence = label.getConfidence();
int index = label.getIndex();
}
for (label in labels) {
val text = label.text
val confidence = label.confidence
val index = label.index
}
Dicas para melhorar o desempenho em tempo real
Se você deseja rotular imagens em um aplicativo em tempo real, siga estas diretrizes para obter as melhores taxas de quadros:
- Limite as chamadas para o rotulador de imagens. Se um novo quadro de vídeo ficar disponível enquanto o rotulador de imagens estiver em execução, descarte o quadro. Consulte a classe
VisionProcessorBase
no aplicativo de exemplo de início rápido para ver um exemplo. - Se você estiver usando a saída do rotulador de imagens para sobrepor gráficos na imagem de entrada, primeiro obtenha o resultado e, em seguida, renderize a imagem e sobreponha em uma única etapa. Ao fazer isso, você renderiza na superfície de exibição apenas uma vez para cada quadro de entrada. Consulte as classes
CameraSourcePreview
eGraphicOverlay
no aplicativo de exemplo 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 formato
ImageFormat.NV21
.