Segmentazione dei selfie con ML Kit su iOS

ML Kit fornisce un SDK ottimizzato per la segmentazione dei selfie. Gli asset di Selfie Segmenter sono collegati staticamente alla tua app al momento della compilazione. In questo modo, le dimensioni dell'app aumenteranno fino a 24 MB e la latenza dell'API può variare da circa 7 ms a circa 12 ms a seconda delle dimensioni dell'immagine di input, come misurato su iPhone X.

Prova

  • Prova l'app di esempio per vedere un esempio di utilizzo di questa API.

Prima di iniziare

  1. Includi le seguenti librerie ML Kit nel tuo Podfile:

    pod 'GoogleMLKit/SegmentationSelfie', '8.0.0'
    
  2. Dopo aver installato o aggiornato i pod del progetto, apri il progetto Xcode utilizzando il file .xcworkspace. ML Kit è supportato in Xcode versione 13.2.1 o successive.

1. Crea un'istanza di Segmenter

Per eseguire la segmentazione su un'immagine selfie, crea prima un'istanza di Segmenter con SelfieSegmenterOptions e, se vuoi, specifica le impostazioni di segmentazione.

Opzioni di segmentazione

Modalità Segmenter

Il Segmenter funziona in due modalità. Assicurati di scegliere quello che corrisponde al tuo caso d'uso.

STREAM_MODE (default)

Questa modalità è progettata per lo streaming di frame da video o fotocamera. In questa modalità, il segmentatore sfrutta i risultati dei frame precedenti per restituire risultati di segmentazione più fluidi.

SINGLE_IMAGE_MODE (default)

Questa modalità è pensata per singole immagini non correlate. In questa modalità, il segmentatore elaborerà ogni immagine in modo indipendente, senza smoothing tra i frame.

Attiva maschera delle dimensioni non elaborate

Chiede al segmentatore di restituire la maschera di dimensione non elaborata che corrisponde alla dimensione dell'output del modello.

Le dimensioni della maschera grezza (ad es. 256 x 256) sono in genere inferiori a quelle dell'immagine di input.

Se non specifichi questa opzione, il segmentatore ridimensionerà la maschera grezza in modo che corrisponda alle dimensioni dell'immagine di input. Valuta la possibilità di utilizzare questa opzione se vuoi applicare una logica di ridimensionamento personalizzata o se il ridimensionamento non è necessario per il tuo caso d'uso.

Specifica le opzioni del segmentatore:

Swift

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

Objective-C

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

Infine, ottieni un'istanza di Segmenter. Trasmetti le opzioni che hai specificato:

Swift

let segmenter = Segmenter.segmenter(options: options)

Objective-C

MLKSegmenter *segmenter = [MLKSegmenter segmenterWithOptions:options];

2. Prepara l'immagine di input

Per segmentare i selfie, segui questi passaggi per ogni immagine o frame del video. Se hai attivato la modalità flusso, devi creare oggetti VisionImage da CMSampleBuffer.

Crea un oggetto VisionImage utilizzando un UIImage o un CMSampleBuffer.

Se utilizzi un UIImage, segui questi passaggi:

  • Crea un oggetto VisionImage con UIImage. Assicurati di specificare il .orientation corretto.

    Swift

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

    Objective-C

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

Se utilizzi un CMSampleBuffer, segui questi passaggi:

  • Specifica l'orientamento dei dati immagine contenuti in CMSampleBuffer.

    Per ottenere l'orientamento dell'immagine:

    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;
      }
    }
          
  • Crea un oggetto VisionImage utilizzando l'oggetto CMSampleBuffer e l'orientamento:

    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. Elabora l'immagine

Passa l'oggetto VisionImage a uno dei metodi di elaborazione delle immagini di Segmenter. Puoi utilizzare il metodo asincrono process(image:) o il metodo sincrono results(in:).

Per eseguire la segmentazione di un'immagine selfie in modo sincrono:

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.

Per eseguire la segmentazione di un'immagine selfie in modo asincrono:

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. Recuperare la maschera di segmentazione

Puoi ottenere il risultato della segmentazione nel seguente modo:

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

Per un esempio completo di come utilizzare i risultati della segmentazione, consulta l'esempio di avvio rapido di ML Kit.

Suggerimenti per migliorare il rendimento

La qualità dei risultati dipende dalla qualità dell'immagine di input:

  • Affinché ML Kit ottenga un risultato di segmentazione accurato, l'immagine deve essere di almeno 256 x 256 pixel.
  • Se esegui la segmentazione dei selfie in un'applicazione in tempo reale, potresti anche prendere in considerazione le dimensioni complessive delle immagini di input. Le immagini più piccole possono essere elaborate più velocemente, quindi per ridurre la latenza, acquisisci immagini a risoluzioni inferiori, ma tieni presente i requisiti di risoluzione sopra indicati e assicurati che il soggetto occupi la maggior parte dell'immagine possibile.
  • Anche una messa a fuoco scadente dell'immagine può influire sull'accuratezza. Se i risultati non sono accettabili, chiedi all'utente di acquisire nuovamente l'immagine.

Se vuoi utilizzare la segmentazione in un'applicazione in tempo reale, segui queste linee guida per ottenere i migliori frame rate:

  • Utilizza la modalità segmentatore stream.
  • Valuta la possibilità di acquisire immagini a una risoluzione inferiore. Tuttavia, tieni presente anche i requisiti di dimensione delle immagini di questa API.
  • Per l'elaborazione dei frame video, utilizza l'API sincrona results(in:) del segmentatore. Chiama questo metodo dalla funzione captureOutput(_, didOutput:from:) di AVCaptureVideoDataOutputSampleBufferDelegate per ottenere in modo sincrono i risultati dal frame video specificato. Mantieni alwaysDiscardsLateVideoFrames di AVCaptureVideoDataOutput impostato su true per limitare le chiamate al segmentatore. Se un nuovo frame video diventa disponibile durante l'esecuzione del segmentatore, verrà eliminato.
  • Se utilizzi l'output del segmentatore per sovrapporre elementi grafici all'immagine di input, prima ottieni il risultato da ML Kit, poi esegui il rendering dell'immagine e della sovrapposizione in un unico passaggio. In questo modo, il rendering sulla superficie di visualizzazione viene eseguito una sola volta per ogni frame di input elaborato. Per un esempio, consulta le classi previewOverlayView e CameraViewController nell'esempio di avvio rapido di ML Kit.