Segmentación de selfies con ML Kit en iOS

El Kit de AA proporciona un SDK optimizado para la segmentación de selfies. Los recursos de Selfie Segmenter se vinculan de forma estática a tu app en el tiempo de compilación. Esto aumentará el tamaño de tu app hasta en 24 MB, y la latencia de la API puede variar de alrededor de 7 ms a alrededor de 12 ms según el tamaño de la imagen de entrada, como se mide en el iPhone X.

Probar

Antes de comenzar

  1. Incluye las siguientes bibliotecas de ML Kit en tu Podfile:

    pod 'GoogleMLKit/SegmentationSelfie', '7.0.0'
    
  2. Después de instalar o actualizar los Pods de tu proyecto, abre el proyecto de Xcode con su .xcworkspace. ML Kit es compatible con Xcode versión 13.2.1 o posterior.

1. Crea una instancia de Segmenter

Para realizar la segmentación en una imagen de selfie, primero crea una instancia de Segmenter con SelfieSegmenterOptions y, de manera opcional, especifica la configuración de segmentación.

Opciones del segmentador

Modo de segmentador

Segmenter funciona en dos modos. Asegúrate de elegir la que coincida con tu caso de uso.

STREAM_MODE (default)

Este modo está diseñado para transmitir fotogramas desde videos o cámaras. En este modo, el segmentador aprovechará los resultados de fotogramas anteriores para generar resultados de segmentación más fluidos.

SINGLE_IMAGE_MODE (default)

Este modo está diseñado para imágenes individuales que no están relacionadas. En este modo, el segmentador procesará cada imagen de forma independiente, sin suavizar los fotogramas.

Habilita la máscara de tamaño sin procesar

Le pide al segmentador que muestre la máscara de tamaño sin procesar que coincida con el tamaño de salida del modelo.

El tamaño de la máscara sin procesar (p.ej., 256 × 256) suele ser más pequeño que el tamaño de la imagen de entrada.

Sin especificar esta opción, el segmentador volverá a escalar la máscara sin procesar para que coincida con el tamaño de la imagen de entrada. Considera usar esta opción si deseas aplicar una lógica de cambio de escala personalizada o si no es necesario cambiar la escala para tu caso de uso.

Especifica las opciones del segmentador:

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

Por último, obtén una instancia de Segmenter. Pasa las opciones que especificaste:

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

2. Prepara la imagen de entrada

Para segmentar selfies, haz lo siguiente con cada imagen o fotograma de video. Si habilitaste el modo de transmisión, debes crear objetos VisionImage a partir de varias CMSampleBuffer.

Crea un objeto VisionImage con una UIImage o una CMSampleBuffer.

Si usas una UIImage, sigue estos pasos:

  • Crea un objeto VisionImage con UIImage. Asegúrate de especificar la .orientation correcta.
    let image = VisionImage(image: UIImage)
    visionImage.orientation = image.imageOrientation
    MLKVisionImage *visionImage = [[MLKVisionImage alloc] initWithImage:image];
    visionImage.orientation = image.imageOrientation;

Si usas una CMSampleBuffer, sigue estos pasos:

  • Especifica la orientación de los datos de imagen contenidos en CMSampleBuffer.

    Para obtener la orientación de la imagen, haz lo siguiente:

    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;
      }
    }
          
  • Crea un objeto VisionImage con el objeto CMSampleBuffer y la orientación:
    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. Procesa la imagen

Pasa el objeto VisionImage a uno de los métodos de procesamiento de imágenes de Segmenter. Puedes usar el método asíncrono process(image:) o el método síncrono results(in:).

Para realizar la segmentación de una imagen de selfie de forma síncrona, haz lo siguiente:

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.

Para realizar la segmentación de una imagen de selfie de forma asíncrona, haz lo siguiente:

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. Obtén la máscara de segmentación

Puedes obtener el resultado de la segmentación de la siguiente manera:

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

Para ver un ejemplo completo de cómo usar los resultados de la segmentación, consulta la muestra de la guía de inicio rápido del Kit de AA.

Sugerencias para mejorar el rendimiento

La calidad de los resultados depende de la calidad de la imagen de entrada:

  • Para que ML Kit obtenga un resultado de segmentación preciso, la imagen debe tener al menos 256 × 256 píxeles.
  • Si realizas la segmentación de selfies en una aplicación en tiempo real, te recomendamos tener en cuenta las dimensiones generales de las imágenes de entrada. Las imágenes más pequeñas se pueden procesar más rápido. Así que, para reducir la latencia, captura imágenes con resoluciones más bajas, pero ten en cuenta los requisitos de resolución anteriores y asegúrate de que el sujeto ocupe la mayor parte de la imagen como sea posible.
  • Un enfoque de imagen deficiente también puede afectar la exactitud. Si no obtienes resultados aceptables, pídele al usuario que vuelva a capturar la imagen.

Si quieres usar la segmentación en una aplicación en tiempo real, sigue estos lineamientos para lograr las mejores velocidades de fotogramas:

  • Usa el modo de segmentador stream.
  • Considera capturar imágenes con una resolución más baja. Sin embargo, también ten en cuenta los requisitos de dimensiones de imágenes de esta API.
  • Para procesar fotogramas de video, usa la API síncrona results(in:) del segmentador. Llama a este método desde la función captureOutput(_, didOutput:from:) de AVCaptureVideoDataOutputSampleBufferDelegate para obtener resultados de forma síncrona desde el fotograma de video determinado. Mantén alwaysDiscardsLateVideoFrames de AVCaptureVideoDataOutput como verdadero para limitar las llamadas al segmentador. Si hay un fotograma de video nuevo disponible mientras se ejecuta el segmentador, se descartará.
  • Si usas la salida del segmentador para superponer gráficos en la imagen de entrada, primero obtén el resultado de ML Kit y, luego, renderiza la imagen y la superposición en un solo paso. De esta manera, procesas la superficie de visualización solo una vez por cada fotograma de entrada procesado. Consulta las clases previewOverlayView y CameraViewController en la muestra de la guía de inicio rápido de ML Kit para ver un ejemplo.