Utilizza ML Kit per aggiungere facilmente funzionalità di segmentazione dei soggetti alla tua app.
Funzionalità | Dettagli |
---|---|
Nome SDK | play-services-mlkit-subject-segmentation |
Implementazione | Non in bundle: il modello viene scaricato in modo dinamico utilizzando Google Play Services. |
Impatto sulle dimensioni dell'app | Aumento delle dimensioni di circa 200 kB. |
Tempo di inizializzazione | Gli utenti potrebbero dover attendere il download del modello prima del primo utilizzo. |
Prova
- Prova l'app di esempio per per vedere un esempio di utilizzo di questa API.
Prima di iniziare
- Nel file
build.gradle
a livello di progetto, assicurati di includere il Repository Maven di Google in entrambe le sezionibuildscript
eallprojects
. - Aggiungi la dipendenza per la libreria di segmentazione dei soggetti del kit ML al file gradle a livello di app del tuo modulo, che in genere è
app/build.gradle
:
dependencies {
implementation 'com.google.android.gms:play-services-mlkit-subject-segmentation:16.0.0-beta1'
}
Come indicato sopra, il modello è fornito da Google Play Services.
Puoi configurare l'app in modo che scarichi automaticamente il modello sul dispositivo
dopo l'installazione dell'app dal Play Store. Per farlo, aggiungi quanto segue:
dichiarazione al file AndroidManifest.xml
della tua 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>
Puoi anche verificare esplicitamente la disponibilità del modello e richiedere il download tramite Google Play Services con l'API ModuleInstallaClient.
Se non attivi i download dei modelli al momento dell'installazione o richiedi un download esplicito il modello viene scaricato la prima volta che si esegue lo strumento di segmentazione. Le tue richieste prima del completamento del download non producono risultati.
1. Prepara l'immagine di input
Per eseguire la segmentazione su un'immagine, crea un oggetto InputImage
da un array di byte Bitmap
, media.Image
, ByteBuffer
, o da un file
del dispositivo.
Puoi creare una InputImage
da diverse origini, ciascuna è spiegata di seguito.
Utilizzo di un media.Image
Per creare una InputImage
da un oggetto media.Image
, ad esempio quando acquisisci un'immagine da un
fotocamera del dispositivo, passa l'oggetto media.Image
e la
rotazione in InputImage.fromMediaImage()
.
Se utilizzi
nella libreria di CameraX, OnImageCapturedListener
e
ImageAnalysis.Analyzer
classi calcolano il valore di rotazione
per te.
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 non utilizzi una raccolta di videocamere che fornisce il grado di rotazione dell'immagine, può calcolarlo in base al grado di rotazione e all'orientamento della fotocamera nel 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; }
Quindi, passa l'oggetto media.Image
e
valore del grado di rotazione su InputImage.fromMediaImage()
:
val image = InputImage.fromMediaImage(mediaImage, rotation)
InputImage image = InputImage.fromMediaImage(mediaImage, rotation);
Utilizzo di un URI del file
Per creare una InputImage
da un URI file, passa il contesto dell'app e l'URI del file a
InputImage.fromFilePath()
. È utile quando
utilizza un intent ACTION_GET_CONTENT
per chiedere all'utente di selezionare
un'immagine dall'app Galleria.
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();
}
Con ByteBuffer
o ByteArray
Per creare una InputImage
oggetto da un valore ByteBuffer
o ByteArray
, prima calcola l'immagine
grado di rotazione come descritto in precedenza per l'input media.Image
.
Quindi, crea l'oggetto InputImage
con il buffer o l'array, insieme al campo
altezza, larghezza, formato di codifica del colore e grado di rotazione:
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 );
Utilizzo di un Bitmap
Per creare una InputImage
oggetto da un oggetto Bitmap
, effettua la seguente dichiarazione:
val image = InputImage.fromBitmap(bitmap, 0)
InputImage image = InputImage.fromBitmap(bitmap, rotationDegree);
L'immagine è rappresentata da un oggetto Bitmap
e da un grado di rotazione.
2. Crea un'istanza di SubjectSegmenter
Definisci le opzioni del segmento di pubblico
Per segmentare l'immagine, crea prima un'istanza di SubjectSegmenterOptions
come
segui:
val options = SubjectSegmenterOptions.Builder() // enable options .build()
SubjectSegmenterOptions options = new SubjectSegmenterOptions.Builder() // enable options .build();
Ecco i dettagli di ciascuna opzione:
Maschera di confidenza in primo piano
La maschera di confidenza in primo piano ti consente di distinguere il soggetto in primo piano sullo sfondo.
Chiama enableForegroundConfidenceMask()
nelle opzioni per recuperarlo in un secondo momento
la maschera in primo piano richiamando getForegroundMask()
sul
Oggetto SubjectSegmentationResult
restituito dopo l'elaborazione dell'immagine.
val options = SubjectSegmenterOptions.Builder() .enableForegroundConfidenceMask() .build()
SubjectSegmenterOptions options = new SubjectSegmenterOptions.Builder() .enableForegroundConfidenceMask() .build();
Bitmap in primo piano
Analogamente, puoi ottenere una bitmap del soggetto in primo piano.
Chiama enableForegroundBitmap()
nelle opzioni per recuperarlo in un secondo momento
la bitmap in primo piano richiamando getForegroundBitmap()
sul
Oggetto SubjectSegmentationResult
restituito dopo l'elaborazione dell'immagine.
val options = SubjectSegmenterOptions.Builder() .enableForegroundBitmap() .build()
SubjectSegmenterOptions options = new SubjectSegmenterOptions.Builder() .enableForegroundBitmap() .build();
Maschera di confidenza di più soggetti
Come per le opzioni in primo piano, puoi usare SubjectResultOptions
per attivare
la maschera di confidenza per ogni soggetto in primo piano come segue:
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 con più soggetti
Allo stesso modo, puoi abilitare la bitmap per ciascun soggetto:
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()
Crea la segmentazione degli oggetti
Dopo aver specificato le opzioni SubjectSegmenterOptions
, crea un
SubjectSegmenter
dell'istanza che chiama getClient()
e passa le opzioni come
:
val segmenter = SubjectSegmentation.getClient(options)
SubjectSegmenter segmenter = SubjectSegmentation.getClient(options);
3. Elabora un'immagine
Supera il InputImage
preparato
al metodo process
di 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. Ottieni il risultato della segmentazione dei soggetti
Recuperare maschere e bitmap in primo piano
Al termine dell'elaborazione, puoi recuperare la maschera in primo piano per la chiamata all'immagine
getForegroundConfidenceMask()
come segue:
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 );
Puoi anche recuperare una bitmap del primo piano dell'immagine chiamando getForegroundBitmap()
:
val foregroundBitmap = result.foregroundBitmap
Bitmap foregroundBitmap = result.getForegroundBitmap();
Recupera maschere e bitmap per ciascun soggetto
Analogamente, puoi recuperare la maschera per i soggetti segmentati richiamando
getConfidenceMask()
su ogni materia come segue:
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 );
Puoi anche accedere alla bitmap di ciascun soggetto segmentato nel seguente modo:
val bitmaps = mutableListOf() for (subject in subjects) { bitmaps.add(subject.bitmap) }
Listbitmaps = new ArrayList<>(); for (Subject subject : subjects) { bitmaps.add(subject.getBitmap()); }
Suggerimenti per migliorare il rendimento
Per ogni sessione dell'app, la prima inferenza è spesso più lenta di quella successiva grazie all'inizializzazione del modello. Se è fondamentale la bassa latenza, chiamando un "fittizio" l'inferenza in anticipo.
La qualità dei risultati dipende dalla qualità dell'immagine di input:
- Affinché ML Kit ottenga un risultato di segmentazione accurato, l'immagine deve essere di almeno 512 x 512 pixel.
- Anche una scarsa messa a fuoco dell'immagine può influire sulla precisione. Se non ottieni risultati accettabili, chiedi all'utente di recuperare l'immagine.