Puedes usar el Kit de AA para detectar rostros en imágenes y videos.
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' // If you want to detect face contours (landmark detection and classification // don't require this additional model): implementation 'com.google.firebase:firebase-ml-vision-face-model:20.0.1' }
- Opcional, pero recomendado: Configura tu app para descargar automáticamente el modelo de AA al dispositivo después de instalar la app desde Play Store.
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="face" /> <!-- To use multiple models: android:value="face,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. Las solicitudes que realices antes de que se complete la descarga no generarán ningún resultado.
Lineamientos para imágenes de entrada
Para que el Kit de AA detecte rostros con precisión, las imágenes de entrada deben contener rostros representados con datos de píxeles suficientes. Por lo general, cada rostro que quieras detectar en una imagen debe tener al menos 100×100 píxeles. Si quieres detectar el contorno de los rostros, el Kit de AA requiere una entrada con mayor resolución: cada rostro debe ser de al menos 200x200 píxeles.
Si reconoces rostros en una aplicación en tiempo real, te recomendamos tener en cuenta las dimensiones generales de las imágenes de entrada. Las imágenes más pequeñas se pueden procesar más rápido. Así que, para reducir la latencia, captura imágenes con resoluciones más bajas (ten en cuenta los requisitos de exactitud mencionados anteriormente) y asegúrate de que el rostro de la persona ocupe la mayor parte de la imagen como sea posible. Consulta también las sugerencias para mejorar el rendimiento en tiempo real.
Un enfoque de imagen deficiente puede afectar la exactitud. Si no obtienes resultados aceptables, intenta pedirle al usuario que vuelva a capturar la imagen.
La orientación de un rostro en relación con la cámara también puede afectar las características que detecta el Kit de AA. Consulta Conceptos de detección de rostro.
1. Configura el detector de rostros
Antes de aplicar la detección de rostro a una imagen, si quieres modificar la configuración predeterminada del detector de rostros, especifica las opciones de configuración con un objetoFirebaseVisionFaceDetectorOptions
.
Puedes cambiar las siguientes opciones de configuración:
Configuración | |
---|---|
Modo de rendimiento | FAST (predeterminado) | ACCURATE Prefiere la velocidad o la precisión en la detección de rostros. |
Detectar puntos de referencia | NO_LANDMARKS (predeterminado) | ALL_LANDMARKS Indica si se debe intentar identificar “puntos de referencia” faciales: ojos, orejas, nariz, mejillas, boca, etcétera. |
Detectar contornos | NO_CONTOURS (predeterminado) | ALL_CONTOURS Indica si se deben detectar los contornos de rasgos faciales. Los contornos se detectarán solo para los rostros más destacados de la imagen. |
Clasificar rostros | NO_CLASSIFICATIONS (predeterminado)
| ALL_CLASSIFICATIONS Indica si se deben clasificar los rostros en categorías como “sonriente” y “ojos abiertos”. |
Tamaño mínimo del rostro | float (predeterminado: 0.1f )
El tamaño mínimo, en relación con la imagen, de los rostros que se detectarán. |
Habilitar el seguimiento de rostros | false (predeterminado) | true Indica si se deben asignar ID a los rostros, que se pueden usar para seguir los rostros entre imágenes. Ten en cuenta que cuando la detección de rostro está habilitada, solo se detectará un rostro, por lo que el seguimiento de rostro no brindará resultados útiles. Por eso, y para mejorar la velocidad de la detección, no habilites la detección de contorno y el seguimiento de rostro a la vez. |
Por ejemplo:
Java
// High-accuracy landmark detection and face classification FirebaseVisionFaceDetectorOptions highAccuracyOpts = new FirebaseVisionFaceDetectorOptions.Builder() .setPerformanceMode(FirebaseVisionFaceDetectorOptions.ACCURATE) .setLandmarkMode(FirebaseVisionFaceDetectorOptions.ALL_LANDMARKS) .setClassificationMode(FirebaseVisionFaceDetectorOptions.ALL_CLASSIFICATIONS) .build(); // Real-time contour detection of multiple faces FirebaseVisionFaceDetectorOptions realTimeOpts = new FirebaseVisionFaceDetectorOptions.Builder() .setContourMode(FirebaseVisionFaceDetectorOptions.ALL_CONTOURS) .build();
Kotlin+KTX
// High-accuracy landmark detection and face classification val highAccuracyOpts = FirebaseVisionFaceDetectorOptions.Builder() .setPerformanceMode(FirebaseVisionFaceDetectorOptions.ACCURATE) .setLandmarkMode(FirebaseVisionFaceDetectorOptions.ALL_LANDMARKS) .setClassificationMode(FirebaseVisionFaceDetectorOptions.ALL_CLASSIFICATIONS) .build() // Real-time contour detection of multiple faces val realTimeOpts = FirebaseVisionFaceDetectorOptions.Builder() .setContourMode(FirebaseVisionFaceDetectorOptions.ALL_CONTOURS) .build()
2. Ejecuta el detector de rostros
Para detectar rostros en una imagen, crea un objetoFirebaseVisionImage
a partir de un Bitmap
, una media.Image
, un ByteBuffer
, un arreglo de bytes o un archivo ubicado en el dispositivo. Luego, pasa el objeto FirebaseVisionImage
al método detectInImage
de FirebaseVisionFaceDetector
.
Para el reconocimiento facial, debes utilizar una imagen con una dimensión de al menos 480 × 360 píxeles. Si el reconocimiento de rostros es en tiempo real, puedes capturar fotogramas con esta resolución mínima para reducir la latencia.
Crea un objeto
FirebaseVisionImage
a partir de tu imagen.-
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.
-
Obtén una instancia de
FirebaseVisionFaceDetector
:Java
FirebaseVisionFaceDetector detector = FirebaseVision.getInstance() .getVisionFaceDetector(options);
Kotlin+KTX
val detector = FirebaseVision.getInstance() .getVisionFaceDetector(options)
Por último, pasa la imagen al método
detectInImage
:Java
Task<List<FirebaseVisionFace>> result = detector.detectInImage(image) .addOnSuccessListener( new OnSuccessListener<List<FirebaseVisionFace>>() { @Override public void onSuccess(List<FirebaseVisionFace> faces) { // Task completed successfully // ... } }) .addOnFailureListener( new OnFailureListener() { @Override public void onFailure(@NonNull Exception e) { // Task failed with an exception // ... } });
Kotlin+KTX
val result = detector.detectInImage(image) .addOnSuccessListener { faces -> // Task completed successfully // ... } .addOnFailureListener { e -> // Task failed with an exception // ... }
3. Obtén información sobre los rostros detectados
Si la operación de reconocimiento de rostros se ejecuta correctamente, se pasará una lista de objetosFirebaseVisionFace
al objeto de escucha que detecta el resultado correcto. Cada objeto FirebaseVisionFace
representa un rostro que se detectó en la imagen. Para cada rostro, puedes obtener las coordenadas de sus límites en la imagen de entrada, junto con cualquier otra información que encuentre el detector de rostros según la configuración que le asignaste. Por ejemplo:
Java
for (FirebaseVisionFace face : faces) { Rect bounds = face.getBoundingBox(); float rotY = face.getHeadEulerAngleY(); // Head is rotated to the right rotY degrees float rotZ = face.getHeadEulerAngleZ(); // Head is tilted sideways rotZ degrees // If landmark detection was enabled (mouth, ears, eyes, cheeks, and // nose available): FirebaseVisionFaceLandmark leftEar = face.getLandmark(FirebaseVisionFaceLandmark.LEFT_EAR); if (leftEar != null) { FirebaseVisionPoint leftEarPos = leftEar.getPosition(); } // If contour detection was enabled: List<FirebaseVisionPoint> leftEyeContour = face.getContour(FirebaseVisionFaceContour.LEFT_EYE).getPoints(); List<FirebaseVisionPoint> upperLipBottomContour = face.getContour(FirebaseVisionFaceContour.UPPER_LIP_BOTTOM).getPoints(); // If classification was enabled: if (face.getSmilingProbability() != FirebaseVisionFace.UNCOMPUTED_PROBABILITY) { float smileProb = face.getSmilingProbability(); } if (face.getRightEyeOpenProbability() != FirebaseVisionFace.UNCOMPUTED_PROBABILITY) { float rightEyeOpenProb = face.getRightEyeOpenProbability(); } // If face tracking was enabled: if (face.getTrackingId() != FirebaseVisionFace.INVALID_ID) { int id = face.getTrackingId(); } }
Kotlin+KTX
for (face in faces) { val bounds = face.boundingBox val rotY = face.headEulerAngleY // Head is rotated to the right rotY degrees val rotZ = face.headEulerAngleZ // Head is tilted sideways rotZ degrees // If landmark detection was enabled (mouth, ears, eyes, cheeks, and // nose available): val leftEar = face.getLandmark(FirebaseVisionFaceLandmark.LEFT_EAR) leftEar?.let { val leftEarPos = leftEar.position } // If contour detection was enabled: val leftEyeContour = face.getContour(FirebaseVisionFaceContour.LEFT_EYE).points val upperLipBottomContour = face.getContour(FirebaseVisionFaceContour.UPPER_LIP_BOTTOM).points // If classification was enabled: if (face.smilingProbability != FirebaseVisionFace.UNCOMPUTED_PROBABILITY) { val smileProb = face.smilingProbability } if (face.rightEyeOpenProbability != FirebaseVisionFace.UNCOMPUTED_PROBABILITY) { val rightEyeOpenProb = face.rightEyeOpenProbability } // If face tracking was enabled: if (face.trackingId != FirebaseVisionFace.INVALID_ID) { val id = face.trackingId } }
Ejemplo de contornos de rostro
Cuando la detección de contorno de rostro se encuentra habilitada, obtienes una lista de puntos por cada característica facial que se detectó. Estos puntos representan la forma de la característica. Consulta Descripción general de los conceptos de la detección de rostros para obtener más información sobre cómo se representan los contornos.
En la siguiente imagen, se describe cómo se asignan estos puntos a un rostro (haz clic en la imagen para ampliarla):
Detección de rostro en tiempo real
Si quieres usar la detección de rostro en una aplicación en tiempo real, sigue estos lineamientos para lograr los mejores fotogramas por segundo:
Configura el detector de rostros para usar la detección de contorno facial o la clasificación y detección de puntos de referencia, pero no ambos.
Detección de contorno
Detección de puntos de referencia
Clasificación
Detección y clasificación de puntos de referencia
Detección de contorno y de puntos de referencia
Detección y clasificación de contorno
Detección de contorno y de puntos de referencia, y clasificaciónHabilita el modo
FAST
(habilitado de forma predeterminada).Captura imágenes con una resolución más baja. De todas formas, también ten en cuenta los requisitos de dimensiones de imágenes de esta API.
- Limita las llamadas al detector. Si hay un fotograma de video nuevo disponible mientras se ejecuta el detector, ignora ese fotograma.
- Si estás usando la salida del detector para superponer gráficos en la imagen de entrada, primero obtén el resultado de la detección de ML Kit y, luego, procesa la imagen y 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
.