Segmentation des sujets avec ML Kit pour Android

Utilisez ML Kit pour ajouter facilement des fonctionnalités de segmentation par sujet à votre application.

Fonctionnalité Détails
Nom du SDK play-services-mlkit-subject-segmentation
Implémentation Dégroupé: le modèle est téléchargé de manière dynamique à l'aide des services Google Play.
Impact sur la taille de l'application Augmentation de la taille d'environ 200 Ko.
Délai d'initialisation Les utilisateurs devront peut-être attendre la fin du téléchargement du modèle avant de l'utiliser pour la première fois.

Essayer

Avant de commencer

  1. Dans le fichier build.gradle au niveau du projet, veillez à inclure le dépôt Maven de Google à la fois dans vos sections buildscript et allprojects.
  2. Ajoutez la dépendance de la bibliothèque de segmentation de l'objet de ML Kit au fichier Gradle au niveau de l'application de votre module, qui est généralement app/build.gradle:
dependencies {
   implementation 'com.google.android.gms:play-services-mlkit-subject-segmentation:16.0.0-beta1'
}

Comme indiqué ci-dessus, le modèle est fourni par les services Google Play. Vous pouvez configurer votre application pour télécharger automatiquement le modèle sur l'appareil après l'installation de votre application depuis le Play Store. Pour ce faire, ajoutez le code suivant : dans le fichier AndroidManifest.xml de votre application:

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

Vous pouvez également vérifier explicitement la disponibilité du modèle et demander un téléchargement via les services Google Play à l'aide de l'API ModuleInstallClient.

Si vous n'activez pas les téléchargements de modèles au moment de l'installation ou si vous ne demandez pas de téléchargement explicite le modèle est téléchargé la première fois que vous exécutez le segmenteur. Demandes que vous effectuez avant la fin du téléchargement ne produit aucun résultat.

1. Préparer l'image d'entrée

Pour effectuer une segmentation sur une image, créez un objet InputImage. à partir d'un Bitmap, d'un media.Image, d'un ByteBuffer, d'un tableau d'octets ou d'un fichier sur l'appareil.

Vous pouvez créer un InputImage de différentes sources. Chacune d'elles est expliquée ci-dessous.

Utiliser un media.Image

Pour créer un InputImage à partir d'un objet media.Image, par exemple lorsque vous capturez une image à partir d'un l'appareil photo de l'appareil, transmettez l'objet media.Image et l'image la rotation sur InputImage.fromMediaImage().

Si vous utilisez les la bibliothèque CameraX, les OnImageCapturedListener et Les classes ImageAnalysis.Analyzer calculent la valeur de rotation pour vous.

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
          // ...
        }
    }
}

Si vous n'utilisez pas de bibliothèque d'appareils photo qui indique le degré de rotation de l'image, le calcul à partir du degré de rotation de l'appareil et de l'orientation de la caméra capteur de l'appareil:

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

Ensuite, transmettez l'objet media.Image et valeur du degré de rotation sur InputImage.fromMediaImage():

Kotlin

val image = InputImage.fromMediaImage(mediaImage, rotation)

Java

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

Utiliser un URI de fichier

Pour créer un InputImage à partir d'un URI de fichier, transmettez le contexte de l'application et l'URI du fichier à InputImage.fromFilePath() Cela est utile lorsque vous Utiliser un intent ACTION_GET_CONTENT pour inviter l'utilisateur à sélectionner une image de son application Galerie.

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

Utiliser un ByteBuffer ou un ByteArray

Pour créer un InputImage d'un objet ByteBuffer ou ByteArray, calculez d'abord l'image degré de rotation décrit précédemment pour l'entrée media.Image. Ensuite, créez l'objet InputImage avec le tampon ou le tableau, ainsi que l'objet image la hauteur, la largeur, le format d'encodage des couleurs et le degré de rotation:

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

Utiliser un Bitmap

Pour créer un InputImage à partir d'un objet Bitmap, effectuez la déclaration suivante:

Kotlin

val image = InputImage.fromBitmap(bitmap, 0)

Java

InputImage image = InputImage.fromBitmap(bitmap, rotationDegree);

L'image est représentée par un objet Bitmap associé à des degrés de rotation.

2. Créer une instance de SubjectSegmenter

Définir les options du segmenteur

Pour segmenter votre image, créez d'abord une instance de SubjectSegmenterOptions en tant que suivre:

Kotlin

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

Java

SubjectSegmenterOptions options = new SubjectSegmenterOptions.Builder()
        // enable options
        .build();

Voici les détails de chaque option:

Masque de confiance au premier plan

Le masque de confiance au premier plan vous permet de distinguer le sujet au premier plan en arrière-plan.

Appelez enableForegroundConfidenceMask() dans les options pour récupérer plus tard le masque de premier plan en appelant getForegroundMask() sur la Objet SubjectSegmentationResult renvoyé après le traitement de l'image.

Kotlin

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

Java

SubjectSegmenterOptions options = new SubjectSegmenterOptions.Builder()
        .enableForegroundConfidenceMask()
        .build();
Bitmap de premier plan

De même, vous pouvez obtenir un bitmap du sujet au premier plan.

Appelez enableForegroundBitmap() dans les options pour récupérer plus tard le bitmap de premier plan en appelant getForegroundBitmap() sur le Objet SubjectSegmentationResult renvoyé après le traitement de l'image.

Kotlin

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

Java

SubjectSegmenterOptions options = new SubjectSegmenterOptions.Builder()
        .enableForegroundBitmap()
        .build();
Masque de confiance multisujet

Comme pour les options de premier plan, vous pouvez utiliser SubjectResultOptions pour activer le masque de confiance pour chaque sujet au premier plan, comme suit:

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 multi-sujets

De même, vous pouvez activer le bitmap pour chaque sujet:

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

Créer le segmenteur de sujet

Après avoir spécifié les options SubjectSegmenterOptions, créez un Instance SubjectSegmenter qui appelle getClient() et transmet les options en tant que :

Kotlin

val segmenter = SubjectSegmentation.getClient(options)

Java

SubjectSegmenter segmenter = SubjectSegmentation.getClient(options);

3. Traiter une image

Transmettre le InputImage préparé à la méthode process de 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. Obtenir le résultat de la segmentation du sujet

Récupérer les masques et les bitmaps de premier plan

Une fois le traitement terminé, vous pouvez récupérer le masque de premier plan de votre image appelante. getForegroundConfidenceMask() comme suit:

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

Vous pouvez également récupérer un bitmap du premier plan de l'image en appelant getForegroundBitmap():

Kotlin

val foregroundBitmap = result.foregroundBitmap

Java

Bitmap foregroundBitmap = result.getForegroundBitmap();

Récupérer les masques et les bitmaps pour chaque sujet

De même, vous pouvez récupérer le masque des sujets segmentés en appelant getConfidenceMask() sur chaque sujet, comme suit:

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

Vous pouvez également accéder au bitmap de chaque sujet segmenté comme suit:

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

Conseils pour améliorer les performances

Pour chaque session d'application, la première inférence est souvent plus lente que les suivantes dues à l'initialisation du modèle. Si une faible latence est essentielle, envisagez CANNOT TRANSLATE l'inférence à l'avance.

La qualité des résultats dépend de la qualité de l'image d'entrée:

  • Pour que ML Kit obtienne un résultat de segmentation précis, l'image doit faire au moins 512 x 512 pixels.
  • Une mauvaise mise au point de l'image peut également nuire à sa précision. Si les résultats ne sont pas satisfaisants, demandez à l'utilisateur de reprendre l'image.