Use o Kit de ML para adicionar recursos de segmentação de assunto ao seu app.
| Recurso | Detalhes |
|---|---|
| Nome do SDK | play-services-mlkit-subject-segmentation |
| Implementação | Não agrupada: o modelo é baixado dinamicamente usando o Google Play Services. |
| Impacto no tamanho do app | Aumento de tamanho de aproximadamente 200 KB. |
| Tempo de inicialização | Os usuários talvez precisem esperar o download do modelo antes do primeiro uso. |
Faça um teste
- Teste o app de exemplo para conferir um exemplo de uso dessa API.
Antes de começar
- No arquivo
build.gradlepara envolvidos no projeto, inclua o repositório Maven do Google nas seçõesbuildscripteallprojects. - Adicione a dependência da biblioteca de segmentação de assunto do Kit de ML ao arquivo Gradle do módulo no nível do app, que geralmente é
app/build.gradle:
dependencies {
implementation 'com.google.android.gms:play-services-mlkit-subject-segmentation:16.0.0-beta1'
}
Como mencionado acima, o modelo é fornecido pelo Google Play Services.
É possível configurar o app para baixar automaticamente o modelo para o dispositivo depois que ele for instalado na Google Play Store. Para fazer isso, adicione a seguinte declaração ao arquivo AndroidManifest.xml do app:
<application ...>
...
<meta-data
android:name="com.google.mlkit.vision.DEPENDENCIES"
android:value="subject_segment" >
<!-- To use multiple models: android:value="subject_segment,model2,model3" -->
</application>
Também é possível verificar explicitamente a disponibilidade do modelo e solicitar o download pelo Google Play Services com a API ModuleInstallClient.
Se você não ativar os downloads do modelo no momento da instalação ou solicitar o download explícito, o modelo será baixado na primeira vez em que você executar o segmentador. As solicitações feitas antes da conclusão do download não produzem resultados.
1. 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.
É possível criar um InputImage
objeto de diferentes fontes, cada uma explicada abaixo.
Como usar um media.Image
Para criar um InputImage
objeto 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
CameraX biblioteca, as OnImageCapturedListener e
ImageAnalysis.Analyzer classes vão calcular o valor de rotação
para você.
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 forneça o grau de rotação da imagem, você poderá calculá-lo com base no grau de rotação do dispositivo e na orientação do sensor da câmera no 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; }
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);
Como usar um URI de arquivo
Para criar um InputImage
objeto 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 no 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 um ByteBuffer ou ByteArray
Para criar um InputImage
objeto com base em um ByteBuffer ou um ByteArray, primeiro calcule o grau de rotação da imagem
conforme descrito anteriormente para a entrada media.Image.
Em seguida, crie o objeto InputImage com o buffer ou a matriz, 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 InputImage
objeto com base em um Bitmap objeto, 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 graus de rotação.
2. Criar uma instância de SubjectSegmenter
Definir as opções do segmentador
Para segmentar a imagem, primeiro crie uma instância de SubjectSegmenterOptions da seguinte maneira:
Kotlin
val options = SubjectSegmenterOptions.Builder()
// enable options
.build()Java
SubjectSegmenterOptions options = new SubjectSegmenterOptions.Builder()
// enable options
.build();Confira os detalhes de cada opção:
Máscara de confiança em primeiro plano
A máscara de confiança em primeiro plano permite distinguir o assunto em primeiro plano do plano de fundo.
Chamar enableForegroundConfidenceMask() nas opções permite recuperar a máscara de primeiro plano chamando getForegroundMask() no objeto SubjectSegmentationResult retornado após o processamento da imagem.
Kotlin
val options = SubjectSegmenterOptions.Builder() .enableForegroundConfidenceMask() .build()
Java
SubjectSegmenterOptions options = new SubjectSegmenterOptions.Builder() .enableForegroundConfidenceMask() .build();
Bitmap de primeiro plano
Da mesma forma, também é possível receber um bitmap do assunto em primeiro plano.
Chamar enableForegroundBitmap() nas opções permite recuperar o bitmap de primeiro plano chamando getForegroundBitmap() no objeto SubjectSegmentationResult retornado após o processamento da imagem.
Kotlin
val options = SubjectSegmenterOptions.Builder() .enableForegroundBitmap() .build()
Java
SubjectSegmenterOptions options = new SubjectSegmenterOptions.Builder() .enableForegroundBitmap() .build();
Máscara de confiança de vários assuntos
Assim como para as opções de primeiro plano, é possível usar SubjectResultOptions para ativar a máscara de confiança de cada assunto em primeiro plano da seguinte maneira:
Kotlin
val subjectResultOptions = SubjectSegmenterOptions.SubjectResultOptions.Builder()
.enableConfidenceMask()
.build()
val options = SubjectSegmenterOptions.Builder()
.enableMultipleSubjects(subjectResultOptions)
.build()Java
SubjectResultOptions subjectResultOptions =
new SubjectSegmenterOptions.SubjectResultOptions.Builder()
.enableConfidenceMask()
.build()
SubjectSegmenterOptions options = new SubjectSegmenterOptions.Builder()
.enableMultipleSubjects(subjectResultOptions)
.build()Bitmap de vários assuntos
Da mesma forma, é possível ativar o bitmap de cada assunto:
Kotlin
val subjectResultOptions = SubjectSegmenterOptions.SubjectResultOptions.Builder()
.enableSubjectBitmap()
.build()
val options = SubjectSegmenterOptions.Builder()
.enableMultipleSubjects(subjectResultOptions)
.build()Java
SubjectResultOptions subjectResultOptions =
new SubjectSegmenterOptions.SubjectResultOptions.Builder()
.enableSubjectBitmap()
.build()
SubjectSegmenterOptions options = new SubjectSegmenterOptions.Builder()
.enableMultipleSubjects(subjectResultOptions)
.build()Criar o segmentador de assunto
Depois de especificar as opções SubjectSegmenterOptions, crie uma
SubjectSegmenter instância chamando getClient() e transmitindo as opções como um
parâmetro:
Kotlin
val segmenter = SubjectSegmentation.getClient(options)
Java
SubjectSegmenter segmenter = SubjectSegmentation.getClient(options);
3. Processar uma imagem
Transmita o InputImage
objeto para o método process do SubjectSegmenter:
Kotlin
segmenter.process(inputImage) .addOnSuccessListener { result -> // Task completed successfully // ... } .addOnFailureListener { e -> // Task failed with an exception // ... }
Java
segmenter.process(inputImage) .addOnSuccessListener(new OnSuccessListener() { @Override public void onSuccess(SubjectSegmentationResult result) { // Task completed successfully // ... } }) .addOnFailureListener(new OnFailureListener() { @Override public void onFailure(@NonNull Exception e) { // Task failed with an exception // ... } });
4. Receber o resultado da segmentação de assunto
Recuperar máscaras e bitmaps de primeiro plano
Depois de processada, é possível recuperar a máscara de primeiro plano da imagem chamando getForegroundConfidenceMask() da seguinte maneira:
Kotlin
val colors = IntArray(image.width * image.height) val foregroundMask = result.foregroundConfidenceMask for (i in 0 until image.width * image.height) { if (foregroundMask[i] > 0.5f) { colors[i] = Color.argb(128, 255, 0, 255) } } val bitmapMask = Bitmap.createBitmap( colors, image.width, image.height, Bitmap.Config.ARGB_8888 )
Java
int[] colors = new int[image.getWidth() * image.getHeight()]; FloatBuffer foregroundMask = result.getForegroundConfidenceMask(); for (int i = 0; i < image.getWidth() * image.getHeight(); i++) { if (foregroundMask.get() > 0.5f) { colors[i] = Color.argb(128, 255, 0, 255); } } Bitmap bitmapMask = Bitmap.createBitmap( colors, image.getWidth(), image.getHeight(), Bitmap.Config.ARGB_8888 );
Também é possível recuperar um bitmap do primeiro plano da imagem chamando getForegroundBitmap():
Kotlin
val foregroundBitmap = result.foregroundBitmap
Java
Bitmap foregroundBitmap = result.getForegroundBitmap();
Recuperar máscaras e bitmaps de cada assunto
Da mesma forma, é possível recuperar a máscara dos assuntos segmentados chamando getConfidenceMask() em cada assunto da seguinte maneira:
Kotlin
val subjects = result.subjects val colors = IntArray(image.width * image.height) for (subject in subjects) { val mask = subject.confidenceMask for (i in 0 until subject.width * subject.height) { val confidence = mask[i] if (confidence > 0.5f) { colors[image.width * (subject.startY - 1) + subject.startX] = Color.argb(128, 255, 0, 255) } } } val bitmapMask = Bitmap.createBitmap( colors, image.width, image.height, Bitmap.Config.ARGB_8888 )
Java
Listsubjects = result.getSubjects(); int[] colors = new int[image.getWidth() * image.getHeight()]; for (Subject subject : subjects) { FloatBuffer mask = subject.getConfidenceMask(); for (int i = 0; i < subject.getWidth() * subject.getHeight(); i++) { float confidence = mask.get(); if (confidence > 0.5f) { colors[width * (subject.getStartY() - 1) + subject.getStartX()] = Color.argb(128, 255, 0, 255); } } } Bitmap bitmapMask = Bitmap.createBitmap( colors, image.width, image.height, Bitmap.Config.ARGB_8888 );
Também é possível acessar o bitmap de cada assunto segmentado da seguinte maneira:
Kotlin
val bitmaps = mutableListOf() for (subject in subjects) { bitmaps.add(subject.bitmap) }
Java
Listbitmaps = new ArrayList<>(); for (Subject subject : subjects) { bitmaps.add(subject.getBitmap()); }
Dicas para melhorar a performance
Para cada sessão do app, a primeira inferência geralmente é mais lenta do que as inferências subsequentes devido à inicialização do modelo. Se a baixa latência for essencial, considere chamar uma inferência "fictícia" com antecedência.
A qualidade dos resultados depende da qualidade da imagem de entrada:
- Para que o Kit de ML receba um resultado de segmentação preciso, a imagem precisa ter pelo menos 512 x 512 pixels.
- O foco inadequado da imagem também pode afetar a precisão. Se você não receber resultados aceitáveis, peça para o usuário recapturar a imagem.