Selfie-Segmentierung mit ML Kit für Android

ML Kit bietet ein optimiertes SDK für die Segmentierung von Selfies. Die Assets des Selfie-Segmentierers sind bei der Erstellung statisch mit Ihrer App verknüpft. Dadurch erhöht sich die Downloadgröße Ihrer App um bis zu 5 MB und die API-Latenz kann je nach Größe des Eingabebilds auf einem Pixel 4 zwischen 25 ms und 65 ms liegen.

  • Probieren Sie die Beispiel-App aus, um ein Beispiel für die Verwendung dieser API zu sehen.

Hinweis

  1. Achten Sie darauf, dass Sie in Ihrer build.gradle-Datei auf Projektebene das Maven-Repository von Google in die Abschnitte buildscript und allprojects aufnehmen.
  2. Fügen Sie die Abhängigkeiten für die ML Kit-Android-Bibliotheken in die Gradle-Datei des Moduls auf App-Ebene ein. Das ist in der Regel app/build.gradle:
dependencies {
  implementation 'com.google.mlkit:segmentation-selfie:16.0.0-beta4'
}

1. Segmentierungsinstanz erstellen

Segmentierungsoptionen

Erstellen Sie zum Segmentieren eines Bilds zuerst eine Instanz von Segmenter. Geben Sie dazu die folgenden Optionen an.

Detektormodus

Segmenter betreibt zwei Modi. Wählen Sie den für Ihren Anwendungsfall passenden Typ aus.

STREAM_MODE (default)

Dieser Modus wurde entwickelt, um Frames von einem Video oder von einer Kamera zu streamen. In diesem Modus verwendet das Segmentierungstool Ergebnisse aus vorherigen Frames, um eine reibungslosere Segmentierung zu ermöglichen.

SINGLE_IMAGE_MODE

Dieser Modus ist für einzelne Bilder gedacht, die keinen Bezug zueinander haben. In diesem Modus verarbeitet das Segmentierer jedes Bild unabhängig und ohne Glättung der Frames.

Maske mit Rohdaten aktivieren

Fordert das Segmentierer auf, die Rohgrößemaske zurückzugeben, die der Ausgabegröße des Modells entspricht.

Die Größe der Rohmaske (z.B. 256 x 256) ist normalerweise kleiner als die Größe des Eingabebilds. Rufen Sie SegmentationMask#getWidth() und SegmentationMask#getHeight() auf, um die Maskengröße aufzurufen, wenn Sie diese Option aktivieren.

Ohne Angabe dieser Option skaliert das Segmentierer die Rohmaske neu, um sie der Größe des Eingabebilds anzupassen. Verwenden Sie diese Option, wenn Sie keine benutzerdefinierte Logik zur Neuskalierung anwenden möchten oder eine erneute Skalierung für Ihren Anwendungsfall nicht erforderlich ist.

Geben Sie die Segmentierungsoptionen an:

Kotlin

val options =
        SelfieSegmenterOptions.Builder()
            .setDetectorMode(SelfieSegmenterOptions.STREAM_MODE)
            .enableRawSizeMask()
            .build()

Java

SelfieSegmenterOptions options =
        new SelfieSegmenterOptions.Builder()
            .setDetectorMode(SelfieSegmenterOptions.STREAM_MODE)
            .enableRawSizeMask()
            .build();

Erstellen Sie eine Instanz von Segmenter. Übergeben Sie die angegebenen Optionen:

Kotlin

val segmenter = Segmentation.getClient(options)

Java

Segmenter segmenter = Segmentation.getClient(options);

2. Eingabebild vorbereiten

Erstellen Sie zum Segmentieren eines Bildes ein InputImage-Objekt aus einem Bitmap-, media.Image-, ByteBuffer-, Byte-Array oder einer Datei auf dem Gerät.

Sie können ein InputImage-Objekt aus verschiedenen Quellen erstellen. Dies wird unten erläutert.

Mit einem media.Image

Wenn Sie ein InputImage-Objekt aus einem media.Image-Objekt erstellen möchten, z. B. wenn Sie ein Bild von der Kamera eines Geräts aufnehmen, übergeben Sie das Objekt media.Image und die Bilddrehung an InputImage.fromMediaImage().

Wenn Sie die KameraX-Bibliothek verwenden, berechnen die Klassen OnImageCapturedListener und ImageAnalysis.Analyzer den Rotationswert für Sie.

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

Wenn du keine Kamerabibliothek verwendest, die dir den Grad der Drehung des Bildes liefert, kannst du ihn aus dem Rotationsgrad des Geräts und der Ausrichtung des Kamerasensors im Gerät berechnen:

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

Übergib dann das media.Image-Objekt und den Rotationsgradwert an InputImage.fromMediaImage():

Kotlin

val image = InputImage.fromMediaImage(mediaImage, rotation)

Java

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

Datei-URI verwenden

Übergeben Sie den Anwendungskontext und den Datei-URI an InputImage.fromFilePath(), um ein InputImage-Objekt aus einem Datei-URI zu erstellen. Dies ist nützlich, wenn Sie den Intent ACTION_GET_CONTENT verwenden, um den Nutzer aufzufordern, ein Bild aus seiner Galerie-App auszuwählen.

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

Mit ByteBuffer oder ByteArray

Um ein InputImage-Objekt aus einem ByteBuffer oder einem ByteArray zu erstellen, musst du zuerst den Grad der Bilddrehung berechnen, wie zuvor für die media.Image-Eingabe beschrieben. Erstellen Sie dann das InputImage-Objekt mit dem Zwischenspeicher oder Array sowie Höhe, Breite, Farbcodierungsformat und Rotationsgrad des Bildes:

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

Mit einem Bitmap

So erstellen Sie ein InputImage-Objekt aus einem Bitmap-Objekt:

Kotlin

val image = InputImage.fromBitmap(bitmap, 0)

Java

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

Das Bild wird durch ein Bitmap-Objekt mit Rotationsgrad dargestellt.

3. Bild verarbeiten

Übergeben Sie das vorbereitete InputImage-Objekt an die Methode process von Segmenter.

Kotlin

Task<SegmentationMask> result = segmenter.process(image)
       .addOnSuccessListener { results ->
           // Task completed successfully
           // ...
       }
       .addOnFailureListener { e ->
           // Task failed with an exception
           // ...
       }

Java

Task<SegmentationMask> result =
        segmenter.process(image)
                .addOnSuccessListener(
                        new OnSuccessListener<SegmentationMask>() {
                            @Override
                            public void onSuccess(SegmentationMask mask) {
                                // Task completed successfully
                                // ...
                            }
                        })
                .addOnFailureListener(
                        new OnFailureListener() {
                            @Override
                            public void onFailure(@NonNull Exception e) {
                                // Task failed with an exception
                                // ...
                            }
                        });

4. Ergebnis der Segmentierung abrufen

So erhalten Sie das Segmentierungsergebnis:

Kotlin

val mask = segmentationMask.getBuffer()
val maskWidth = segmentationMask.getWidth()
val maskHeight = segmentationMask.getHeight()

for (val y = 0; y < maskHeight; y++) {
  for (val x = 0; x < maskWidth; x++) {
    // Gets the confidence of the (x,y) pixel in the mask being in the foreground.
    val foregroundConfidence = mask.getFloat()
  }
}

Java

ByteBuffer mask = segmentationMask.getBuffer();
int maskWidth = segmentationMask.getWidth();
int maskHeight = segmentationMask.getHeight();

for (int y = 0; y < maskHeight; y++) {
  for (int x = 0; x < maskWidth; x++) {
    // Gets the confidence of the (x,y) pixel in the mask being in the foreground.
    float foregroundConfidence = mask.getFloat();
  }
}

Ein vollständiges Beispiel für die Verwendung der Segmentierungsergebnisse finden Sie im ML Kit-Kurzanleitungsbeispiel.

Tipps zur Leistungssteigerung

Die Qualität der Ergebnisse hängt von der Qualität des Eingabebilds ab:

  • Damit das ML Kit ein genaues Segmentierungsergebnis erhalten kann, sollte das Bild mindestens 256 x 256 Pixel groß sein.
  • Ein schlechter Bildfokus kann sich auch auf die Genauigkeit auswirken. Wenn Sie keine akzeptablen Ergebnisse erhalten, bitten Sie den Nutzer, das Bild neu aufzunehmen.

Wenn Sie die Segmentierung in Echtzeitanwendungen verwenden möchten, sollten Sie die folgenden Richtlinien beachten, um die besten Framerates zu erzielen:

  • STREAM_MODE verwenden.
  • Sie sollten Bilder mit einer geringeren Auflösung aufnehmen. Beachten Sie jedoch die Anforderungen an die Bildabmessungen dieser API.
  • Erwägen Sie, die Option für die Größe der Maske mit rohen Größen zu aktivieren und alle Logiken zur neuen Skalierung zu kombinieren. Anstatt die API die Maske beispielsweise neu an die Größe des Eingabebilds anpassen zu lassen und sie dann noch einmal neu zu skalieren, damit sie der Ansichtsgröße für die Anzeige entspricht, fordern Sie einfach die Maske mit der Rohgröße an und kombinieren diese beiden Schritte zu einem.
  • Wenn Sie die API Camera oder camera2 verwenden, drosseln Sie Aufrufe an den Detektor. Wenn während der Ausführung des Detektors ein neuer Videoframe verfügbar wird, lassen Sie den Frame los. Ein Beispiel findest du in der Kurzanleitungs-Beispielanwendung in der Klasse VisionProcessorBase.
  • Wenn Sie die CameraX API verwenden, achten Sie darauf, dass die Gegendruckstrategie auf den Standardwert ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST festgelegt ist. So wird sichergestellt, dass jeweils nur ein Bild zur Analyse übermittelt wird. Wenn bei der Ausarbeitung des Analysetools mehr Bilder erstellt werden, werden diese automatisch gelöscht und nicht in die Warteschlange gestellt. Sobald das analysierte Bild durch Aufrufen von ImageProxy.close() geschlossen wird, wird das nächste neueste Bild bereitgestellt.
  • Wenn Sie die Ausgabe des Detektors verwenden, um Grafiken auf dem Eingabebild einzublenden, rufen Sie zuerst das Ergebnis aus ML Kit ab und rendern Sie dann das Bild und das Overlay in einem einzigen Schritt. Dies wird für jeden Eingabeframe nur einmal auf der Anzeigeoberfläche gerendert. Ein Beispiel finden Sie in den Beispielkursen CameraSourcePreview und GraphicOverlay.
  • Wenn Sie die Camera2 API verwenden, nehmen Sie Bilder im Format ImageFormat.YUV_420_888 auf. Wenn du die ältere Camera API verwendest, solltest du Bilder im Format ImageFormat.NV21 aufnehmen.