Quando você transmite uma imagem para o Kit de ML, ele detecta até cinco objetos na imagem junto com a posição de cada objeto na imagem. Ao detectar objetos em streams de vídeo, cada objeto tem um ID exclusivo que pode ser usado para rastrear o objeto de frame a frame.
É possível usar um modelo de classificação de imagens personalizado para classificar os objetos detectado. 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. É possível agrupar o modelo colocá-lo na pasta de recursos do app ou fazer o download dele dinamicamente do Firebase. A tabela a seguir compara as duas opções.
Modelo em pacote | Modelo hospedado |
---|---|
O modelo faz parte do APK do app, o que aumenta o tamanho dele. | O modelo não faz parte do seu APK. Ele é hospedado por meio do upload para Machine Learning do Firebase. |
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 republicar o app para atualizar o modelo | Enviar atualizações do modelo sem republicar o app |
Sem testes A/B integrados | 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. um exemplo de uso do modelo empacotado e do app de início rápido do AutoML para uma exemplo de uso do modelo hospedado.
- Veja a vitrine do Material Design app para uma implementação completa dessa API.
Antes de começar
No arquivo
build.gradle
no nível do projeto, inclua Repositório Maven do Google embuildscript
eallprojects
.Adicione as dependências das bibliotecas do Android do Kit de ML ao arquivo arquivo do Gradle no nível do app, que geralmente é
app/build.gradle
:Para agrupar um modelo e seu app:
dependencies { // ... // Object detection & tracking feature with custom bundled model implementation 'com.google.mlkit:object-detection-custom:17.0.2' }
Para fazer o download dinâmico de um modelo do Firebase, adicione
linkFirebase
dependência:dependencies { // ... // Object detection & tracking feature with model downloaded // from firebase implementation 'com.google.mlkit:object-detection-custom:17.0.2' implementation 'com.google.mlkit:linkfirebase:17.0.0' }
Se você quiser fazer o download de um modelo, verifique se adicione o Firebase ao seu projeto Android, caso ainda não tenha feito isso. Isso não é necessário quando você agrupa o modelo.
1. Carregar o modelo
Configurar uma fonte de modelo local
Para agrupar o modelo e o app, faça o seguinte:
Copie o arquivo do modelo (geralmente terminando em
.tflite
ou.lite
) para o arquivoassets/
. Talvez seja necessário criar a pasta primeiro clicando com o botão direito do mouse na pastaapp/
e, em seguida, Novo > Pasta > de recursos.)Em seguida, adicione o seguinte ao arquivo
build.gradle
do app para garantir que O Gradle não compacta 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 estará disponível para o Kit de ML como um ativo 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 modelo hospedada no Firebase
Para usar o modelo hospedado remotamente, crie um objeto CustomRemoteModel
ao
FirebaseModelSource
, especificando o nome que você atribuiu ao modelo ao
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 do qual você quer permitir o download. Se o modelo não estiver no dispositivo ou se um modelo mais recente versão do modelo estiver disponível, a tarefa fará o download do arquivo 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.
2. Configurar o detector de objetos
Depois de configurar as origens do modelo, configure o detector de objetos do seu
caso de uso com um objeto CustomObjectDetectorOptions
. É possível alterar
seguintes configurações:
Configurações do detector de objetos | |
---|---|
Modo de detecção |
STREAM_MODE (padrão) | SINGLE_IMAGE_MODE
Em Em |
Detectar e rastrear vários objetos |
false (padrão) | true
Se deve detectar e rastrear até cinco objetos ou apenas os mais objeto proeminente (padrão). |
Classificar objetos |
false (padrão) | true
Se os objetos detectados são ou não classificados usando os atributos
modelo de classificador personalizado. Para usar sua classificação personalizada
modelo, defina-o como |
Limite de confiança de classificação |
Pontuação de confiança mínima dos rótulos detectados. Se não for definido, qualquer um o limite do classificador especificado pelos metadados do modelo será usado. Se o modelo não tiver metadados ou se os metadados não tiverem especificar um limite de classificador, um limite padrão de 0,0 será usados. |
Máximo de rótulos por objeto |
Número máximo de rótulos por objeto que o detector voltar. Se não for definido, o valor padrão de 10 será usado. |
A API de detecção e rastreamento de objetos é otimizada para esses dois principais casos:
- Detecção ao vivo e rastreamento do objeto mais proeminente na câmera visor.
- 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 ele foi
antes de executá-lo. É possível verificar o status do download do modelo
tarefa usando o método isModelDownloaded()
do gerenciador de modelos.
Embora isso só precise ser confirmado antes de executar o detector, se você mas tem um modelo hospedado remotamente e um modelo agrupado localmente, isso pode tornar sentido realizar essa verificação ao instanciar o detector de imagem: crie um de detecção a partir do modelo remoto, caso tenha sido baixado, e do módulo de outro modelo.
Kotlin
RemoteModelManager.getInstance().isModelDownloaded(remoteModel) .addOnSuccessListener { isDownloaded -> val optionsBuilder = if (isDownloaded) { CustomObjectDetectorOptions.Builder(remoteModel) } else { CustomObjectDetectorOptions.Builder(localModel) } val customObjectDetectorOptions = optionsBuilder .setDetectorMode(CustomObjectDetectorOptions.SINGLE_IMAGE_MODE) .enableClassification() .setClassificationConfidenceThreshold(0.5f) .setMaxPerObjectLabelCount(3) .build() val objectDetector = ObjectDetection.getClient(customObjectDetectorOptions) }
Java
RemoteModelManager.getInstance().isModelDownloaded(remoteModel) .addOnSuccessListener(new OnSuccessListener() { @Override public void onSuccess(Boolean isDownloaded) { CustomObjectDetectorOptions.Builder optionsBuilder; if (isDownloaded) { optionsBuilder = new CustomObjectDetectorOptions.Builder(remoteModel); } else { optionsBuilder = new CustomObjectDetectorOptions.Builder(localModel); } CustomObjectDetectorOptions customObjectDetectorOptions = optionsBuilder .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
da interface (por exemplo, usar o recurso esmaecer ou ocultar parte da interface) até
confirme se 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. } });
3. Preparar a imagem de entrada
Crie um objetoInputImage
usando sua imagem.
O detector de objetos é executado diretamente de um Bitmap
, NV21 ByteBuffer
ou
YUV_420_888 media.Image
. Criar um InputImage
dessas origens é
recomendado se você tiver acesso direto a um deles. Se você criar um
InputImage
de outras origens, vamos processar a conversão internamente para
para você, e ela pode ser menos eficiente.
Você pode criar um InputImage
de diferentes origens, cada uma explicada abaixo.
Como usar um media.Image
Para criar um InputImage
de um objeto media.Image
, como quando você captura uma imagem de um
da câmera do dispositivo, transmita o objeto media.Image
e o
rotação para InputImage.fromMediaImage()
.
Se você usar o método
CameraX, os recursos OnImageCapturedListener
e
As classes ImageAnalysis.Analyzer
calculam 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 informe o grau de rotação da imagem, pode calculá-lo usando o grau de rotação do dispositivo e a orientação da câmera no dispositivo:
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
grau de rotação para InputImage.fromMediaImage()
:
Kotlin
val image = InputImage.fromMediaImage(mediaImage, rotation)
Java
InputImage image = InputImage.fromMediaImage(mediaImage, rotation);
Usar um URI de arquivo
Para criar um InputImage
de um URI de arquivo, transmita o contexto do aplicativo e o URI do arquivo para
InputImage.fromFilePath()
. Isso é útil quando você
usar uma intent ACTION_GET_CONTENT
para solicitar que o usuário selecione
uma imagem do app Galeria.
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 InputImage
objeto de uma ByteBuffer
ou ByteArray
, primeiro calcule a imagem
grau de rotação conforme descrito anteriormente para a entrada media.Image
.
Depois, crie o objeto InputImage
com o buffer ou a matriz, junto com o
altura, largura, formato de codificação de cores e grau de rotação:
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 InputImage
de 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. Conseguir informações sobre os objetos rotulados
Se a chamada para process()
for bem-sucedida, uma lista de DetectedObject
s será transmitida para
o listener de sucesso.
Cada DetectedObject
contém as seguintes propriedades:
Caixa delimitadora | Uma Rect que indica a posição do objeto no
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(); } }
Como garantir uma ótima experiência do usuário
Para ter a melhor experiência do usuário, siga estas diretrizes no app:
- A detecção bem-sucedida de objetos depende da complexidade visual do objeto. Em para serem detectados, os objetos com um pequeno número de recursos visuais podem precisar para ocupar uma parte maior da imagem. Você deve fornecer aos usuários orientação sobre capturando entradas que funcionam bem com o tipo de objetos que você quer detectar.
- Na classificação, para detectar objetos que não se enquadram claramente nas categorias suportadas, implemente tratamento especial para objetos.
Além disso, confira a App de demonstração do Kit de ML com Material Design e os Material Design Coleção Padrões para recursos com tecnologia de aprendizado de máquina.
Como melhorar o desempenho
Para usar a detecção de objetos em um aplicativo em tempo real, siga estas instruções diretrizes para obter as melhores taxas de quadros:Ao usar o modo de streaming em um aplicativo em tempo real, não use diversos detecção de objetos, já que a maioria dos dispositivos não é capaz de produzir taxas de quadros adequadas.
- Se você usar o método
Camera
ou APIcamera2
, limitar chamadas ao detector. Se um novo vídeo fica disponível enquanto o detector está em execução, descarte esse frame. Consulte aVisionProcessorBase
no app de amostra do guia de início rápido para conferir um exemplo. - Se você usa a API
CameraX
, verificar se a estratégia de pressão de retorno está definida para o valor padrãoImageAnalysis.STRATEGY_KEEP_ONLY_LATEST
. Isso garante que apenas uma imagem será 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 enfileiradas entrega. Depois que a imagem analisada é 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
a imagem de entrada, primeiro acesse o resultado do Kit de ML e, em seguida, renderize a imagem
e sobreposição em uma única etapa. Isso é renderizado na superfície da tela.
apenas uma vez para cada frame de entrada. Consulte a
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
ImageFormat.YUV_420_888
. Se você usar a API Camera mais antiga, capture imagens noImageFormat.NV21
.