Cuando pasas una imagen al Kit de AA, este detecta hasta cinco objetos junto con la posición de cada objeto en la imagen. Al detectar objetos en transmisiones de video por Internet, cada objeto tiene un ID único que puedes usar para seguir el objeto de un fotograma a otro.
Puedes usar un modelo de clasificación de imágenes personalizado para clasificar los objetos detectado. Consulta Modelos personalizados con el Kit de AA para obtener orientación sobre los requisitos de compatibilidad de los modelos, dónde encontrar modelos previamente entrenados, y cómo entrenar tus propios modelos.
Hay dos formas de integrar un modelo personalizado. Puedes empaquetar el modelo colocarla dentro de la carpeta de recursos de la app, o bien puedes descargarla de forma dinámica desde Firebase. En la siguiente tabla, se comparan las dos opciones.
Modelo empaquetado | Modelo alojado |
---|---|
El modelo es parte del APK de tu app, que aumenta su tamaño. | El modelo no forma parte de tu APK. Se aloja subiendo a Aprendizaje automático de Firebase. |
El modelo está disponible de inmediato, incluso cuando el dispositivo Android está sin conexión | El modelo se descarga a pedido |
No se necesita un proyecto de Firebase | Requiere un proyecto de Firebase |
Debes volver a publicar tu app para actualizar el modelo | Envía actualizaciones del modelo sin volver a publicar tu app |
Sin pruebas A/B integradas | Pruebas A/B sencillas con Firebase Remote Config |
Probar
- Consulta la app de inicio rápido de Vision. para ver un ejemplo de uso del modelo empaquetado la app de inicio rápido de AutoML para un ejemplo de uso del modelo alojado.
- Visita la vitrina de Material Design app para una implementación de extremo a extremo de esta API.
Antes de comenzar
En tu archivo
build.gradle
de nivel de proyecto, asegúrate de incluir lo siguiente: Puedes acceder al repositorio Maven de Google en tubuildscript
yallprojects
.Agrega las dependencias para las bibliotecas de Android del ML Kit al archivo archivo de Gradle a nivel de la app, que suele ser
app/build.gradle
:Para empaquetar un modelo con tu app, sigue estos pasos:
dependencies { // ... // Object detection & tracking feature with custom bundled model implementation 'com.google.mlkit:object-detection-custom:17.0.2' }
Para descargar un modelo de Firebase de forma dinámica, agrega
linkFirebase
. dependencia: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' }
Si quieres descargar un modelo, asegúrate de agregar Firebase a tu proyecto de Android si aún no lo has hecho. Esto no es necesario cuando agrupas el modelo.
1. Carga el modelo
Configura una fuente de modelo local
Para empaquetar el modelo con tu app, haz lo siguiente:
Copia el archivo de modelo (que generalmente termina en
.tflite
o.lite
) en el archivoassets/
carpeta (Es posible que primero debas crear la carpeta haz clic con el botón derecho en la carpetaapp/
y, luego, en Nuevo > Carpeta > Recursos).Luego, agrega lo siguiente al archivo
build.gradle
de tu app para asegurarte de que Gradle no comprime el archivo del modelo cuando se compila la app:android { // ... aaptOptions { noCompress "tflite" // or noCompress "lite" } }
El archivo del modelo se incluirá en el paquete de la app y estará disponible para el Kit de AA. como activo sin procesar.
Crea un objeto
LocalModel
y especifica la ruta de acceso al archivo 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();
Configura una fuente de modelo alojada en Firebase
Para usar el modelo alojado de forma remota, crea un objeto CustomRemoteModel
. Para ello, haz lo siguiente:
FirebaseModelSource
, que especifica el nombre que le asignaste al modelo cuando
lo publicó:
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();
Luego, inicia la tarea de descarga del modelo y especifica las condiciones que deseas permitir la descarga. Si el modelo no está en el dispositivo o si es del modelo está disponible, la tarea descargará de forma asíncrona la de 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. } });
Muchas apps comienzan la tarea de descarga en su código de inicialización, pero tú puedes hacerlo en cualquier momento antes de usar el modelo.
2. Configura el detector de objetos
Después de configurar las fuentes de tu modelo, configura el detector de objetos para tu
caso de uso con un objeto CustomObjectDetectorOptions
. Puedes cambiar el
los siguientes parámetros de configuración:
Configuración del detector de objetos | |
---|---|
Modo de detección |
STREAM_MODE (predeterminado) | SINGLE_IMAGE_MODE
En En |
Detecta varios objetos y hazles un seguimiento |
false (predeterminado) | true
Ya sea para detectar y rastrear hasta cinco objetos o solo los más objeto destacado (predeterminado). |
Clasifica objetos |
false (predeterminado) | true
Si se deben clasificar o no los objetos detectados
de clasificación personalizado. Para usar tu clasificación personalizada
debes configurarlo como |
Umbral de confianza de clasificación |
Puntuación de confianza mínima de las etiquetas detectadas. Si no se establece, de clasificación especificado por los metadatos del modelo. Si el modelo no contiene metadatos o si estos no especificas un umbral del clasificador, se establecerá un umbral predeterminado de 0.0 que se usan. |
Cantidad máxima de etiquetas por objeto |
Cantidad máxima de etiquetas por objeto que el detector el resultado. Si no la estableces, se usará el valor predeterminado de 10. |
La API de detección y seguimiento de objetos está optimizada para estos dos usos principales casos:
- Detección y seguimiento en vivo del objeto más prominente de la cámara visor.
- Es la detección de varios objetos a partir de una imagen estática.
Si deseas configurar la API para estos casos de uso con un modelo empaquetado localmente, haz lo siguiente:
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);
Si tienes un modelo alojado de forma remota, deberás verificar que se haya
descargado antes de ejecutarlo. Puedes verificar el estado de la descarga del modelo
tarea con el método isModelDownloaded()
del administrador de modelos.
Aunque solo tienes que confirmar esto antes de ejecutar el detector, si tener un modelo alojado de forma remota y uno empaquetado localmente, podría hacer sentido realizar esta verificación cuando se crea una instancia del detector de imágenes: crea un del modelo remoto, si se descargó, y del servidor de lo contrario.
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); } });
Si solo tienes un modelo alojado de forma remota, debes inhabilitar las funciones
(por ejemplo, ocultar o inhabilitar parte de la IU) hasta que
confirmas que el modelo se descargó. Puedes hacerlo adjuntando un objeto de escucha
al método download()
del administrador 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. Prepara la imagen de entrada
Crea un objetoInputImage
a partir de tu imagen.
El detector de objetos se ejecuta directamente desde un Bitmap
, un ByteBuffer
de NV21 o un
YUV_420_888 media.Image
. Se puede construir una InputImage
a partir de esas fuentes
se recomienda si tienes acceso directo a uno de ellos. Si construyes un
InputImage
provenientes de otras fuentes, manejaremos la conversión de forma interna para
y podría ser menos eficiente.
Puedes crear un InputImage
objeto de diferentes fuentes, cada uno se explica a continuación.
Usa un media.Image
Para crear un elemento InputImage
, sigue estos pasos:
objeto de un objeto media.Image
, como cuando capturas una imagen de una
la cámara del dispositivo, pasa el objeto media.Image
y el
rotación a InputImage.fromMediaImage()
.
Si usas
biblioteca de CameraX, los elementos OnImageCapturedListener
y
Las clases ImageAnalysis.Analyzer
calculan el valor de rotación
por ti.
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 // ... } } }
Si no usas una biblioteca de cámaras que indique el grado de rotación de la imagen, calcularlo a partir del grado de rotación del dispositivo y la orientación de la cámara sensor en el 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; }
Luego, pasa el objeto media.Image
y el
valor de grado de rotación a InputImage.fromMediaImage()
:
Kotlin
val image = InputImage.fromMediaImage(mediaImage, rotation)
Java
InputImage image = InputImage.fromMediaImage(mediaImage, rotation);
Usa un URI de archivo
Para crear un elemento InputImage
, sigue estos pasos:
objeto de un URI de archivo, pasa el contexto de la app y el URI del archivo a
InputImage.fromFilePath()
Esto es útil cuando
usa un intent ACTION_GET_CONTENT
para solicitarle al usuario que seleccione
una imagen de su app de galería.
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(); }
Usa un objeto ByteBuffer
o ByteArray
Para crear un elemento InputImage
, sigue estos pasos:
objeto de una ByteBuffer
o ByteArray
, primero calcula la imagen
grado de rotación como se describió anteriormente para la entrada media.Image
.
Luego, crea el objeto InputImage
con el búfer o array, junto con los atributos
El alto, el ancho, el formato de codificación de color y el grado de rotación:
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 );
Usa un Bitmap
Para crear un elemento InputImage
, sigue estos pasos:
objeto a partir de un objeto Bitmap
, realiza la siguiente declaración:
Kotlin
val image = InputImage.fromBitmap(bitmap, 0)
Java
InputImage image = InputImage.fromBitmap(bitmap, rotationDegree);
La imagen se representa con un objeto Bitmap
junto con los grados de rotación.
4. Ejecuta el 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. Obtén información sobre los objetos etiquetados
Si la llamada a process()
funciona correctamente, se pasa una lista de DetectedObject
a
la persona que escucha el éxito.
Cada DetectedObject
contiene las siguientes propiedades:
Cuadro de límite | Un Rect que indica la posición del objeto en el
imagen. |
||||||
ID de seguimiento | Un número entero que identifica el objeto en las imágenes. Nulo en SINGLE_IMAGE_MODE | ||||||
Etiquetas |
|
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(); } }
Cómo garantizar una excelente experiencia del usuario
Para obtener la mejor experiencia del usuario, sigue estos lineamientos en tu app:
- La detección correcta de objetos depende de la complejidad visual del objeto. En ser detectados, los objetos con un número reducido de características visuales podrían necesitar ocupe una parte más grande de la imagen. Debes brindar orientación a los usuarios sobre y capturar entradas que funcionen bien con el tipo de objetos que quieres detectar.
- Cuando usas la clasificación, si deseas detectar objetos que no caen de forma clara en las categorías admitidas, implementa un manejo especial para los objetos.
Además, consulta la App de muestra de Material Design del Kit de AA y Material Design Colección de patrones para los atributos con tecnología de aprendizaje automático.
Cómo mejorar el rendimiento
Si quieres usar la detección de objetos en una aplicación en tiempo real, sigue estos pasos: pautas para lograr la mejor velocidad de fotogramas:Cuando uses el modo de transmisión en una aplicación en tiempo real, no uses varias detección de objetos, ya que la mayoría de los dispositivos no podrán producir una velocidad de fotogramas adecuada.
- Si usas
Camera
o API decamera2
, limitar las llamadas al detector. Si un video nuevo esté disponible mientras se ejecuta el detector, descarta el fotograma. Consulta laVisionProcessorBase
en la app de muestra de inicio rápido para ver un ejemplo. - Si usas la API de
CameraX
, asegúrate de que la estrategia de contrapresión se haya establecido en su valor predeterminadoImageAnalysis.STRATEGY_KEEP_ONLY_LATEST
De esta forma, se garantiza que solo se entregará una imagen a la vez para su análisis. Si hay más imágenes que se producen cuando el analizador está ocupado, se eliminarán automáticamente y no se agregarán a la cola la entrega de software. Una vez que la imagen que se está analizando se cierra con una llamada a ImageProxy.close(), se publicará la siguiente imagen más reciente. - Si usas la salida del detector para superponer gráficos
la imagen de entrada, primero obtén el resultado del Kit de AA y, luego, renderiza la imagen
y superponerla en un solo paso. Se renderiza en la superficie de visualización.
solo una vez para cada fotograma de entrada. Consulta la
CameraSourcePreview
yGraphicOverlay
en la app de muestra de inicio rápido para ver un ejemplo. - Si usas la API de Camera2, captura imágenes en
ImageFormat.YUV_420_888
. Si usas la API de Camera, captura imágenes enImageFormat.NV21
.