Selfie-Segmentierung mit ML Kit für iOS

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 wird die App-Größe um bis zu 24 MB erhöht und die API-Latenz kann je nach Größe des Eingabebilds auf dem iPhone X zwischen ~7 ms und ~12 ms variieren.

  • 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 seiner .xcworkspace-Datei. ML Kit wird ab Xcode-Version 13.2.1 unterstützt.

1. Segmentierungsinstanz erstellen

Um eine Segmentierung für ein Selfie durchzuführen, erstellen Sie zuerst eine Instanz von Segmenter mit SelfieSegmenterOptions und legen Sie optional die Segmentierungseinstellungen fest.

Segmentierungsoptionen

Segmentierungsmodus

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

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.

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:

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 schließlich 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 oder jeden Frame des Videos folgendermaßen vor, um Selfies zu segmentieren: Wenn Sie den Streammodus aktiviert haben, müssen Sie VisionImage-Objekte aus CMSampleBuffers erstellen.

Erstelle ein VisionImage-Objekt mit einem UIImage oder einem CMSampleBuffer.

Wenn Sie UIImage verwenden, gehen Sie so vor:

  • Erstellen Sie ein VisionImage-Objekt mit UIImage. Gib die richtige .orientation an.

    Swift

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

    Objective-C

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

Wenn Sie CMSampleBuffer verwenden, gehen Sie so vor:

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

    So erhalten Sie die Bildausrichtung:

    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 ein VisionImage-Objekt mit dem CMSampleBuffer-Objekt und der -Ausrichtung:

    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

Übergib das Objekt VisionImage an eine Bildverarbeitungsmethode 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:

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 eine Segmentierung für ein Selfie-Bild 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-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.
  • Wenn Sie eine 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 einer geringeren Auflösung aufnehmen. Beachten Sie jedoch die oben genannten Anforderungen an die Auflösung und achten Sie darauf, dass der Nutzer einen möglichst großen Teil des Bilds einnimmt.
  • 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:

  • Verwenden Sie den Segmentierungsmodus stream.
  • Sie sollten Bilder mit einer geringeren Auflösung aufnehmen. Beachten Sie jedoch die Anforderungen an die Bildabmessungen dieser API.
  • Verwenden Sie zur Verarbeitung von Videoframes die synchrone API results(in:) des Segmentierers. Rufen Sie diese Methode aus der Funktion AVCaptureVideoDataOutputSampleBufferDelegate's CaptureOutput(_, DidOutput:from:) auf, um synchron Ergebnisse aus dem angegebenen Videoframe zu erhalten. Lassen Sie AVCaptureVideoDataOutput immer auf true setzen, um Aufrufe an das Segmentierer zu drosseln. Wenn während der Ausführung des Segmentierers ein neuer Videoframe verfügbar wird, wird dieser entfernt.
  • Wenn Sie die Ausgabe des Segmentierers 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. Dadurch wird die Anzeige nur einmal für jeden verarbeiteten Eingabeframe auf der Anzeigefläche gerendert. Ein Beispiel dafür finden Sie in den Klassen previewOverlayView und CameraViewController im ML Kit-Kurzanleitungsbeispiel.