Você pode usar o ML Kit para detectar e rastrear objetos em quadros de vídeo.
Quando você passa imagens do ML Kit, o ML Kit retorna, para cada imagem, uma lista de até cinco objetos detectados e sua posição na imagem. Ao detectar objetos em fluxos de vídeo, cada objeto possui um ID que você pode usar para rastreá-lo nas imagens. Opcionalmente, você também pode ativar a classificação aproximada de objetos, que rotula os objetos com descrições amplas de categorias.
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-object-detection-model:19.0.6' }
1. Configure o detector de objetos
Para começar a detectar e rastrear objetos, primeiro crie uma instância de FirebaseVisionObjectDetector
, especificando opcionalmente quaisquer configurações de detector que você deseja alterar do padrão.
Configure o detector de objetos para seu caso de uso com um objeto
FirebaseVisionObjectDetectorOptions
. Você pode alterar as seguintes configurações:Configurações do detector de objetos Modo de detecção STREAM_MODE
(padrão) |SINGLE_IMAGE_MODE
Em
STREAM_MODE
(padrão), o detector de objetos é executado com baixa latência, mas pode produzir resultados incompletos (como caixas delimitadoras ou rótulos de categoria não especificados) nas primeiras invocações do detector. Além disso, emSTREAM_MODE
, o detector atribui IDs de rastreamento a objetos, que você pode usar para rastrear objetos entre quadros. Use este modo quando quiser rastrear objetos ou quando a baixa latência for importante, como ao processar fluxos de vídeo em tempo real.Em
SINGLE_IMAGE_MODE
, o detector de objetos aguarda até que a caixa delimitadora de um objeto detectado e (se você habilitou a classificação) o rótulo da categoria estejam disponíveis antes de retornar um resultado. Como consequência, a latência de detecção é potencialmente maior. Além disso, emSINGLE_IMAGE_MODE
, os IDs de rastreamento não são atribuídos. Use este modo se a latência não for crítica e você não quiser lidar com resultados parciais.Detecte e rastreie vários objetos false
(padrão) |true
Seja para detectar e rastrear até cinco objetos ou apenas o objeto mais proeminente (padrão).
Classificar objetos false
(padrão) |true
Se deve ou não classificar os objetos detectados em categorias grosseiras. Quando ativado, o detector de objetos classifica os objetos nas seguintes categorias: produtos de moda, alimentos, produtos domésticos, lugares, plantas e desconhecidos.
A API de detecção e rastreamento de objetos é otimizada para estes dois casos de uso principais:
- Detecção e rastreamento ao vivo do objeto mais proeminente no visor da câmera
- Detecção de vários objetos de uma imagem estática
Para configurar a API para estes casos de uso:
Java
// Live detection and tracking FirebaseVisionObjectDetectorOptions options = new FirebaseVisionObjectDetectorOptions.Builder() .setDetectorMode(FirebaseVisionObjectDetectorOptions.STREAM_MODE) .enableClassification() // Optional .build(); // Multiple object detection in static images FirebaseVisionObjectDetectorOptions options = new FirebaseVisionObjectDetectorOptions.Builder() .setDetectorMode(FirebaseVisionObjectDetectorOptions.SINGLE_IMAGE_MODE) .enableMultipleObjects() .enableClassification() // Optional .build();
Kotlin+KTX
// Live detection and tracking val options = FirebaseVisionObjectDetectorOptions.Builder() .setDetectorMode(FirebaseVisionObjectDetectorOptions.STREAM_MODE) .enableClassification() // Optional .build() // Multiple object detection in static images val options = FirebaseVisionObjectDetectorOptions.Builder() .setDetectorMode(FirebaseVisionObjectDetectorOptions.SINGLE_IMAGE_MODE) .enableMultipleObjects() .enableClassification() // Optional .build()
Obtenha uma instância do
FirebaseVisionObjectDetector
:Java
FirebaseVisionObjectDetector objectDetector = FirebaseVision.getInstance().getOnDeviceObjectDetector(); // Or, to change the default settings: FirebaseVisionObjectDetector objectDetector = FirebaseVision.getInstance().getOnDeviceObjectDetector(options);
Kotlin+KTX
val objectDetector = FirebaseVision.getInstance().getOnDeviceObjectDetector() // Or, to change the default settings: val objectDetector = FirebaseVision.getInstance().getOnDeviceObjectDetector(options)
2. Execute o detector de objetos
Para detectar e rastrear objetos, passe imagens para o método processImage()
da instância FirebaseVisionObjectDetector
.
Para cada quadro de vídeo ou imagem em uma sequência, faça o seguinte:
Crie um objeto
FirebaseVisionImage
a partir da sua imagem.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.
Passe a imagem para o método
processImage()
:Java
objectDetector.processImage(image) .addOnSuccessListener( new OnSuccessListener<List<FirebaseVisionObject>>() { @Override public void onSuccess(List<FirebaseVisionObject> detectedObjects) { // Task completed successfully // ... } }) .addOnFailureListener( new OnFailureListener() { @Override public void onFailure(@NonNull Exception e) { // Task failed with an exception // ... } });
Kotlin+KTX
objectDetector.processImage(image) .addOnSuccessListener { detectedObjects -> // Task completed successfully // ... } .addOnFailureListener { e -> // Task failed with an exception // ... }
Se a chamada para
processImage()
for bem-sucedida, uma lista deFirebaseVisionObject
s será transmitida ao ouvinte de sucesso.Cada
FirebaseVisionObject
contém as seguintes propriedades:Caixa delimitadora Um Rect
indicando a posição do objeto na imagem.ID de rastreamento Um inteiro que identifica o objeto nas imagens. Nulo em SINGLE_IMAGE_MODE. Categoria A categoria grosseira do objeto. Se o detector de objetos não tiver a classificação habilitada, será sempre FirebaseVisionObject.CATEGORY_UNKNOWN
.Confiança O valor de confiança da classificação do objeto. Se o detector de objetos não tiver a classificação habilitada ou o objeto for classificado como desconhecido, será null
.Java
// The list of detected objects contains one item if multiple object detection wasn't enabled. for (FirebaseVisionObject obj : detectedObjects) { Integer id = obj.getTrackingId(); Rect bounds = obj.getBoundingBox(); // If classification was enabled: int category = obj.getClassificationCategory(); Float confidence = obj.getClassificationConfidence(); }
Kotlin+KTX
// The list of detected objects contains one item if multiple object detection wasn't enabled. for (obj in detectedObjects) { val id = obj.trackingId // A number that identifies the object across images val bounds = obj.boundingBox // The object's position in the image // If classification was enabled: val category = obj.classificationCategory val confidence = obj.classificationConfidence }
Melhorando a usabilidade e o desempenho
Para obter a melhor experiência do usuário, siga estas diretrizes em seu aplicativo:
- A detecção bem-sucedida de objetos depende da complexidade visual do objeto. Objetos com um pequeno número de características visuais podem precisar ocupar uma parte maior da imagem para serem detectados. Você deve fornecer aos usuários orientações sobre como capturar entradas que funcionem bem com o tipo de objetos que você deseja detectar.
- Ao usar a classificação, se você quiser detectar objetos que não se enquadram perfeitamente nas categorias suportadas, implemente um tratamento especial para objetos desconhecidos.
Além disso, confira o [aplicativo de demonstração do ML Kit Material Design][showcase-link]{: .external } e a coleção de recursos do Material Design Patterns para aprendizado de máquina .
Ao usar o modo de streaming em um aplicativo em tempo real, siga estas diretrizes para obter as melhores taxas de quadros:
Não use a detecção de múltiplos objetos no modo de streaming, pois a maioria dos dispositivos não será capaz de produzir taxas de quadros adequadas.
Desative a classificação se não precisar dela.
- 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
.