Quando você transmite uma imagem para o Kit de ML, ele detecta até cinco objetos nela junto com a posição de cada um. Ao detectar objetos em streams de vídeo, cada um tem um ID exclusivo que pode ser usado para rastrear o objeto de frame a frame.
É possível usar um modelo personalizado de classificação de imagens para classificar os objetos detectados. Consulte Modelos personalizados com o Kit de ML para orientações sobre requisitos de compatibilidade de modelos, onde encontrar modelos pré-treinados e como treinar seus próprios modelos.
Há duas maneiras de integrar um modelo personalizado. Você pode empacotar o modelo colocando-o na pasta de recursos do app ou fazer o download dele dinamicamente do Cloud Storage. A tabela a seguir compara as 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 seu APK. Ele é hospedado por upload no Cloud Storage. Recomendamos usar o Cloud Storage para Firebase. |
| O modelo estará disponível imediatamente, mesmo quando o dispositivo Android estiver off-line | O app precisa incluir código para fazer o download do modelo sob demanda. |
| Não é necessário ter um projeto do Firebase | Requer um projeto do Firebase (se você estiver usando o Cloud Storage para Firebase). |
| Você precisa republicar o app para atualizar o modelo | Enviar atualizações do modelo sem republicar o app |
| Sem teste A/B integrado | Teste A/B com a Configuração remota do Firebase |
Faça um teste
- Consulte o app de início rápido do Vision para um exemplo de uso do modelo agrupado e o app de início rápido do AutoML para um exemplo de uso do modelo hospedado.
- Consulte o app de demonstração do Material Design para uma implementação completa dessa API.
Antes de começar
1. No arquivobuild.gradle.kts no nível do projeto, inclua o repositório Maven do Google nas seções buildscript e allprojects.
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.kts:Para empacotar um modelo e seu app:
dependencies { // ... // Object detection & tracking feature with custom bundled model implementation("com.google.mlkit:object-detection-custom:17.0.2") }Se você quiser fazer o download de um modelo do Cloud Storage para Firebase, adicione o Firebase ao seu projeto do Android, caso ainda não tenha feito isso. Isso não é necessário se o modelo for empacotado.
1. Carregar o modelo
É possível carregar o modelo de uma fonte agrupada localmente ou de uma fonte hospedada remotamente.
Configurar uma fonte de modelo local
Para agrupar o modelo e o app:
Copie o arquivo modelo (geralmente terminado em
.tfliteou.lite) para a pastaassets/do seu app. Talvez seja necessário criar a pasta primeiro clicando com o botão direito do mouse na pastaapp/e depois em Novo > Pasta > Pasta de recursos.Crie um objeto
LocalModel, especificando o caminho para o arquivo modelo:Kotlin
val localModel = LocalModel.Builder() .setAssetFilePath("model.tflite") // or .setAbsoluteFilePath(absolute path to model file) // or .setUri(URI to model file) .build()
Java
LocalModel localModel = new LocalModel.Builder() .setAssetFilePath("model.tflite") // or .setAbsoluteFilePath(absolute path to model file) // or .setUri(URI to model file) .build();
Configurar uma fonte de modelo hospedada remotamente
Para usar o modelo hospedado remotamente, faça o download do arquivo modelo para o armazenamento local do dispositivo usando sua própria lógica de app e carregue-o como um modelo local. Recomendamos usar o Cloud Storage para Firebase para hospedar um modelo. Para detalhes da implementação, consulte o guia de migração do Firebase ML para o Cloud Storage.
2. Configurar o detector de objetos
Depois de configurar as origens do modelo, configure o detector de objetos para seu
caso de uso com um objeto CustomObjectDetectorOptions. É possível mudar as
seguintes configurações:
| Configurações do detector de objetos | |
|---|---|
| Modo de detecção |
STREAM_MODE (padrão) | SINGLE_IMAGE_MODE
No No |
| Detectar e rastrear vários objetos |
false (padrão) | true
Se for preciso detectar e rastrear até cinco objetos ou apenas o objeto mais proeminente (padrão). |
| Classificar objetos |
false (padrão) | true
Se é preciso ou não classificar os objetos detectados usando o modelo de classificador personalizado fornecido. Para usar seu modelo de classificação personalizada, defina isso como |
| Limite de confiança de classificação |
Pontuação mínima de confiança dos rótulos detectados. Se não for definido, qualquer limite de classificação especificado pelos metadados do modelo será usado. Se o modelo não tiver metadados ou se eles não especificarem um limite de classificação, será usado um limite padrão de 0,0. |
| Número máximo de rótulos por objeto |
Número máximo de rótulos por objeto que o detector vai retornar. Se não for definido, o valor padrão de 10 será usado. |
A API de detecção de objeto e rastreamento de objetos é otimizada para estes dois casos de uso principais:
- Detecção ao vivo e rastreamento do objeto mais proeminente no visor da câmera.
- A detecção de vários objetos em uma imagem estática.
Para configurar a API para esses casos de uso com um modelo agrupado localmente:
Kotlin
// Live detection and tracking val customObjectDetectorOptions = CustomObjectDetectorOptions.Builder(localModel) .setDetectorMode(CustomObjectDetectorOptions.STREAM_MODE) .enableClassification() .setClassificationConfidenceThreshold(0.5f) .setMaxPerObjectLabelCount(3) .build() // Multiple object detection in static images val customObjectDetectorOptions = CustomObjectDetectorOptions.Builder(localModel) .setDetectorMode(CustomObjectDetectorOptions.SINGLE_IMAGE_MODE) .enableMultipleObjects() .enableClassification() .setClassificationConfidenceThreshold(0.5f) .setMaxPerObjectLabelCount(3) .build() val objectDetector = ObjectDetection.getClient(customObjectDetectorOptions)
Java
// Live detection and tracking CustomObjectDetectorOptions customObjectDetectorOptions = new CustomObjectDetectorOptions.Builder(localModel) .setDetectorMode(CustomObjectDetectorOptions.STREAM_MODE) .enableClassification() .setClassificationConfidenceThreshold(0.5f) .setMaxPerObjectLabelCount(3) .build(); // Multiple object detection in static images CustomObjectDetectorOptions customObjectDetectorOptions = new CustomObjectDetectorOptions.Builder(localModel) .setDetectorMode(CustomObjectDetectorOptions.SINGLE_IMAGE_MODE) .enableMultipleObjects() .enableClassification() .setClassificationConfidenceThreshold(0.5f) .setMaxPerObjectLabelCount(3) .build(); ObjectDetector objectDetector = ObjectDetection.getClient(customObjectDetectorOptions);
Se você tiver um modelo hospedado remotamente, será necessário verificar se foi feito o download dele antes de executá-lo.
Embora você só precise confirmar isso antes de executar o detector, se tiver um modelo hospedado remotamente e um agrupado localmente, talvez seja interessante realizar essa verificação ao instanciar o detector de imagens: crie um detector do modelo remoto se ele tiver sido baixado e do modelo local caso contrário.
Kotlin
val modelFile = File(context.cacheDir, "my_remote_model.tflite") val model = if (modelFile.exists()) { // Use the downloaded model if available LocalModel.Builder().setAbsoluteFilePath(modelFile.absolutePath).build() } else { // Fall back to the bundled model LocalModel.Builder().setAssetFilePath("model.tflite").build() } val customObjectDetectorOptions = CustomObjectDetectorOptions.Builder(model) .setDetectorMode(CustomObjectDetectorOptions.SINGLE_IMAGE_MODE) .enableClassification() .setClassificationConfidenceThreshold(0.5f) .setMaxPerObjectLabelCount(3) .build() val objectDetector = ObjectDetection.getClient(customObjectDetectorOptions)
Java
File modelFile = new File(context.getCacheDir(), "my_remote_model.tflite"); LocalModel model; if (modelFile.exists()) { // Use the downloaded model if available model = new LocalModel.Builder().setAbsoluteFilePath(modelFile.getAbsolutePath()).build(); } else { // Fall back to the bundled model model = new LocalModel.Builder().setAssetFilePath("model.tflite").build(); } CustomObjectDetectorOptions customObjectDetectorOptions = new CustomObjectDetectorOptions.Builder(model) .setDetectorMode(CustomObjectDetectorOptions.SINGLE_IMAGE_MODE) .enableClassification() .setClassificationConfidenceThreshold(0.5f) .setMaxPerObjectLabelCount(3) .build(); ObjectDetector objectDetector = ObjectDetection.getClient(customObjectDetectorOptions);
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.
Kotlin
val localFile = File(context.cacheDir, "my_remote_model.tflite") if (localFile.exists()) { // Model is already cached, initialize immediately initializeDetector(localFile) } else { // Model is not yet available, show loading UI and start download showLoadingUI() val storage = Firebase.storage val modelRef = storage.getReferenceFromUrl("gs://YOUR_BUCKET/path/to/model.tflite") modelRef.getFile(localFile) .addOnSuccessListener { // Download complete, initialize the detector hideLoadingUI() initializeDetector(localFile) } .addOnFailureListener { // Handle download error showErrorUI() } } private fun initializeDetector(modelFile: File) { val localModel = LocalModel.Builder().setAbsoluteFilePath(modelFile.absolutePath).build() val customObjectDetectorOptions = CustomObjectDetectorOptions.Builder(localModel) .setDetectorMode(CustomObjectDetectorOptions.SINGLE_IMAGE_MODE) .enableClassification() .build() val objectDetector = ObjectDetection.getClient(customObjectDetectorOptions) // Enable ML-related UI features here enableMLFeatures(objectDetector) }
Java
File localFile = new File(context.getCacheDir(), "my_remote_model.tflite"); if (localFile.exists()) { // Model is already cached, initialize immediately initializeDetector(localFile); } else { // Model is not yet available, show loading UI and start download showLoadingUI(); FirebaseStorage storage = FirebaseStorage.getInstance(); StorageReference modelRef = storage.getReferenceFromUrl("gs://YOUR_BUCKET/path/to/model.tflite"); modelRef.getFile(localFile) .addOnSuccessListener(new OnSuccessListener<FileDownloadTask.TaskSnapshot>() { @Override public void onSuccess(FileDownloadTask.TaskSnapshot taskSnapshot) { // Download complete, initialize the detector hideLoadingUI(); initializeDetector(localFile); } }) .addOnFailureListener(new OnFailureListener() { @Override public void onFailure(@NonNull Exception exception) { // Handle download error showErrorUI(); } }); } private void initializeDetector(File modelFile) { LocalModel localModel = new LocalModel.Builder().setAbsoluteFilePath(modelFile.getAbsolutePath()).build(); CustomObjectDetectorOptions customObjectDetectorOptions = new CustomObjectDetectorOptions.Builder(localModel) .setDetectorMode(CustomObjectDetectorOptions.SINGLE_IMAGE_MODE) .enableClassification() .build(); ObjectDetector objectDetector = ObjectDetection.getClient(customObjectDetectorOptions); // Enable ML-related UI features here enableMLFeatures(objectDetector); }
3. Preparar a imagem de entrada
Crie um objetoInputImage com base na sua imagem.
O detector de objetos é executado diretamente de um Bitmap, NV21 ByteBuffer ou um
YUV_420_888 media.Image. É recomendável construir um InputImage dessas fontes se você tiver acesso direto a uma delas. Se você criar um
InputImage de outras fontes, vamos processar a conversão internamente para
você, e isso pode ser menos eficiente.
É possível criar um objeto InputImage
de diferentes fontes, cada uma explicada abaixo.
Como usar um media.Image
Para criar um objeto InputImage
usando um objeto media.Image, como ao capturar 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
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 ofereça o grau de rotação da imagem, será possível calcular 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 no 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.
4. Executar o detector de objetos
Kotlin
objectDetector .process(image) .addOnFailureListener(e -> {...}) .addOnSuccessListener(results -> { for (detectedObject in results) { // ... } });
Java
objectDetector .process(image) .addOnFailureListener(e -> {...}) .addOnSuccessListener(results -> { for (DetectedObject detectedObject : results) { // ... } });
5. Receber informações sobre objetos rotulados
Se a chamada para process() for bem-sucedida, uma lista de DetectedObjects será transmitida para o listener de êxito.
Cada DetectedObject contém as seguintes propriedades:
| Caixa delimitadora | Um Rect que indica a posição do objeto na
imagem. |
||||||
| ID de acompanhamento | Um número inteiro que identifica o objeto nas imagens. Nulo em SINGLE_IMAGE_MODE. | ||||||
| Rótulos |
|
Kotlin
// The list of detected objects contains one item if multiple // object detection wasn't enabled. for (detectedObject in results) { val boundingBox = detectedObject.boundingBox val trackingId = detectedObject.trackingId for (label in detectedObject.labels) { val text = label.text val index = label.index val confidence = label.confidence } }
Java
// The list of detected objects contains one item if multiple // object detection wasn't enabled. for (DetectedObject detectedObject : results) { Rect boundingBox = detectedObject.getBoundingBox(); Integer trackingId = detectedObject.getTrackingId(); for (Label label : detectedObject.getLabels()) { String text = label.getText(); int index = label.getIndex(); float confidence = label.getConfidence(); } }
Garantir uma ótima experiência do usuário
Para a melhor experiência do usuário, siga estas diretrizes no aplicativo:
- A detecção de objeto bem-sucedida depende da complexidade visual do objeto. Para serem detectados, objetos com poucos recursos visuais precisam ocupar uma parte maior da imagem. Forneça aos usuários orientações sobre como capturar entradas que funcionem bem com o tipo de objeto que você quer detectar.
- Ao usar a classificação, se você quiser detectar objetos que não se enquadrem nas categorias aceitas, implemente o tratamento especial para objetos desconhecidos.
Além disso, confira o app de demonstração do Kit de ML com Material Design e a coleção de Padrões para recursos com tecnologia de machine learning do Material Design.
Como melhorar o desempenho
Se você quiser usar a detecção de objetos em um aplicativo em tempo real, siga estas diretrizes para conseguir as melhores taxas de frames:Ao usar o modo de streaming em um aplicativo em tempo real, não use a detecção de vários objetos, porque a maioria dos dispositivos não será capaz de produzir taxas de frames adequadas.
- Se você usar a API
Cameraoucamera2, limite as chamadas ao detector. Se um novo frame de vídeo for disponibilizado durante a execução do detector, descarte esse frame. Consulte a classeVisionProcessorBaseno 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 contrapressão está definida como o valor padrãoImageAnalysis.STRATEGY_KEEP_ONLY_LATEST. Isso garante que apenas uma imagem seja entregue 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 enfileiradas para entrega. Depois que a imagem analisada for fechada chamando ImageProxy.close(), a próxima imagem mais recente será entregue. - Se você usar a saída do detector para sobrepor elementos gráficos na
imagem de entrada, primeiro acesse o resultado do Kit de ML. Em seguida, renderize a imagem
e faça a sobreposição de uma só vez. Isso renderiza a superfície de exibição apenas uma vez para cada quadro de entrada. Consulte as classes
CameraSourcePrevieweGraphicOverlayno app de exemplo 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.