Segmentação por selfie com o Kit de ML no iOS

O ML Kit oferece um SDK otimizado para segmentação de selfies. Os recursos do Selfie Segmenter são vinculados estaticamente ao seu app no momento da criação. Isso vai aumentar o tamanho do app em até 24 MB, e a latência da API pode variar de ~7 ms a ~12 ms, dependendo do tamanho da imagem de entrada, conforme medido no iPhone X.

Faça um teste

Antes de começar

  1. Inclua as seguintes bibliotecas do ML Kit no seu Podfile:

    pod 'GoogleMLKit/SegmentationSelfie', '8.0.0'
    
  2. Depois de instalar ou atualizar os pods do projeto, abra o projeto do Xcode usando o .xcworkspace. O ML Kit é compatível com o Xcode versão 13.2.1 ou mais recente.

1. Criar uma instância de Segmenter

Para realizar a segmentação em uma selfie, primeiro crie uma instância de Segmenter com SelfieSegmenterOptions e, opcionalmente, especifique as configurações de segmentação.

Opções do segmentador

Modo segmentador

O Segmenter opera em dois modos. Escolha a opção que corresponde ao seu caso de uso.

STREAM_MODE (default)

Esse modo foi criado para transmitir frames de vídeo ou câmera. Nesse modo, o segmentador usa resultados de frames anteriores para retornar resultados de segmentação mais suaves.

SINGLE_IMAGE_MODE (default)

Esse modo é projetado para imagens únicas que não estão relacionadas. Nesse modo, o segmentador processa cada imagem de forma independente, sem suavização nos frames.

Ativar máscara de tamanho bruto

Pede ao segmentador para retornar a máscara de tamanho bruto que corresponde ao tamanho da saída do modelo.

O tamanho da máscara bruta (por exemplo, 256 x 256) geralmente é menor que o tamanho da imagem de entrada.

Sem especificar essa opção, o segmentador vai redimensionar a máscara bruta para corresponder ao tamanho da imagem de entrada. Use essa opção se quiser aplicar uma lógica de reescala personalizada ou se a reescala não for necessária para seu caso de uso.

Especifique as opções do segmentador:

Swift

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

Objective-C

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

Por fim, receba uma instância de Segmenter. Transmita as opções especificadas:

Swift

let segmenter = Segmenter.segmenter(options: options)

Objective-C

MLKSegmenter *segmenter = [MLKSegmenter segmenterWithOptions:options];

2. Preparar a imagem de entrada

Para segmentar selfies, faça o seguinte para cada imagem ou frame de vídeo. Se você tiver ativado o modo de stream, precisará criar objetos VisionImage a partir de CMSampleBuffer.

Crie um objeto VisionImage usando um UIImage ou um CMSampleBuffer.

Se você usa um UIImage, siga estas etapas:

  • Crie um objeto VisionImage com o UIImage. Especifique a .orientation correta.

    Swift

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

    Objective-C

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

Se você usa um CMSampleBuffer, siga estas etapas:

  • Especifique a orientação dos dados da imagem contidos no CMSampleBuffer.

    Para ver a orientação da imagem:

    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;
      }
    }
          
  • Crie um objeto VisionImage usando o objeto CMSampleBuffer e a orientação:

    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. Processar a imagem

Transmita o objeto VisionImage para um dos métodos de processamento de imagem do Segmenter. É possível usar o método process(image:) assíncrono ou o método results(in:) síncrono.

Para realizar a segmentação em uma selfie de forma síncrona:

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.

Para realizar a segmentação de uma selfie de maneira assíncrona:

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. Receber a máscara de segmentação

Você pode receber o resultado da segmentação da seguinte forma:

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

Para um exemplo completo de como usar os resultados da segmentação, consulte a amostra do guia de início rápido do Kit de ML.

Dicas para melhorar a performance

A qualidade dos resultados depende da qualidade da imagem de entrada:

  • Para que o Kit de ML tenha um resultado de segmentação preciso, a imagem precisa ter pelo menos 256 x 256 pixels.
  • Se você fizer a segmentação de selfies em um aplicativo em tempo real, considere as dimensões gerais das imagens de entrada. Como as imagens menores podem ser processadas mais rapidamente, para reduzir a latência, capture imagens em resoluções mais baixas, mas tenha em mente os requisitos de resolução acima e faça o assunto ocupar o máximo possível da imagem.
  • Uma imagem com foco inadequado também pode prejudicar a precisão. Se os resultados não forem aceitáveis, peça para o usuário recapturar a imagem.

Se você quiser usar a segmentação em um aplicativo em tempo real, siga estas diretrizes para conseguir as melhores taxas de frames:

  • Use o modo segmentador stream.
  • Capture imagens em uma resolução menor. No entanto, lembre-se também dos requisitos de dimensão de imagem da API.
  • Para processar frames de vídeo, use a API síncrona results(in:) do segmentador. Chame esse método da função captureOutput(_, didOutput:from:) do AVCaptureVideoDataOutputSampleBufferDelegate para receber resultados de forma síncrona do frame de vídeo especificado. Mantenha alwaysDiscardsLateVideoFrames de AVCaptureVideoDataOutput como "true" para limitar as chamadas ao segmentador. Se um novo frame de vídeo ficar disponível enquanto o segmentador estiver em execução, ele será descartado.
  • Se você usar a saída do segmentador para sobrepor elementos gráficos na imagem de entrada, primeiro acesse o resultado do Kit de ML e, em seguida, renderize a imagem e a sobreposição em uma única etapa. Ao fazer isso, você renderiza a superfície de exibição apenas uma vez para cada frame de entrada processado. Consulte as classes previewOverlayView e CameraViewController na amostra do guia de início rápido do Kit de ML para ver um exemplo.