Segmentacja selfie za pomocą ML Kit na iOS

ML Kit to zoptymalizowany pakiet SDK do segmentacji selfie. Zasoby segmentu selfie są statycznie połączone z Twoją aplikacją w czasie tworzenia kampanii. Zwiększy to rozmiar aplikacji o nawet 24 MB, a opóźnienie interfejsu API może wynosić od 7 ms do 12 ms w zależności od rozmiaru obrazu wejściowego (mierzonego na iPhonie X).

Wypróbuj

Zanim zaczniesz

  1. W pliku Podfile umieść te biblioteki ML Kit:

    pod 'GoogleMLKit/SegmentationSelfie', '3.2.0'
    
  2. Po zainstalowaniu lub zaktualizowaniu podów projektu 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 segmentacji

Aby przeprowadzić segmentację zdjęcia selfie, najpierw utwórz wystąpienie elementu Segmenter z atrybutem SelfieSegmenterOptions i opcjonalnie określ ustawienia podziału na segmenty.

Opcje segmentowania

Tryb segmentacji

Segmenter działa w 2 trybach. Wybierz taki, który pasuje do Twojego przypadku użycia.

STREAM_MODE (default)

Ten tryb jest przeznaczony do strumieniowania klatek filmu lub kamery. W tym trybie segmenter korzysta z wyników z poprzednich klatek, aby zwrócić płynniejszy wynik segmentacji.

SINGLE_IMAGE_MODE (default)

Ten tryb jest przeznaczony do pojedynczych zdjęć, które nie są ze sobą powiązane. W tym trybie segmenter przetwarza każde zdjęcie niezależnie, bez wygładzania klatek.

Włącz maskę rozmiaru nieprzetworzonego

Prosi segmentację o zwrócenie maski rozmiaru nieprzetworzonego, która odpowiada rozmiarowi wyjściowemu modelu.

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

Jeśli nie określisz tej opcji, segmenter przeskaluje maskę nieprzetworzoną, aby dopasować ją do rozmiaru obrazu wejściowego. Rozważ użycie tej opcji, jeśli chcesz zastosować niestandardową logikę zmiany skali lub ponowne skalowanie nie jest potrzebne w Twoim przypadku użycia.

Określ opcje segmentowania:

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 pobierz instancję Segmenter. Prześlij określone opcje:

Swift

let segmenter = Segmenter.segmenter(options: options)

Objective-C

MLKSegmenter *segmenter = [MLKSegmenter segmenterWithOptions:options];

2. Przygotowywanie obrazu wejściowego

Aby podzielić selfie, wykonaj te czynności w przypadku każdego zdjęcia lub każdej klatki filmu. Jeśli masz włączony tryb strumieniowania, musisz utworzyć obiekty VisionImage z CMSampleBuffer.

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

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

  • Utwórz obiekt VisionImage za pomocą UIImage. Pamiętaj, by określić 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 zdjęć zawartych w pliku CMSampleBuffer

    Aby sprawdzić 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 za pomocą CMSampleBuffer obiekt i orientacja:

    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 zdjęcia

Przekaż obiekt VisionImage do jednej z metod przetwarzania obrazu w Segmenter. Możesz używać asynchronicznej metody process(image:) lub synchronicznej 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. Pobierz maskę podziału na segmenty

Wynik podziału na segmenty 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łen przykład korzystania z wyników segmentacji znajdziesz Przykład krótkiego wprowadzenia do ML Kit.

Wskazówki dotyczące poprawy skuteczności

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

  • Aby narzędzie ML Kit mogło uzyskać dokładny wynik podziału na segmenty, obraz powinien mieć co najmniej 256 × 256 pikseli.
  • Jeśli segmentujesz selfie w aplikacji działającej w czasie rzeczywistym, warto też wziąć pod uwagę ogólne wymiary zdjęć wejściowych. Mniejsze obrazy mogą być przetwarzane szybciej, więc aby zmniejszyć opóźnienia, rób zdjęcia w niższej rozdzielczości, ale pamiętaj o powyższych wymaganiach dotyczących rozdzielczości i postaraj się, aby obiekt zajmował jak najwięcej miejsca.
  • Słaba ostrość obrazu również może mieć wpływ na dokładność. Jeśli wyniki nie są zadowalające, poproś użytkownika o ponowne wykonanie zdjęcia.

Jeśli chcesz zastosować segmentację w aplikacji w czasie rzeczywistym, postępuj zgodnie z tymi wskazówkami, aby uzyskać najlepszą liczbę klatek:

  • Użyj trybu segmentacji stream.
  • Rozważ robienie zdjęć w niższej rozdzielczości. Pamiętaj jednak o wymaganiach tego interfejsu API dotyczących wymiarów zdjęć.
  • Do przetwarzania klatek wideo używaj synchronicznego interfejsu API results(in:). Wywołaj tę metodę z funkcji captureOutput(_, didOutput:from:) w funkcji AVCaptureVideoDataOutputSampleBufferDelegate w celu synchronicznego pobierania wyników z danej klatki wideo. Aby ograniczyć wywołania do segmentatora, w atrybucie AVCaptureVideoDataOutput ustaw wartość alwaysDiscardsLateVideoFrames na wartość true (prawda). Jeśli w trakcie działania segmentacji pojawi się nowa klatka wideo, zostanie ona usunięta.
  • Jeśli użyjesz danych wyjściowych segmentatora do nałożenia grafiki na obraz wejściowy, najpierw pobierz wynik z pakietu ML Kit, a potem w jednym kroku wyrenderuj obraz i nakładkę. Dzięki temu renderowanie na powierzchni wyświetlania będzie odbywać się tylko raz na każdą przetworzoną klatkę wejściową. Przykład znajdziesz w klasach previewOverlayView i CameraViewController w przykładzie krótkiego wprowadzenia do pakietu ML Kit.