El Kit de AA proporciona un SDK optimizado para la segmentación de selfies.
Los recursos de Selfie Segmenter se vinculan de forma estática a tu app en el tiempo de compilación. Esto aumentará el tamaño de descarga de tu app en alrededor de 4.5 MB, y la latencia de la API puede variar de 25 ms a 65 ms según el tamaño de la imagen de entrada, como se mide en un Pixel 4.
Probar
- Prueba la app de ejemplo para ver un ejemplo de uso de esta API.
Antes de comenzar
- En tu archivo
build.gradle
a nivel de proyecto, asegúrate de incluir el repositorio Maven de Google en las seccionesbuildscript
yallprojects
. - Agrega las dependencias para las bibliotecas de Android de ML Kit al archivo Gradle a nivel de la app de tu módulo, que suele ser
app/build.gradle
:
dependencies {
implementation 'com.google.mlkit:segmentation-selfie:16.0.0-beta6'
}
1. Crea una instancia de Segmenter
Opciones del segmentador
Para segmentar una imagen, primero crea una instancia de Segmenter
especificando las siguientes opciones.
Modo de detector
Segmenter
funciona en dos modos. Asegúrate de elegir la que coincida con tu caso de uso.
STREAM_MODE (default)
Este modo está diseñado para transmitir fotogramas desde videos o cámaras. En este modo, el segmentador aprovechará los resultados de fotogramas anteriores para generar resultados de segmentación más fluidos.
SINGLE_IMAGE_MODE
Este modo está diseñado para imágenes individuales que no están relacionadas. En este modo, el segmentador procesará cada imagen de forma independiente, sin suavizar los fotogramas.
Habilita la máscara de tamaño sin procesar
Le pide al segmentador que muestre la máscara de tamaño sin procesar que coincida con el tamaño de salida del modelo.
El tamaño de la máscara sin procesar (p.ej., 256 × 256) suele ser más pequeño que el tamaño de la imagen de entrada. Llama a SegmentationMask#getWidth()
y SegmentationMask#getHeight()
para obtener el tamaño de la máscara cuando habilites esta opción.
Sin especificar esta opción, el segmentador volverá a escalar la máscara sin procesar para que coincida con el tamaño de la imagen de entrada. Considera usar esta opción si deseas aplicar una lógica de cambio de escala personalizada o si no es necesario cambiar la escala para tu caso de uso.
Especifica las opciones del segmentador:
val options = SelfieSegmenterOptions.Builder() .setDetectorMode(SelfieSegmenterOptions.STREAM_MODE) .enableRawSizeMask() .build()
SelfieSegmenterOptions options = new SelfieSegmenterOptions.Builder() .setDetectorMode(SelfieSegmenterOptions.STREAM_MODE) .enableRawSizeMask() .build();
Crea una instancia de Segmenter
. Pasa las opciones que especificaste:
val segmenter = Segmentation.getClient(options)
Segmenter segmenter = Segmentation.getClient(options);
2. Prepara la imagen de entrada
Para realizar la segmentación de una imagen, crea un objeto InputImage
a partir de un Bitmap
, una media.Image
, un ByteBuffer
, un array de bytes o un archivo ubicado en el dispositivo.
Puedes crear un objeto InputImage
a partir de diferentes fuentes, que se explican a continuación.
Usa un media.Image
Para crear un objeto InputImage
a partir de un objeto media.Image
, como cuando se captura una imagen con la cámara de un dispositivo, pasa el objeto media.Image
y la rotación de la imagen a InputImage.fromMediaImage()
.
Si usas la biblioteca
CameraX, las clases OnImageCapturedListener
y
ImageAnalysis.Analyzer
calculan el valor de rotación
por ti.
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 // ... } } }
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 te proporcione el grado de 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:
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 }
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()
:
val image = InputImage.fromMediaImage(mediaImage, rotation)
InputImage image = InputImage.fromMediaImage(mediaImage, rotation);
Usa un URI de archivo
Para crear un objeto InputImage
a partir de un URI de archivo, pasa el contexto de la app y el URI del archivo a InputImage.fromFilePath()
. Esto es útil cuando usas un intent ACTION_GET_CONTENT
para solicitarle al usuario que seleccione una imagen de su app de galería.
val image: InputImage try { image = InputImage.fromFilePath(context, uri) } catch (e: IOException) { e.printStackTrace() }
InputImage image;
try {
image = InputImage.fromFilePath(context, uri);
} catch (IOException e) {
e.printStackTrace();
}
Usa ByteBuffer
o ByteArray
Para crear un objeto InputImage
a partir de un ByteBuffer
o un ByteArray
, primero calcula el grado de rotación de la imagen como se describió anteriormente en la entrada media.Image
.
Luego, crea el objeto InputImage
con el búfer o el array, junto con la altura,
el ancho, el formato de codificación de color y el grado de rotación de la imagen:
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 )
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 objeto InputImage
a partir de un objeto Bitmap
, realiza la siguiente declaración:
val image = InputImage.fromBitmap(bitmap, 0)
InputImage image = InputImage.fromBitmap(bitmap, rotationDegree);
La imagen está representada por un objeto Bitmap
junto con los grados de rotación.
3. Procesa la imagen
Pasa el objeto InputImage
preparado al método process
de Segmenter
.
Task<SegmentationMask> result = segmenter.process(image) .addOnSuccessListener { results -> // Task completed successfully // ... } .addOnFailureListener { e -> // Task failed with an exception // ... }
Task<SegmentationMask> result = segmenter.process(image) .addOnSuccessListener( new OnSuccessListener<SegmentationMask>() { @Override public void onSuccess(SegmentationMask mask) { // Task completed successfully // ... } }) .addOnFailureListener( new OnFailureListener() { @Override public void onFailure(@NonNull Exception e) { // Task failed with an exception // ... } });
4. Obtén el resultado de la segmentación
Puedes obtener el resultado de la segmentación de la siguiente manera:
val mask = segmentationMask.getBuffer() val maskWidth = segmentationMask.getWidth() val maskHeight = segmentationMask.getHeight() for (val y = 0; y < maskHeight; y++) { for (val x = 0; x < maskWidth; x++) { // Gets the confidence of the (x,y) pixel in the mask being in the foreground. val foregroundConfidence = mask.getFloat() } }
ByteBuffer mask = segmentationMask.getBuffer(); int maskWidth = segmentationMask.getWidth(); int maskHeight = segmentationMask.getHeight(); for (int y = 0; y < maskHeight; y++) { for (int x = 0; x < maskWidth; x++) { // Gets the confidence of the (x,y) pixel in the mask being in the foreground. float foregroundConfidence = mask.getFloat(); } }
Para ver un ejemplo completo de cómo usar los resultados de la segmentación, consulta la muestra de la guía de inicio rápido del Kit de AA.
Sugerencias para mejorar el rendimiento
La calidad de los resultados depende de la calidad de la imagen de entrada:
- Para que ML Kit obtenga un resultado de segmentación preciso, la imagen debe tener al menos 256 × 256 píxeles.
- Un enfoque de imagen deficiente también puede afectar la exactitud. Si no obtienes resultados aceptables, pídele al usuario que vuelva a capturar la imagen.
Si quieres usar la segmentación en una aplicación en tiempo real, sigue estos lineamientos para lograr las mejores velocidades de fotogramas:
- Utiliza
STREAM_MODE
. - Considera capturar imágenes con una resolución más baja. Sin embargo, también ten en cuenta los requisitos de dimensiones de imágenes de esta API.
- Considera habilitar la opción de máscara de tamaño sin procesar y combinar toda la lógica de cambio de escala. Por ejemplo, en lugar de permitir que la API vuelva a escalar la máscara para que coincida primero con el tamaño de la imagen de entrada y, luego, volver a escalarla para que coincida con el tamaño de la vista para la visualización, solo solicita la máscara de tamaño sin procesar y combina estos dos pasos en uno.
- Si usas la API de
Camera
ocamera2
, limita las llamadas al detector. Si hay un fotograma de video nuevo disponible mientras se ejecuta el detector, ignora ese fotograma. Consulta la claseVisionProcessorBase
de la app de ejemplo de la guía de inicio rápido para ver un ejemplo. - Si usas la API de
CameraX
, asegúrate de que la estrategia de contrapresión esté configurada en su valor predeterminadoImageAnalysis.STRATEGY_KEEP_ONLY_LATEST
. Esto garantiza que solo se entregue una imagen para el análisis a la vez. Si se producen más imágenes cuando el analizador está ocupado, se descartarán automáticamente y no se pondrán en cola para la publicación. Una vez que se cierre la imagen que se está analizando llamando a ImageProxy.close(), se entregará la siguiente imagen más reciente. - Si usas la salida del detector para superponer gráficos en la imagen de entrada, primero obtén el resultado de ML Kit y, luego, procesa la imagen y la superposición en un solo paso. Esto se renderiza en la superficie de visualización solo una vez por cada fotograma de entrada. Consulta las clases
CameraSourcePreview
yGraphicOverlay
en la app de ejemplo de la guía de inicio rápido para ver un ejemplo. - 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 formatoImageFormat.NV21
.