Segmentazione dei soggetti con ML Kit per Android

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

  1. Nel file build.gradle a livello di progetto, assicurati di includere il Repository Maven di Google in entrambe le sezioni buildscript e allprojects.
  2. 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.

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 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:

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;
}

Quindi, passa l'oggetto media.Image e valore del grado di rotazione su InputImage.fromMediaImage():

Kotlin

val image = InputImage.fromMediaImage(mediaImage, rotation)

Java

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.

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();
}

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:

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
);

Utilizzo di un Bitmap

Per creare una InputImage oggetto da un oggetto Bitmap, effettua la seguente dichiarazione:

Kotlin

val image = InputImage.fromBitmap(bitmap, 0)

Java

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:

Kotlin

val options = SubjectSegmenterOptions.Builder()
       // enable options
       .build()

Java

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.

Kotlin

val options = SubjectSegmenterOptions.Builder()
        .enableForegroundConfidenceMask()
        .build()

Java

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.

Kotlin

val options = SubjectSegmenterOptions.Builder()
        .enableForegroundBitmap()
        .build()

Java

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:

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 con più soggetti

Allo stesso modo, puoi abilitare la bitmap per ciascun soggetto:

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()

Crea la segmentazione degli oggetti

Dopo aver specificato le opzioni SubjectSegmenterOptions, crea un'istanza SubjectSegmenter dell'istanza che chiama getClient() e passa le opzioni come :

Kotlin

val segmenter = SubjectSegmentation.getClient(options)

Java

SubjectSegmenter segmenter = SubjectSegmentation.getClient(options);

3. Elabora un'immagine

Supera la InputImage preparata al metodo process di 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. 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:

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
);

Puoi anche recuperare una bitmap del primo piano dell'immagine chiamando getForegroundBitmap():

Kotlin

val foregroundBitmap = result.foregroundBitmap

Java

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:

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

List subjects = 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:

Kotlin

val bitmaps = mutableListOf()
for (subject in subjects) {
  bitmaps.add(subject.bitmap)
}

Java

List bitmaps = 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.