O Kit de ML fornece um SDK otimizado para a segmentação de selfies.
Os recursos do segmentador de selfie são vinculados estaticamente ao seu app no tempo de criação. Isso aumenta o tamanho do download do app em cerca de 4,5 MB, e a latência da API pode variar de 25 ms a 65 ms, dependendo do tamanho da imagem de entrada, conforme medido em um Pixel 4.
Testar
- Teste o app de exemplo para ver um exemplo de uso da API.
Antes de começar
- No arquivo
build.gradle
no nível do projeto, inclua o repositório Maven do Google nas seçõesbuildscript
eallprojects
. - Adicione as dependências das bibliotecas do Android do Kit de ML ao arquivo Gradle do módulo no nível do app, que geralmente é
app/build.gradle
:
dependencies {
implementation 'com.google.mlkit:segmentation-selfie:16.0.0-beta5'
}
1. Criar uma instância do segmentador
Opções do segmentador
Para fazer segmentação em uma imagem, primeiro crie uma instância de Segmenter
especificando as opções a seguir.
Modo de detector
O Segmenter
opera de dois modos. Certifique-se de escolher aquela que corresponde ao seu caso de uso.
STREAM_MODE (default)
Esse modo foi criado para transmitir frames de vídeo ou de câmera. Nesse modo, o segmentador aproveita os resultados de frames anteriores para retornar resultados de segmentação mais suaves.
SINGLE_IMAGE_MODE
Esse modo foi criado para imagens únicas não relacionadas. Nesse modo, o segmentador processa cada imagem de maneira independente, sem suavização dos frames.
Ativar máscara de tamanho bruto
Solicita ao segmentador para retornar a máscara de tamanho bruto que corresponda ao tamanho da saída do modelo.
O tamanho da máscara bruta (por exemplo, 256 x 256) geralmente é menor que o tamanho da imagem de entrada. Chame SegmentationMask#getWidth()
e SegmentationMask#getHeight()
para saber o tamanho da máscara ao ativar essa opção.
Sem especificar essa opção, o segmentador redimensionará a máscara bruta para corresponder ao tamanho da imagem de entrada. Considere usar essa opção se você quiser aplicar a lógica de redimensionamento personalizada ou se o redimensionamento não for necessário para seu caso de uso.
Especifique as opções do segmentador:
Kotlin
val options = SelfieSegmenterOptions.Builder() .setDetectorMode(SelfieSegmenterOptions.STREAM_MODE) .enableRawSizeMask() .build()
Java
SelfieSegmenterOptions options = new SelfieSegmenterOptions.Builder() .setDetectorMode(SelfieSegmenterOptions.STREAM_MODE) .enableRawSizeMask() .build();
Crie uma instância de Segmenter
. Transmita as opções especificadas:
Kotlin
val segmenter = Segmentation.getClient(options)
Java
Segmenter segmenter = Segmentation.getClient(options);
2. Preparar a imagem de entrada
Para realizar a segmentação em uma imagem, crie um objeto InputImage
usando Bitmap
, media.Image
, ByteBuffer
, matriz de bytes ou um arquivo
no dispositivo.
Você pode criar um objeto InputImage
de diferentes origens, cada uma explicada abaixo.
Como usar um media.Image
Para criar um objeto InputImage
com base em um objeto media.Image
, como quando você captura uma imagem da câmera de um dispositivo, transmita o objeto media.Image
e a rotação da imagem para InputImage.fromMediaImage()
.
Se você usar a biblioteca
CameraX, as classes OnImageCapturedListener
e
ImageAnalysis.Analyzer
vão calcular o valor de rotação
automaticamente.
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 // ... } } }
Se você não usar uma biblioteca de câmera que ofereça o grau de rotação da imagem, será possível calculá-lo usando o grau de rotação do dispositivo e a orientação do sensor da câmera:
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; }
Em seguida, transmita o objeto media.Image
e o
valor do grau de rotação para InputImage.fromMediaImage()
:
Kotlin
val image = InputImage.fromMediaImage(mediaImage, rotation)
Java
InputImage image = InputImage.fromMediaImage(mediaImage, rotation);
Usar um URI de arquivo
Para criar um objeto InputImage
usando o URI de um arquivo, transmita o contexto do app e o URI do arquivo para
InputImage.fromFilePath()
. Isso é útil ao usar
uma intent ACTION_GET_CONTENT
para solicitar que o usuário selecione
uma imagem do app de galeria dele.
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(); }
Como usar ByteBuffer
ou ByteArray
Para criar um objeto InputImage
com base em ByteBuffer
ou ByteArray
, primeiro calcule o grau de rotação da imagem conforme descrito anteriormente para a entrada de media.Image
.
Em seguida, crie o objeto InputImage
com o buffer ou a matriz, junto com a altura, a largura, o formato de codificação de cores e o grau de rotação da imagem:
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 );
Como usar um Bitmap
Para criar um objeto InputImage
com base em um objeto Bitmap
, faça a seguinte declaração:
Kotlin
val image = InputImage.fromBitmap(bitmap, 0)
Java
InputImage image = InputImage.fromBitmap(bitmap, rotationDegree);
A imagem é representada por um objeto Bitmap
com os graus de rotação.
3. Processar a imagem
Transmita o objeto InputImage
preparado para o método process
do Segmenter
.
Kotlin
Task<SegmentationMask> result = segmenter.process(image) .addOnSuccessListener { results -> // Task completed successfully // ... } .addOnFailureListener { e -> // Task failed with an exception // ... }
Java
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. Acessar o resultado da segmentação
É possível ver o resultado da segmentação da seguinte maneira:
Kotlin
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() } }
Java
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 um exemplo completo de como usar os resultados da segmentação, consulte a amostra do guia de início rápido do Kit de ML.
Dicas para melhorar o desempenho
A qualidade dos resultados depende da qualidade da imagem de entrada:
- Para que o Kit de ML gere um resultado de segmentação preciso, a imagem precisa ter pelo menos 256 x 256 pixels.
- Uma imagem com foco inadequado também pode afetar a precisão. Se você não conseguir resultados aceitáveis, peça ao usuário para recapturar a imagem.
Se você quiser usar a segmentação em um aplicativo em tempo real, siga estas diretrizes para ter os melhores frame rates:
- Use
STREAM_MODE
. - Capture imagens em uma resolução mais baixa. No entanto, lembre-se também dos requisitos de dimensão de imagem dessa API.
- Considere ativar a opção de máscara de tamanho bruto e combinar toda a lógica de redimensionamento. Por exemplo, em vez de permitir que a API redimensione a máscara para corresponder ao tamanho da imagem de entrada e depois redimensione novamente para corresponder ao tamanho da visualização para exibição, solicite a máscara de tamanho bruto e combine essas duas etapas em uma.
- Se você usa a API
Camera
oucamera2
, limite as chamadas para o detector. Se um novo frame de vídeo ficar disponível enquanto o detector estiver em execução, descarte esse frame. Consulte a classeVisionProcessorBase
no app de amostra do guia de início rápido para conferir um exemplo. - Se você usar a API
CameraX
, verifique se a estratégia de pressão de retorno está definida com o valor padrãoImageAnalysis.STRATEGY_KEEP_ONLY_LATEST
. Isso garante que apenas uma imagem será enviada para análise por vez. Se mais imagens forem produzidas quando o analisador estiver ocupado, elas serão descartadas automaticamente e não serão enfileiradas para entrega. Quando a imagem analisada é fechada chamando ImageProxy.close(), a próxima imagem mais recente é entregue. - Se você usar a saída do detector para sobrepor elementos gráficos na imagem de entrada, primeiro acesse o resultado do Kit de ML e, em seguida, renderize a imagem e a sobreposição em uma única etapa. Isso é renderizado na superfície de exibição
apenas uma vez para cada frame de entrada. Consulte as classes
CameraSourcePreview
eGraphicOverlay
no app de amostra do guia de início rápido para conferir um exemplo. - 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 formatoImageFormat.NV21
.