Selfiesegmentierung mit ML Kit für iOS

ML Kit bietet ein optimiertes SDK für die Selfie-Segmentierung. Die Selfie Segmenter-Assets sind zum Zeitpunkt der Erstellung statisch mit Ihrer App verknüpft. Dadurch wird die App-Größe um bis zu 24 MB erhöht und die API-Latenz kann je nach Größe des Eingabebilds (gemessen auf dem iPhone X) zwischen 7 ms und 12 ms variieren.

Ausprobieren

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

Hinweis

  1. Fügen Sie die folgenden ML Kit-Bibliotheken in Ihre Podfile-Datei ein:

    pod 'GoogleMLKit/SegmentationSelfie', '3.2.0'
    
  2. Nachdem Sie die Pods Ihres Projekts installiert oder aktualisiert haben, öffnen Sie Ihr Xcode-Projekt mit der xcworkspace. ML Kit wird ab Xcode-Version 13.2.1 unterstützt.

1. Instanz von Segmenter erstellen

Um ein Selfie zu segmentieren, erstellen Sie zuerst eine Instanz von Segmenter mit SelfieSegmenterOptions und geben Sie optional die Segmentierungseinstellungen an.

Segmentierungsoptionen

Segmentierungsmodus

Für Segmenter gibt es zwei Modi. Wählen Sie die Option aus, die zu Ihrem Anwendungsfall passt.

STREAM_MODE (default)

Dieser Modus ist für das Streamen von Frames aus dem Video oder der Kamera vorgesehen. In diesem Modus nutzt der Segmentierer die Ergebnisse früherer Frames, um eine flüssigere Segmentierung zu ermöglichen.

SINGLE_IMAGE_MODE (default)

Dieser Modus ist für einzelne Bilder geeignet, die keinen Bezug haben. In diesem Modus verarbeitet die Segmentierung jedes Bild unabhängig und ohne Glättung über Frames.

Maske für Rohgröße aktivieren

Fordert den Segmentierer auf, die Rohgrößenmaske zurückzugeben, die der Größe der Modellausgabe entspricht.

Die Größe der Rohmaske (z.B. 256 x 256) ist normalerweise kleiner als die Größe des Eingabebilds.

Ohne Angabe dieser Option skaliert der Segmentierer die Rohmaske neu, damit sie der Eingabebildgröße entspricht. Verwenden Sie diese Option, wenn Sie eine benutzerdefinierte Neuskalierungslogik anwenden möchten oder für Ihren Anwendungsfall keine Neuskalierung erforderlich ist.

Geben Sie die Segmentierungsoptionen an:

Swift

let options = SelfieSegmenterOptions()
options.segmenterMode = .singleImage
options.shouldEnableRawSizeMask = true

Objective-C

MLKSelfieSegmenterOptions *options = [[MLKSelfieSegmenterOptions alloc] init];
options.segmenterMode = MLKSegmenterModeSingleImage;
options.shouldEnableRawSizeMask = YES;

Rufen Sie abschließend eine Instanz von Segmenter ab. Übergeben Sie die angegebenen Optionen:

Swift

let segmenter = Segmenter.segmenter(options: options)

Objective-C

MLKSegmenter *segmenter = [MLKSegmenter segmenterWithOptions:options];

2. Eingabebild vorbereiten

Gehen Sie für jedes Bild bzw. jeden Videoframe so vor, um Selfies zu segmentieren. Wenn Sie den Streammodus aktiviert haben, müssen Sie VisionImage-Objekte aus CMSampleBuffers erstellen.

Erstellen Sie mit UIImage oder CMSampleBuffer ein VisionImage-Objekt.

Wenn du UIImage verwendest, gehe so vor:

  • Erstellen Sie mit der UIImage ein VisionImage-Objekt. Achte darauf, den richtigen .orientation anzugeben.

    Swift

    let image = VisionImage(image: UIImage)
    visionImage.orientation = image.imageOrientation

    Objective-C

    MLKVisionImage *visionImage = [[MLKVisionImage alloc] initWithImage:image];
    visionImage.orientation = image.imageOrientation;

Wenn du CMSampleBuffer verwendest, gehe so vor:

  • Geben Sie die Ausrichtung der Bilddaten an, die in CMSampleBuffer enthalten sind.

    So rufen Sie die Bildausrichtung ab:

    Swift

    func imageOrientation(
      deviceOrientation: UIDeviceOrientation,
      cameraPosition: AVCaptureDevice.Position
    ) -> UIImage.Orientation {
      switch deviceOrientation {
      case .portrait:
        return cameraPosition == .front ? .leftMirrored : .right
      case .landscapeLeft:
        return cameraPosition == .front ? .downMirrored : .up
      case .portraitUpsideDown:
        return cameraPosition == .front ? .rightMirrored : .left
      case .landscapeRight:
        return cameraPosition == .front ? .upMirrored : .down
      case .faceDown, .faceUp, .unknown:
        return .up
      }
    }
          

    Objective-C

    - (UIImageOrientation)
      imageOrientationFromDeviceOrientation:(UIDeviceOrientation)deviceOrientation
                             cameraPosition:(AVCaptureDevicePosition)cameraPosition {
      switch (deviceOrientation) {
        case UIDeviceOrientationPortrait:
          return cameraPosition == AVCaptureDevicePositionFront ? UIImageOrientationLeftMirrored
                                                                : UIImageOrientationRight;
    
        case UIDeviceOrientationLandscapeLeft:
          return cameraPosition == AVCaptureDevicePositionFront ? UIImageOrientationDownMirrored
                                                                : UIImageOrientationUp;
        case UIDeviceOrientationPortraitUpsideDown:
          return cameraPosition == AVCaptureDevicePositionFront ? UIImageOrientationRightMirrored
                                                                : UIImageOrientationLeft;
        case UIDeviceOrientationLandscapeRight:
          return cameraPosition == AVCaptureDevicePositionFront ? UIImageOrientationUpMirrored
                                                                : UIImageOrientationDown;
        case UIDeviceOrientationUnknown:
        case UIDeviceOrientationFaceUp:
        case UIDeviceOrientationFaceDown:
          return UIImageOrientationUp;
      }
    }
          
  • Erstelle mit dem Objekt CMSampleBuffer und der Ausrichtung ein VisionImage-Objekt:

    Swift

    let image = VisionImage(buffer: sampleBuffer)
    image.orientation = imageOrientation(
      deviceOrientation: UIDevice.current.orientation,
      cameraPosition: cameraPosition)

    Objective-C

     MLKVisionImage *image = [[MLKVisionImage alloc] initWithBuffer:sampleBuffer];
     image.orientation =
       [self imageOrientationFromDeviceOrientation:UIDevice.currentDevice.orientation
                                    cameraPosition:cameraPosition];

3. Bild verarbeiten

Übergeben Sie das Objekt VisionImage an eine der Bildverarbeitungsmethoden der Segmenter. Sie können entweder die asynchrone process(image:)-Methode oder die synchrone results(in:)-Methode verwenden.

So führen Sie eine synchrone Segmentierung eines Selfies durch:

Swift

var mask: [SegmentationMask]
do {
  mask = try segmenter.results(in: image)
} catch let error {
  print("Failed to perform segmentation with error: \(error.localizedDescription).")
  return
}

// Success. Get a segmentation mask here.

Objective-C

NSError *error;
MLKSegmentationMask *mask =
    [segmenter resultsInImage:image error:&error];
if (error != nil) {
  // Error.
  return;
}

// Success. Get a segmentation mask here.

So führen Sie die Segmentierung eines Selfies asynchron durch:

Swift

segmenter.process(image) { mask, error in
  guard error == nil else {
    // Error.
    return
  }
  // Success. Get a segmentation mask here.

Objective-C

[segmenter processImage:image
             completion:^(MLKSegmentationMask * _Nullable mask,
                          NSError * _Nullable error) {
               if (error != nil) {
                 // Error.
                 return;
               }
               // Success. Get a segmentation mask here.
             }];

4. Segmentierungsmaske abrufen

So erhalten Sie das Segmentierungsergebnis:

Swift

let maskWidth = CVPixelBufferGetWidth(mask.buffer)
let maskHeight = CVPixelBufferGetHeight(mask.buffer)

CVPixelBufferLockBaseAddress(mask.buffer, CVPixelBufferLockFlags.readOnly)
let maskBytesPerRow = CVPixelBufferGetBytesPerRow(mask.buffer)
var maskAddress =
    CVPixelBufferGetBaseAddress(mask.buffer)!.bindMemory(
        to: Float32.self, capacity: maskBytesPerRow * maskHeight)

for _ in 0...(maskHeight - 1) {
  for col in 0...(maskWidth - 1) {
    // Gets the confidence of the pixel in the mask being in the foreground.
    let foregroundConfidence: Float32 = maskAddress[col]
  }
  maskAddress += maskBytesPerRow / MemoryLayout<Float32>.size
}

Objective-C

size_t width = CVPixelBufferGetWidth(mask.buffer);
size_t height = CVPixelBufferGetHeight(mask.buffer);

CVPixelBufferLockBaseAddress(mask.buffer, kCVPixelBufferLock_ReadOnly);
size_t maskBytesPerRow = CVPixelBufferGetBytesPerRow(mask.buffer);
float *maskAddress = (float *)CVPixelBufferGetBaseAddress(mask.buffer);

for (int row = 0; row < height; ++row) {
  for (int col = 0; col < width; ++col) {
    // Gets the confidence of the pixel in the mask being in the foreground.
    float foregroundConfidence = maskAddress[col];
  }
  maskAddress += maskBytesPerRow / sizeof(float);
}

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

Tipps zur Leistungsverbesserung

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

  • Damit das ML Kit ein genaues Segmentierungsergebnis erhält, sollte das Bild mindestens 256 × 256 Pixel groß sein.
  • Wenn Sie die Selfie-Segmentierung in einer Echtzeitanwendung durchführen, sollten Sie auch die Gesamtabmessungen der Eingabebilder berücksichtigen. Kleinere Bilder können schneller verarbeitet werden. Um die Latenz zu verringern, sollten Sie Bilder mit niedrigeren Auflösungen aufnehmen. Beachten Sie jedoch die oben genannten Auflösungsanforderungen und achten Sie darauf, dass das Motiv so viel wie möglich einnimmt.
  • Ein schlechter Bildfokus kann sich auch auf die Genauigkeit auswirken. Sollten Sie keine akzeptablen Ergebnisse erhalten, bitten Sie den Nutzer, das Bild neu aufzunehmen.

Wenn Sie die Segmentierung in einer Echtzeitanwendung verwenden möchten, beachten Sie die folgenden Richtlinien, um die besten Frame-Raten zu erzielen:

  • Verwenden Sie den Segmentierungsmodus stream.
  • Nehmen Sie Bilder mit einer niedrigeren Auflösung auf. Beachten Sie jedoch auch die Anforderungen dieser API an die Bildabmessung.
  • Verwenden Sie zum Verarbeiten von Videoframes die synchrone results(in:) API des Segmentierers. Rufen Sie diese Methode aus der Funktion captureOutput(_, didOutput:from:) von AVCaptureVideoDataOutputSampleBufferDelegate auf, um synchron Ergebnisse aus dem angegebenen Videoframe abzurufen. Belassen Sie das Attribut alwaysDiscardsLateVideoFrames von AVCaptureVideoDataOutput auf „true“, um Aufrufe an den Segmentierer zu drosseln. Steht ein neuer Videoframe zur Verfügung, während die Segmentierung ausgeführt wird, wird dieser entfernt.
  • Wenn Sie die Ausgabe des Segmentierers verwenden, um Grafiken auf dem Eingabebild einzublenden, rufen Sie zuerst das Ergebnis aus dem ML Kit ab und rendern Sie dann das Bild und das Overlay in einem einzigen Schritt. Auf diese Weise rendern Sie für jeden verarbeiteten Eingabeframe nur einmal auf der Anzeigeoberfläche. Ein Beispiel finden Sie in den Klassen previewOverlayView und CameraViewController im ML Kit-Schnellstartbeispiel.