Use o kit de ML para adicionar facilmente recursos de segmentação de assuntos ao seu app.
Recurso | Detalhes |
---|---|
Nome do SDK | play-services-mlkit-subject-segmentation |
Implementação | Desvinculado: o modelo é baixado dinamicamente usando o Google Play Services. |
Impacto no tamanho do app | Aumento de tamanho de ~200 KB. |
Tempo de inicialização | Talvez os usuários 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.gradle
no nível do projeto, inclua o repositório Maven do Google nas seçõesbuildscript
eallprojects
. - Adicione a dependência da biblioteca de segmentação de assuntos 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 fazer o download automático do modelo no dispositivo
depois que ele for instalado na 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 um download explícito, o modelo será transferido na primeira vez 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 objeto InputImage
de diferentes origens. Cada uma é explicada abaixo.
Como usar um media.Image
Para criar um objeto InputImage
usando 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.
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 // ... } } }
Se você não usar uma biblioteca de câmera que ofereça o grau de rotação da imagem, será possível calcular usando o grau de rotação do dispositivo e a orientação do sensor da câmera:
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; }
Em seguida, transmita o objeto media.Image
e o
valor do grau de rotação para InputImage.fromMediaImage()
:
val image = InputImage.fromMediaImage(mediaImage, rotation)
InputImage image = InputImage.fromMediaImage(mediaImage, rotation);
Como usar um URI de arquivo
Para criar um objeto InputImage
com base no 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.
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();
}
Como usar ByteBuffer
ou ByteArray
Para criar um objeto InputImage
usando um ByteBuffer
ou um 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, com a altura,
a largura, o formato de codificação de cores e o grau de rotação da imagem:
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 );
Como usar um Bitmap
Para criar um objeto InputImage
usando um objeto Bitmap
, faça a seguinte declaração:
val image = InputImage.fromBitmap(bitmap, 0)
InputImage image = InputImage.fromBitmap(bitmap, rotationDegree);
A imagem é representada por um objeto Bitmap
com os graus de rotação.
2. Criar uma instância de SubjectSegmenter
Definir as opções de segmentação
Para segmentar sua imagem, primeiro crie uma instância de SubjectSegmenterOptions
da seguinte
forma:
val options = SubjectSegmenterOptions.Builder() // enable options .build()
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 do primeiro plano permite distinguir o assunto do 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.
val options = SubjectSegmenterOptions.Builder() .enableForegroundConfidenceMask() .build()
SubjectSegmenterOptions options = new SubjectSegmenterOptions.Builder() .enableForegroundConfidenceMask() .build();
Bitmap de primeiro plano
Da mesma forma, você também pode receber um bitmap do objeto em primeiro plano.
Chamar enableForegroundBitmap()
nas opções permite recuperar
o bitmap em primeiro plano chamando getForegroundBitmap()
no
objeto SubjectSegmentationResult
retornado após o processamento da imagem.
val options = SubjectSegmenterOptions.Builder() .enableForegroundBitmap() .build()
SubjectSegmenterOptions options = new SubjectSegmenterOptions.Builder() .enableForegroundBitmap() .build();
Máscara de confiança de vários sujeitos
Assim como nas opções em primeiro plano, é possível usar SubjectResultOptions
para ativar
a máscara de confiança para cada sujeito em primeiro plano da seguinte maneira:
val subjectResultOptions = SubjectSegmenterOptions.SubjectResultOptions.Builder() .enableConfidenceMask() .build() val options = SubjectSegmenterOptions.Builder() .enableMultipleSubjects(subjectResultOptions) .build()
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 para cada assunto:
val subjectResultOptions = SubjectSegmenterOptions.SubjectResultOptions.Builder() .enableSubjectBitmap() .build() val options = SubjectSegmenterOptions.Builder() .enableMultipleSubjects(subjectResultOptions) .build()
SubjectResultOptions subjectResultOptions = new SubjectSegmenterOptions.SubjectResultOptions.Builder() .enableSubjectBitmap() .build() SubjectSegmenterOptions options = new SubjectSegmenterOptions.Builder() .enableMultipleSubjects(subjectResultOptions) .build()
Criar o segmentador de assuntos
Depois de especificar as opções SubjectSegmenterOptions
, crie uma
instância SubjectSegmenter
chamando getClient()
e transmitindo as opções como um
parâmetro:
val segmenter = SubjectSegmentation.getClient(options)
SubjectSegmenter segmenter = SubjectSegmentation.getClient(options);
3. Processar uma imagem
Transmita o objeto InputImage
preparado para o método process
do SubjectSegmenter
:
segmenter.process(inputImage) .addOnSuccessListener { result -> // Task completed successfully // ... } .addOnFailureListener { e -> // Task failed with an exception // ... }
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 do assunto
Extrair máscaras e bitmaps em primeiro plano
Depois de processado, você pode recuperar a máscara de primeiro plano para sua imagem chamando
getForegroundConfidenceMask()
da seguinte maneira:
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 )
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()
:
val foregroundBitmap = result.foregroundBitmap
Bitmap foregroundBitmap = result.getForegroundBitmap();
Extrair máscaras e bitmaps para cada sujeito
Da mesma forma, é possível recuperar a máscara para os assuntos segmentados chamando
getConfidenceMask()
em cada assunto da seguinte maneira:
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 )
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 objeto segmentado da seguinte maneira:
val bitmaps = mutableListOf() for (subject in subjects) { bitmaps.add(subject.bitmap) }
Listbitmaps = new ArrayList<>(); for (Subject subject : subjects) { bitmaps.add(subject.getBitmap()); }
Dicas para melhorar a performance
Em cada sessão do app, a primeira inferência geralmente é mais lenta do que as inferências posteriores 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 consiga um resultado de segmentação preciso, a imagem precisa ter pelo menos 512 x 512 pixels.
- Uma imagem com foco inadequado também pode afetar a precisão. Se os resultados não forem aceitáveis, peça para o usuário recapturar a imagem.