Segmentation des selfies avec ML Kit sur iOS

ML Kit fournit un SDK optimisé pour la segmentation des selfies. Les composants Selfie Segmenter sont liés de manière statique à votre application au moment de la compilation. La taille de votre application augmentera alors jusqu'à 24 Mo, et la latence de l'API peut varier de 7 ms à 12 ms en fonction de la taille de l'image d'entrée, comme mesuré sur l'iPhone X.

Essayer

Avant de commencer

  1. Incluez les bibliothèques ML Kit suivantes dans votre Podfile:

    pod 'GoogleMLKit/SegmentationSelfie', '7.0.0'
    
  2. Après avoir installé ou mis à jour les pods de votre projet, ouvrez votre projet Xcode à l'aide de son fichier .xcworkspace. ML Kit est compatible avec la version 13.2.1 ou ultérieure de Xcode.

1. Créer une instance de Segmenter

Pour effectuer une segmentation sur une image de selfie, créez d'abord une instance de Segmenter avec SelfieSegmenterOptions, puis spécifiez éventuellement les paramètres de segmentation.

Options de segmentation

Mode segmenteur

Segmenter fonctionne dans deux modes. Assurez-vous de choisir celui qui correspond à votre cas d'utilisation.

STREAM_MODE (default)

Ce mode est conçu pour le streaming de frames à partir d'une vidéo ou d'une caméra. Dans ce mode, le segmenteur s'appuie sur les résultats des images précédentes pour renvoyer des résultats de segmentation plus fluides.

SINGLE_IMAGE_MODE (default)

Ce mode est conçu pour les images uniques qui ne sont pas liées. Dans ce mode, le segmenteur traite chaque image indépendamment, sans lissage sur les images.

Activer le masque de taille brute

Demande au segmenteur de renvoyer le masque de taille brut qui correspond à la taille de sortie du modèle.

La taille du masque brut (par exemple, 256 x 256) est généralement inférieure à celle de l'image d'entrée.

Sans spécifier cette option, le segmenteur redimensionnera le masque brut pour qu'il corresponde à la taille de l'image d'entrée. Envisagez d'utiliser cette option si vous souhaitez appliquer une logique de redimensionnement personnalisée ou si le redimensionnement n'est pas nécessaire pour votre cas d'utilisation.

Spécifiez les options du segmenteur:

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

Enfin, obtenez une instance de Segmenter. Transmettez les options que vous avez spécifiées:

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

2. Préparer l'image d'entrée

Pour segmenter les selfies, procédez comme suit pour chaque image ou chaque frame de vidéo. Si vous avez activé le mode de flux, vous devez créer des objets VisionImage à partir de CMSampleBuffer.

Créez un objet VisionImage à l'aide d'un UIImage ou d'un CMSampleBuffer.

Si vous utilisez un UIImage, procédez comme suit:

  • Créez un objet VisionImage avec UIImage. Veillez à spécifier le .orientation approprié.
    let image = VisionImage(image: UIImage)
    visionImage.orientation = image.imageOrientation
    MLKVisionImage *visionImage = [[MLKVisionImage alloc] initWithImage:image];
    visionImage.orientation = image.imageOrientation;

Si vous utilisez un CMSampleBuffer, procédez comme suit:

  • Spécifiez l'orientation des données d'image contenues dans CMSampleBuffer.

    Pour obtenir l'orientation de l'image:

    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;
      }
    }
          
  • Créez un objet VisionImage à l'aide de l'objet CMSampleBuffer et de l'orientation:
    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. Traiter l'image

Transmettez l'objet VisionImage à l'une des méthodes de traitement d'image de Segmenter. Vous pouvez utiliser la méthode process(image:) asynchrone ou la méthode results(in:) synchrone.

Pour effectuer une segmentation synchrone sur une image de selfie:

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.

Pour effectuer une segmentation d'une image de selfie de manière asynchrone:

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. Obtenir le masque de segmentation

Vous pouvez obtenir le résultat de la segmentation comme suit:

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

Pour obtenir un exemple complet sur l'utilisation des résultats de segmentation, consultez l'exemple de démarrage rapide de ML Kit.

Conseils pour améliorer les performances

La qualité de vos résultats dépend de la qualité de l'image d'entrée:

  • Pour que ML Kit obtienne un résultat de segmentation précis, l'image doit faire au moins 256 x 256 pixels.
  • Si vous effectuez une segmentation de selfie dans une application en temps réel, vous pouvez également tenir compte des dimensions globales des images d'entrée. Les images de petite taille peuvent être traitées plus rapidement. Pour réduire la latence, capturez des images à des résolutions inférieures, mais gardez à l'esprit les exigences de résolution ci-dessus et assurez-vous que le sujet occupe autant que possible l'image.
  • Un mauvais cadrage peut également avoir un impact sur la précision. Si vous n'obtenez pas de résultats acceptables, demandez à l'utilisateur de reprendre la photo.

Si vous souhaitez utiliser la segmentation dans une application en temps réel, suivez ces consignes pour obtenir les meilleurs débits d'images:

  • Utilisez le mode de segmentation stream.
  • Envisagez de prendre des images en basse résolution. Toutefois, gardez à l'esprit les exigences concernant les dimensions des images de cette API.
  • Pour traiter les images vidéo, utilisez l'API synchrone results(in:) du segmenteur. Appelez cette méthode à partir de la fonction captureOutput(_, didOutput:from:) de AVCaptureVideoDataOutputSampleBufferDelegate pour obtenir de manière synchrone les résultats du frame vidéo donné. Laissez la valeur alwaysDiscardsLateVideoFrames de AVCaptureVideoDataOutput définie sur "true" pour limiter les appels au segmenteur. Si un nouveau frame vidéo devient disponible pendant l'exécution du segmenteur, il sera supprimé.
  • Si vous utilisez la sortie du segmenteur pour superposer des éléments graphiques à l'image d'entrée, obtenez d'abord le résultat de ML Kit, puis affichez l'image et la superposition en une seule étape. Vous ne procédez au rendu sur la surface d'affichage qu'une seule fois pour chaque frame d'entrée traitée. Pour obtenir un exemple, consultez les classes previewOverlayView et CameraViewController dans l'exemple de démarrage rapide ML Kit.