Selfiesegmentierung mit ML Kit für iOS

ML Kit bietet ein optimiertes SDK für die Segmentierung von Selfies. Die Selfie Segmenter-Assets werden bei der Buildzeit statisch mit Ihrer App verknüpft. Dadurch erhöht sich die App-Größe um bis zu 24 MB und die API-Latenz kann je nach Größe des Eingabebilds zwischen etwa 7 ms und 12 ms liegen, gemessen auf dem iPhone X.

Jetzt ausprobieren

Hinweis

  1. Fügen Sie Ihrer Podfile-Datei die folgenden ML Kit-Bibliotheken hinzu:

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

1. Instanz von Segmenter erstellen

Wenn Sie eine Segmentierung für ein Selfie-Bild ausführen möchten, erstellen Sie zuerst eine Instanz von Segmenter mit SelfieSegmenterOptions und geben Sie optional die Segmentierungseinstellungen an.

Segmentierungsoptionen

Segmentiermodus

Der Segmenter kann in zwei Modi betrieben werden. Achten Sie darauf, die Option auszuwählen, die zu Ihrem Anwendungsfall passt.

STREAM_MODE (default)

Dieser Modus ist für das Streaming von Frames aus einem Video oder einer Kamera konzipiert. In diesem Modus nutzt der Segmentierungsalgorithmus Ergebnisse aus vorherigen Frames, um glattere Segmentierungsergebnisse zu liefern.

SINGLE_IMAGE_MODE (default)

Dieser Modus ist für einzelne Bilder gedacht, die nicht zusammenhängen. In diesem Modus verarbeitet der Segmentierungsalgorithmus jedes Bild unabhängig voneinander, ohne Glättung über Frames hinweg.

Maske für Rohgröße aktivieren

Der Segmentierer wird aufgefordert, die Rohgrößenmaske zurückzugeben, die der Modellausgabegröße entspricht.

Die Größe der Rohmaske (z.B. 256 × 256) ist in der Regel kleiner als die Größe des Eingabebilds.

Wenn Sie diese Option nicht angeben, skaliert der Segmentierungsalgorithmus die Rohmaske so, dass sie der Größe des Eingabebilds entspricht. Verwenden Sie diese Option, wenn Sie eine benutzerdefinierte Logik für die Neuberechnung anwenden möchten oder die Neuberechnung für Ihren Anwendungsfall nicht erforderlich ist.

Geben Sie die Segmentierungsoptionen an:

let options = SelfieSegmenterOptions()
options.segmenterMode = .singleImage
options.shouldEnableRawSizeMask = true
MLKSelfieSegmenterOptions *options = [[MLKSelfieSegmenterOptions alloc] init];
options.segmenterMode = MLKSegmenterModeSingleImage;
options.shouldEnableRawSizeMask = YES;

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

let segmenter = Segmenter.segmenter(options: options)
MLKSegmenter *segmenter = [MLKSegmenter segmenterWithOptions:options];

2. Eingabebild vorbereiten

Wenn Sie Selfies segmentieren möchten, gehen Sie für jedes Bild oder jeden Videoframe so vor: Wenn Sie den Stream-Modus aktiviert haben, müssen Sie VisionImage-Objekte aus CMSampleBuffer-Objekten erstellen.

Erstellen Sie ein VisionImage-Objekt mit einem UIImage oder einem CMSampleBuffer.

Wenn Sie ein UIImage verwenden, gehen Sie so vor:

  • Erstellen Sie ein VisionImage-Objekt mit dem UIImage. Achten Sie darauf, die richtige .orientation anzugeben.
    let image = VisionImage(image: UIImage)
    visionImage.orientation = image.imageOrientation
    MLKVisionImage *visionImage = [[MLKVisionImage alloc] initWithImage:image];
    visionImage.orientation = image.imageOrientation;

Wenn Sie ein CMSampleBuffer verwenden, gehen Sie so vor:

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

    So rufen Sie die Bildausrichtung auf:

    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
      }
    }
          
    - (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;
      }
    }
          
  • Erstellen Sie ein VisionImage-Objekt mit dem CMSampleBuffer-Objekt und der Ausrichtung:
    let image = VisionImage(buffer: sampleBuffer)
    image.orientation = imageOrientation(
      deviceOrientation: UIDevice.current.orientation,
      cameraPosition: cameraPosition)
     MLKVisionImage *image = [[MLKVisionImage alloc] initWithBuffer:sampleBuffer];
     image.orientation =
       [self imageOrientationFromDeviceOrientation:UIDevice.currentDevice.orientation
                                    cameraPosition:cameraPosition];

3. Bild verarbeiten

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

So führen Sie eine synchrone Segmentierung für ein Selfie durch:

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.
NSError *error;
MLKSegmentationMask *mask =
    [segmenter resultsInImage:image error:&error];
if (error != nil) {
  // Error.
  return;
}

// Success. Get a segmentation mask here.

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

segmenter.process(image) { mask, error in
  guard error == nil else {
    // Error.
    return
  }
  // Success. Get a segmentation mask here.
[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 rufen Sie das Segmentierungsergebnis ab:

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
}
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 Beispiel für die ML Kit-Kurzanleitung.

Tipps zur Leistungsverbesserung

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

  • Damit ML Kit ein genaues Segmentierungsergebnis liefern kann, muss das Bild mindestens 256 × 256 Pixel groß sein.
  • Wenn Sie die Selfie-Segmentierung in einer Echtzeitanwendung ausfü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 niedrigerer Auflösung aufnehmen. Beachten Sie dabei jedoch die oben genannten Auflösungsanforderungen und achten Sie darauf, dass das Motiv möglichst viel Platz im Bild einnimmt.
  • Auch ein unscharfer Bildfokus kann sich auf die Genauigkeit auswirken. Wenn Sie keine zufriedenstellenden Ergebnisse erhalten, bitten Sie den Nutzer, das Bild noch einmal aufzunehmen.

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

  • Verwenden Sie den Segmentierungsmodus stream.
  • Sie können auch Bilder mit niedrigerer Auflösung aufnehmen. Beachten Sie jedoch auch die Anforderungen an die Bildabmessungen dieser API.
  • Verwende für die Verarbeitung von Videoframes die synchrone results(in:) API des Segmentierungstools. Rufen Sie diese Methode über die Funktion captureOutput(_, didOutput:from:) des AVCaptureVideoDataOutputSampleBufferDelegate auf, um synchron Ergebnisse aus dem angegebenen Videoframe abzurufen. Lassen Sie alwaysDiscardsLateVideoFrames von AVCaptureVideoDataOutput auf „true“ gesetzt, um Aufrufe an den Segmenter zu drosseln. Wenn während der Ausführung des Segmentierungstools ein neuer Videoframe verfügbar wird, wird er verworfen.
  • Wenn Sie die Ausgabe des Segmentierungstools verwenden, um Grafiken auf das Eingabebild zu legen, rufen Sie zuerst das Ergebnis aus ML Kit ab und rendern Sie dann das Bild und das Overlay in einem einzigen Schritt. So wird für jeden verarbeiteten Eingabeframe nur einmal auf die Displayoberfläche gerendert. Ein Beispiel finden Sie in den Klassen previewOverlayView und CameraViewController im ML Kit-Beispiel für den Schnellstart.