Segmentacja selfie za pomocą ML Kit na iOS

ML Kit udostępnia zoptymalizowany pakiet SDK do segmentacji selfie. Komponenty narzędzia Selfie Segmenter są statycznie połączone z aplikacją w momencie jej tworzenia. Zwiększy to rozmiar aplikacji o maksymalnie 24 MB, a opóźnienie interfejsu API może wynosić od ok. 7 ms do ok. 12 ms w zależności od rozmiaru obrazu wejściowego (pomiar na iPhonie X).

Wypróbuj

Zanim zaczniesz

  1. W pliku Podfile uwzględnij te biblioteki ML Kit:

    pod 'GoogleMLKit/SegmentationSelfie', '8.0.0'
    
  2. Po zainstalowaniu lub zaktualizowaniu Pods w projekcie otwórz projekt Xcode za pomocą pliku .xcworkspace. ML Kit jest obsługiwany w Xcode w wersji 13.2.1 lub nowszej.

1. Tworzenie instancji klasy Segmenter

Aby przeprowadzić segmentację na zdjęciu selfie, najpierw utwórz instancję SegmenterSelfieSegmenterOptions i opcjonalnie określ ustawienia segmentacji.

Opcje segmentatora

Tryb segmentacji

Segmenter działa w 2 trybach. Wybierz opcję, która odpowiada Twojemu przypadkowi użycia.

STREAM_MODE (default)

Ten tryb jest przeznaczony do przesyłania strumieniowego klatek z filmu lub aparatu. W tym trybie segmentator wykorzystuje wyniki z poprzednich klatek, aby zwracać płynniejsze wyniki segmentacji.

SINGLE_IMAGE_MODE (default)

Ten tryb jest przeznaczony dla pojedynczych, niezwiązanych ze sobą obrazów. W tym trybie segmentator przetwarza każdy obraz niezależnie, bez wygładzania klatek.

Włącz maskę rozmiaru surowego

Prosi segmentator o zwrócenie maski rozmiaru pierwotnego, która pasuje do rozmiaru danych wyjściowych modelu.

Rozmiar surowej maski (np. 256 x 256) jest zwykle mniejszy niż rozmiar obrazu wejściowego.

Jeśli nie określisz tej opcji, segmentator przeskaluje surową maskę, aby dopasować ją do rozmiaru obrazu wejściowego. Rozważ użycie tej opcji, jeśli chcesz zastosować dostosowaną logikę zmiany skali lub jeśli w Twoim przypadku użycia zmiana skali nie jest potrzebna.

Określ opcje segmentacji:

Swift

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

Objective-C

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

Na koniec uzyskaj instancję Segmenter. Przekaż określone opcje:

Swift

let segmenter = Segmenter.segmenter(options: options)

Objective-C

MLKSegmenter *segmenter = [MLKSegmenter segmenterWithOptions:options];

2. Przygotowywanie obrazu wejściowego

Aby segmentować selfie, wykonaj te czynności w przypadku każdego zdjęcia lub klatki filmu. Jeśli włączysz tryb strumieniowania, musisz utworzyć obiekty VisionImageCMSampleBuffer.

Utwórz obiekt VisionImage za pomocą UIImage lub CMSampleBuffer.

Jeśli używasz UIImage, wykonaj te czynności:

  • Utwórz obiekt VisionImage z wartością UIImage. Pamiętaj, aby podać prawidłowy .orientation.

    Swift

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

    Objective-C

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

Jeśli używasz CMSampleBuffer, wykonaj te czynności:

  • Określ orientację danych obrazu zawartych w elemencie CMSampleBuffer.

    Aby uzyskać orientację obrazu:

    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;
      }
    }
          
  • Utwórz obiekt VisionImage, używając obiektu CMSampleBuffer i orientacji:

    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. Przetwarzanie obrazu

Przekaż obiekt VisionImage do jednej z metod przetwarzania obrazu w Segmenter. Możesz użyć asynchronicznej metody process(image:) lub synchronicznej metody results(in:).

Aby synchronicznie przeprowadzić segmentację zdjęcia selfie:

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.

Aby asynchronicznie przeprowadzić segmentację zdjęcia selfie:

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. Pobieranie maski segmentacji

Wynik segmentacji możesz uzyskać w ten sposób:

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

Pełny przykład użycia wyników segmentacji znajdziesz w przykładzie szybkiego startu ML Kit.

Wskazówki dotyczące poprawy skuteczności

Jakość wyników zależy od jakości obrazu wejściowego:

  • Aby ML Kit uzyskał dokładny wynik segmentacji, obraz powinien mieć rozmiar co najmniej 256 x 256 pikseli.
  • Jeśli wykonujesz segmentację selfie w aplikacji działającej w czasie rzeczywistym, możesz też wziąć pod uwagę ogólne wymiary obrazów wejściowych. Mniejsze obrazy można przetwarzać szybciej, więc aby zmniejszyć opóźnienie, rób zdjęcia w niższej rozdzielczości. Pamiętaj jednak o wymaganiach dotyczących rozdzielczości i upewnij się, że fotografowany obiekt zajmuje jak największą część obrazu.
  • Na dokładność może też wpływać słaba ostrość obrazu. Jeśli wyniki nie będą zadowalające, poproś użytkownika o ponowne zrobienie zdjęcia.

Jeśli chcesz używać segmentacji w aplikacji działającej w czasie rzeczywistym, postępuj zgodnie z tymi wytycznymi, aby uzyskać najlepszą liczbę klatek na sekundę:

  • Użyj trybu segmentacji stream.
  • Rozważ robienie zdjęć w niższej rozdzielczości. Pamiętaj jednak o wymaganiach dotyczących wymiarów obrazu w tym interfejsie API.
  • Do przetwarzania klatek wideo użyj synchronicznego interfejsu API results(in:) segmentatora. Wywołaj tę metodę z funkcji captureOutput(_, didOutput:from:) protokołu AVCaptureVideoDataOutputSampleBufferDelegate, aby synchronicznie uzyskać wyniki z danej klatki wideo. Ustaw wartość alwaysDiscardsLateVideoFramesAVCaptureVideoDataOutput na true, aby ograniczyć wywołania segmentatora. Jeśli podczas działania segmentatora pojawi się nowa klatka wideo, zostanie ona odrzucona.
  • Jeśli używasz danych wyjściowych segmentatora do nakładania grafiki na obraz wejściowy, najpierw uzyskaj wynik z ML Kit, a potem w jednym kroku wyrenderuj obraz i nałóż na niego grafikę. Dzięki temu renderowanie na powierzchnię wyświetlania odbywa się tylko raz dla każdej przetworzonej klatki wejściowej. Przykład znajdziesz w klasach previewOverlayViewCameraViewControllerprzykładowym projekcie ML Kit.