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.

Provalo

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 in precedenza, 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 la seguente dichiarazione al file AndroidManifest.xml dell'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 abiliti i download del modello al momento dell'installazione o non richiedi un download esplicito, il modello verrà scaricato alla prima esecuzione dello strumento di segmentazione. Le richieste effettuate 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, da un array di byte o da un file sul dispositivo.

Puoi creare un oggetto InputImage da origini diverse, spiegate di seguito.

Utilizzo di un media.Image

Per creare un oggetto InputImage da un oggetto media.Image, ad esempio quando acquisisci un'immagine dalla fotocamera di un dispositivo, passa l'oggetto media.Image e la rotazione dell'immagine a InputImage.fromMediaImage().

Se utilizzi la libreria CameraX, le classi OnImageCapturedListener e ImageAnalysis.Analyzer calcolano il valore della rotazione 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 non utilizzi una raccolta di videocamere che ti fornisce il grado di rotazione dell'immagine, puoi calcolarlo in base al grado di rotazione e all'orientamento del sensore 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 il valore del grado di rotazione a InputImage.fromMediaImage():

Kotlin

val image = InputImage.fromMediaImage(mediaImage, rotation)

Java

InputImage image = InputImage.fromMediaImage(mediaImage, rotation);

Utilizzo di un URI del file

Per creare un oggetto InputImage da un URI file, passa il contesto dell'app e l'URI del file a InputImage.fromFilePath(). Questo è utile quando utilizzi un intent ACTION_GET_CONTENT per richiedere 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 un oggetto InputImage da un elemento ByteBuffer o ByteArray, devi prima calcolare il grado di rotazione dell'immagine come descritto in precedenza per l'input media.Image. Poi, crea l'oggetto InputImage con il buffer o l'array, insieme ad altezza, larghezza, formato di codifica del colore e grado di rotazione dell'immagine:

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 un oggetto InputImage 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 segue:

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 dallo sfondo.

La chiamata a enableForegroundConfidenceMask() nelle opzioni consente di recuperare in un secondo momento la maschera in primo piano richiamando getForegroundMask() sull'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.

La chiamata a enableForegroundBitmap() nelle opzioni consente di recuperare in un secondo momento la bitmap in primo piano richiamando getForegroundBitmap() sull'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 utilizzare SubjectResultOptions per attivare la maschera di confidenza per ogni soggetto in primo piano nel seguente modo:

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 chiamando getClient() e passando le opzioni come parametro:

Kotlin

val segmenter = SubjectSegmentation.getClient(options)

Java

SubjectSegmenter segmenter = SubjectSegmentation.getClient(options);

3. Elabora un'immagine

Passa l'oggetto InputImage preparato 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 dell'immagine a getForegroundConfidenceMask() nel seguente modo:

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

Allo stesso modo, puoi recuperare la maschera per i soggetti segmentati richiamando getConfidenceMask() su ciascun soggetto 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 delle inferenze successive a causa dell'inizializzazione del modello. Se la bassa latenza è fondamentale, valuta la possibilità di chiamare in anticipo un'inferenza "fittizia".

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.