Puedes usar el Kit de AA para etiquetar objetos reconocidos en una imagen mediante un modelo en el dispositivo o un modelo en la nube. Consulta la descripción general para obtener más información sobre los beneficios de cada enfoque.
Antes de comenzar
- Si aún no lo hiciste, agrega Firebase a tu proyecto de Android.
- Agrega las dependencias para las bibliotecas de Android del ML Kit al archivo Gradle
(generalmente
app/build.gradle
) de tu módulo (nivel de app):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-image-label-model:20.0.1' }
-
Opcional pero recomendado: Si usas la API en el dispositivo, configura la app para que, después de instalarse desde Play Store, descargue automáticamente el modelo de AA al dispositivo:
Para ello, agrega la siguiente declaración al archivo
AndroidManifest.xml
de tu app:<application ...> ... <meta-data android:name="com.google.firebase.ml.vision.DEPENDENCIES" android:value="label" /> <!-- To use multiple models: android:value="label,model2,model3" --> </application>
Si no habilitas las descargas de modelos en el momento de la instalación, el modelo se descargará la primera vez que ejecutes el detector en el dispositivo. Las solicitudes que realices antes de que se complete la descarga no generarán resultados. -
Si deseas usar el modelo basado en la nube, pero todavía no habilitaste las API de Cloud para el proyecto, hazlo ahora:
- Abre la página de API del Kit de AA de Firebase console.
-
Si todavía no has actualizado tu proyecto al plan de precios Blaze, haz clic en Actualizar para hacerlo (se te pedirá que realices la actualización únicamente si tu proyecto no está en el plan Blaze).
Solo los proyectos con un plan Blaze pueden usar las API de Cloud.
- Si las API de Cloud no están habilitadas, haz clic en Habilitar las API de Cloud.
Si solo quieres usar el modelo en el dispositivo, puedes omitir este paso.
Ahora estás listo para etiquetar imágenes con un modelo en el dispositivo o uno basado en la nube.
1. Prepara la imagen de entrada
Crea un objetoFirebaseVisionImage
a partir de tu imagen.
El etiquetador de imágenes se ejecuta de forma más rápida cuando usas un Bitmap
o, si usas la API de Camera2, un objeto media.Image
con formato JPEG. Se recomienda que uses estas opciones siempre que sea posible.
-
Para crear un objeto
FirebaseVisionImage
a partir de un objetomedia.Image
, como cuando se captura una imagen con la cámara de un dispositivo, pasa el objetomedia.Image
y la rotación de la imagen aFirebaseVisionImage.fromMediaImage()
.Si usas la biblioteca CameraX, las clases
OnImageCapturedListener
yImageAnalysis.Analyzer
calculan el valor de rotación por ti, así que solo tienes que convertir la rotación en una de las constantesROTATION_
de ML Kit antes de llamar aFirebaseVisionImage.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 // ... } } }
Si no usas una biblioteca de cámaras que te proporcione la rotación de la imagen, puedes calcularla a partir de la rotación del dispositivo y la orientación del sensor de la cámara en el 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 }
Luego, pasa el objeto
media.Image
y el valor de rotación aFirebaseVisionImage.fromMediaImage()
:Java
FirebaseVisionImage image = FirebaseVisionImage.fromMediaImage(mediaImage, rotation);
Kotlin+KTX
val image = FirebaseVisionImage.fromMediaImage(mediaImage, rotation)
- Para crear un objeto
FirebaseVisionImage
a partir de un URI de archivo, pasa el contexto de la app y el URI de archivo aFirebaseVisionImage.fromFilePath()
. Esto es útil cuando usas un intentACTION_GET_CONTENT
para solicitarle al usuario que seleccione una imagen de su app de galería.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 crear un objeto
FirebaseVisionImage
a partir de unByteBuffer
o un array de bytes, primero calcula la rotación de la imagen como se describió anteriormente para la entradamedia.Image
.Luego, crea un objeto
FirebaseVisionImageMetadata
que contenga la altura, el ancho, el formato de codificación de color y la rotación de la imagen: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()
Usa el búfer o array, y el objeto de metadatos, para crear un 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 crear un objeto
FirebaseVisionImage
a partir de un objetoBitmap
, haz lo siguiente:Java
FirebaseVisionImage image = FirebaseVisionImage.fromBitmap(bitmap);
Kotlin+KTX
val image = FirebaseVisionImage.fromBitmap(bitmap)
Bitmap
debe estar en posición vertical, sin que sea necesario rotarla.
2. Configura y ejecuta el etiquetador de imágenes
Para etiquetar objetos de una imagen, pasa el objetoFirebaseVisionImage
al método processImage
de FirebaseVisionImageLabeler
.
Primero, obtén una instancia de
FirebaseVisionImageLabeler
.Si quieres usar el etiquetador de imágenes en el dispositivo, hazlo de la siguiente manera:
Java
FirebaseVisionImageLabeler labeler = FirebaseVision.getInstance() .getOnDeviceImageLabeler(); // Or, to set the minimum confidence required: // FirebaseVisionOnDeviceImageLabelerOptions options = // new FirebaseVisionOnDeviceImageLabelerOptions.Builder() // .setConfidenceThreshold(0.7f) // .build(); // FirebaseVisionImageLabeler labeler = FirebaseVision.getInstance() // .getOnDeviceImageLabeler(options);
Kotlin+KTX
val labeler = FirebaseVision.getInstance().getOnDeviceImageLabeler() // Or, to set the minimum confidence required: // val options = FirebaseVisionOnDeviceImageLabelerOptions.Builder() // .setConfidenceThreshold(0.7f) // .build() // val labeler = FirebaseVision.getInstance().getOnDeviceImageLabeler(options)
Si quieres usar el etiquetador de imágenes en la nube, hazlo de la siguiente manera:
Java
FirebaseVisionImageLabeler labeler = FirebaseVision.getInstance() .getCloudImageLabeler(); // Or, to set the minimum confidence required: // FirebaseVisionCloudImageLabelerOptions options = // new FirebaseVisionCloudImageLabelerOptions.Builder() // .setConfidenceThreshold(0.7f) // .build(); // FirebaseVisionImageLabeler labeler = FirebaseVision.getInstance() // .getCloudImageLabeler(options);
Kotlin+KTX
val labeler = FirebaseVision.getInstance().getCloudImageLabeler() // Or, to set the minimum confidence required: // val options = FirebaseVisionCloudImageLabelerOptions.Builder() // .setConfidenceThreshold(0.7f) // .build() // val labeler = FirebaseVision.getInstance().getCloudImageLabeler(options)
Por último, pasa la imagen al método
processImage()
:Java
labeler.processImage(image) .addOnSuccessListener(new OnSuccessListener<List<FirebaseVisionImageLabel>>() { @Override public void onSuccess(List<FirebaseVisionImageLabel> labels) { // Task completed successfully // ... } }) .addOnFailureListener(new OnFailureListener() { @Override public void onFailure(@NonNull Exception e) { // Task failed with an exception // ... } });
Kotlin+KTX
labeler.processImage(image) .addOnSuccessListener { labels -> // Task completed successfully // ... } .addOnFailureListener { e -> // Task failed with an exception // ... }
3. Obtén información sobre los objetos etiquetados
Si la operación de etiquetado de imagen se ejecuta correctamente, se pasará una lista de objetosFirebaseVisionImageLabel
al objeto de escucha que detecta el resultado correcto. Cada objeto FirebaseVisionImageLabel
representa un elemento etiquetado en la imagen. Por cada etiqueta, puedes obtener su descripción, el ID de entidad del gráfico de conocimiento (si está disponible) y la puntuación de confianza de la coincidencia. Por ejemplo:
Java
for (FirebaseVisionImageLabel label: labels) {
String text = label.getText();
String entityId = label.getEntityId();
float confidence = label.getConfidence();
}
Kotlin+KTX
for (label in labels) {
val text = label.text
val entityId = label.entityId
val confidence = label.confidence
}
Sugerencias para mejorar el rendimiento en tiempo real
Si quieres etiquetar imágenes en una aplicación en tiempo real, sigue estos lineamientos para lograr la mejor velocidad de fotogramas:
- Limita las llamadas al etiquetador de imágenes. Si surge un fotograma de video nuevo mientras se ejecuta el etiquetador de imágenes, ignora ese fotograma.
- Si usas la salida del etiquetador de imágenes para superponer gráficos en la imagen de entrada, primero debes obtener el resultado del Kit de AA y, luego, procesar la imagen y realizar la superposición en un solo paso. De esta manera procesas la superficie de visualización solo una vez por cada fotograma de entrada.
-
Si usas la API de Camera2, captura imágenes en formato
ImageFormat.YUV_420_888
.Si usas la API de Camera más antigua, captura imágenes en formato
ImageFormat.NV21
.
Próximos pasos
- Antes de implementar en producción una app que usa una API de Cloud, debes realizar algunos pasos adicionales para prevenir y mitigar el efecto del acceso no autorizado a la API.