Segmentacja selfie za pomocą ML Kit na iOS

ML Kit udostępnia zoptymalizowany pakiet SDK do segmentacji selfie. Zasoby narzędzia do segmentacji selfie są statycznie połączone z Twoją aplikacją w czasie jej kompilacji. Spowoduje to zwiększenie rozmiaru aplikacji nawet o 24 MB, a opóźnienie interfejsu API może wahać się od 7 ms do około 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 w pliku .xcworkspace. Pakiet ML Kit jest obsługiwany w Xcode w wersji 13.2.1 lub nowszej.

1. Tworzenie instancji narzędzia Segmenter

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

Opcje segmentowania

Tryb segmentowania

Segmenter działa w 2 trybach. Wybierz taką, która pasuje do Twojego przypadku użycia.

STREAM_MODE (default)

Ten tryb jest przeznaczony do strumieniowego przesyłania klatek z filmu lub kamery. W tym trybie segmenter korzysta z wyników z poprzednich klatek, aby uzyskać płynniejszy wynik podziału na segmenty.

SINGLE_IMAGE_MODE (default)

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

Włącz maskę rozmiaru nieprzetworzonego

Wymaga od segmentowania, aby zwracał maskę 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, segmentator przeskaluje maskę nieprzetworzonej, aby dopasować ją do rozmiaru obrazu wejściowego. Rozważ użycie tej opcji, jeśli chcesz zastosować niestandardową logikę skalowania lub przeskalowanie nie jest konieczne w Twoim przypadku.

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 pobierz wystąpienie 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 podzielić selfie na segmenty, wykonaj te czynności w przypadku każdego obrazu lub 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 konta UIImage, wykonaj te czynności:

  • Utwórz obiekt VisionImage z UIImage. Pamiętaj, aby podać prawidłowy atrybut .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 konta CMSampleBuffer, wykonaj te czynności:

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

    Aby określić orientację zdjęcia:

    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 results(in:).

Aby synchronicznie podzielić zdjęcie selfie na segmenty:

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 podziału na segmenty

Wynik segmentacji możesz uzyskać w następujący 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 krótkiego wprowadzenia do ML Kit.

Wskazówki dotyczące poprawy skuteczności

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

  • Aby narzędzie ML Kit mogło otrzymać dokładny wynik podziału na segmenty, obraz powinien mieć co najmniej 256 x 256 pikseli.
  • Jeśli segmentujesz selfie w aplikacji, która pokazuje dane 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. Pamiętaj jednak o powyższych wymaganiach dotyczących rozdzielczości i dopilnuj, aby obiekt zajmował jak najwięcej miejsca.
  • Słaba ostrość obrazu może również wpływać na dokładność. Jeśli nie uda się uzyskać zadowalających wyników, poproś użytkownika o ponowne zrobienie zdjęcia.

Jeśli chcesz wykorzystać podział na segmenty w aplikacji przesyłających dane w czasie rzeczywistym, postępuj zgodnie z tymi wytycznymi, aby uzyskać najlepszą liczbę klatek:

  • Użyj trybu segmentacji stream.
  • Rozważ robienie zdjęć w niższej rozdzielczości. Pamiętaj jednak o wymaganiach dotyczących wymiarów obrazów w tym interfejsie API.
  • Do przetwarzania klatek wideo użyj synchronicznego interfejsu API results(in:) narzędzia segmentacji. Wywołaj tę metodę z funkcji AVCaptureVideoDataOutputSampleBufferDelegate w funkcji captureOutput(_, didOutput:from:), aby synchronicznie pobierać wyniki z danej klatki wideo. Aby ograniczyć wywołania do segmentu, pozostaw wartość „prawda” w polu AVCaptureVideoDataOutput w ustawieniu alwaysDiscardsLateVideoFrames. Jeśli nowa klatka wideo stanie się dostępna, gdy działa moduł segmentacji, zostanie usunięta.
  • Jeśli używasz danych wyjściowych narzędzia do segmentacji w celu nałożenia grafiki na obraz wejściowy, najpierw pobierz wynik z ML Kit, a następnie w jednym kroku wyrenderuj obraz i nakładkę. Dzięki temu renderujesz na wyświetlaczu tylko raz dla każdej przetworzonej ramki wejściowej. Przykład znajdziesz w klasach previewOverlayView i CameraViewController w przykładzie krótkiego wprowadzenia do ML Kit.