iOS'te ML Kiti ile selfie segmentasyonu

ML Kit, selfie segmentasyonu için optimize edilmiş bir SDK sağlar. Selfie Segmenter öğeleri, derleme sırasında uygulamanıza statik olarak bağlanır. Bu işlem, uygulamanızın boyutunu 24 MB'a kadar artırır ve API gecikmesi, iPhone X'te ölçülen giriş resim boyutuna bağlı olarak yaklaşık 7 ms ile yaklaşık 12 ms arasında değişebilir.

Deneyin

Başlamadan önce

  1. Podfile dosyanıza aşağıdaki ML Kit kitaplıklarını ekleyin:

    pod 'GoogleMLKit/SegmentationSelfie', '7.0.0'
    
  2. Projenizin Pod'larını yükledikten veya güncelledikten sonra .xcworkspace uzantılı Xcode projenizi açın. ML Kit, Xcode 13.2.1 veya sonraki sürümlerde desteklenir.

1. Segmenter örneği oluşturma

Selfie resimlerinde segmentasyon yapmak için önce SelfieSegmenterOptions ile Segmenter örneği oluşturun ve isteğe bağlı olarak segmentasyon ayarlarını belirtin.

Segmentör seçenekleri

Segmenter Modu

Segmenter iki modda çalışır. Kullanım alanınıza uygun olanı seçtiğinizden emin olun.

STREAM_MODE (default)

Bu mod, video veya kameradan kare aktarmak için tasarlanmıştır. Bu modda segmenter, daha düzgün segmentasyon sonuçları döndürmek için önceki karelerdeki sonuçlardan yararlanır.

SINGLE_IMAGE_MODE (default)

Bu mod, birbiriyle alakalı olmayan tek resimler için tasarlanmıştır. Bu modda segmenter, kareler üzerinde yumuşatma yapmadan her bir görüntüyü bağımsız olarak işler.

Ham boyut maskesini etkinleştirin

Segmentörden, model çıkış boyutuyla eşleşen ham boyut maskesini döndürmesini ister.

Ham maske boyutu (ör. 256x256) genellikle giriş resim boyutundan daha küçüktür.

Bu seçenek belirtilmeden segmenter, ham maskeyi giriş resmi boyutuna uyacak şekilde yeniden ölçeklendirir. Özelleştirilmiş yeniden ölçeklendirme mantığı uygulamak istiyorsanız veya kullanım alanınız için yeniden ölçeklendirme gerekmiyorsa bu seçeneği kullanabilirsiniz.

Segmentör seçeneklerini belirtin:

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

Son olarak, Segmenter örneği alın. Belirttiğiniz seçenekleri iletin:

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

2. Giriş resmini hazırlama

Selfie'leri segmentlere ayırmak için her resim veya video karesi için aşağıdakileri yapın. Akış modunu etkinleştirdiyseniz CMSampleBuffer'lerden VisionImage nesneleri oluşturmanız gerekir.

UIImage veya CMSampleBuffer kullanarak bir VisionImage nesnesi oluşturun.

UIImage kullanıyorsanız şu adımları uygulayın:

  • UIImage ile bir VisionImage nesnesi oluşturun. Doğru .orientation değerini belirttiğinizden emin olun.
    SwiftObjective-C
    let image = VisionImage(image: UIImage)
    visionImage.orientation = image.imageOrientation
    MLKVisionImage *visionImage = [[MLKVisionImage alloc] initWithImage:image];
    visionImage.orientation = image.imageOrientation;

CMSampleBuffer kullanıyorsanız şu adımları uygulayın:

  • CMSampleBuffer içinde bulunan resim verilerinin yönünü belirtin.

    Resim yönünü almak için:

    SwiftObjective-C
    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;
      }
    }
          
  • CMSampleBuffer nesnesini ve yönünü kullanarak bir VisionImage nesnesi oluşturun:
    SwiftObjective-C
    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. Resmi işleme

VisionImage nesnesini Segmenter'un resim işleme yöntemlerinden birine iletin. Eşzamansız process(image:) yöntemini veya eşzamanlı results(in:) yöntemini kullanabilirsiniz.

Selfie resimlerinde segmentasyonu senkronize olarak gerçekleştirmek için:

SwiftObjective-C
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.

Selfie resminde segmentasyonu eşzamansız olarak gerçekleştirmek için:

SwiftObjective-C
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. Segmentasyon maskesini alma

Segmentasyon sonucunu aşağıdaki şekilde alabilirsiniz:

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

Segmentasyon sonuçlarının nasıl kullanılacağına dair tam bir örnek için lütfen ML Kit hızlı başlangıç örneğine bakın.

Performansı iyileştirmeye yönelik ipuçları

Sonuçlarınızın kalitesi, giriş resminin kalitesine bağlıdır:

  • ML Kit'in doğru bir segmentasyon sonucu elde edebilmesi için resmin en az 256x256 piksel olması gerekir.
  • Gerçek zamanlı bir uygulamada selfie segmentasyonu yapıyorsanız giriş resimlerinin genel boyutlarını da dikkate alabilirsiniz. Küçük resimler daha hızlı işlenebilir. Bu nedenle, gecikmeyi azaltmak için resimleri daha düşük çözünürlüklerde çekin ancak yukarıdaki çözünürlük koşullarını göz önünde bulundurun ve öznenin resmin mümkün olduğunca büyük bir kısmını kaplamasını sağlayın.
  • Resmin odaklanmaması da doğruluğu etkileyebilir. Kabul edilebilir sonuçlar alamazsanız kullanıcıdan resmi yeniden çekmesini isteyin.

Segmentasyonu gerçek zamanlı bir uygulamada kullanmak istiyorsanız en iyi kare hızlarını elde etmek için aşağıdaki yönergeleri uygulayın:

  • stream segmentör modunu kullanın.
  • Resimleri daha düşük çözünürlükte çekmeyi deneyin. Ancak bu API'nin resim boyutu koşullarını da göz önünde bulundurun.
  • Video karelerini işlemek için segmenterin results(in:) senkron API'sini kullanın. Belirtilen video karesinden eşzamanlı olarak sonuç almak için bu yöntemi AVCaptureVideoDataOutputSampleBufferDelegate'ın captureOutput(_, didOutput:from:) işlevinden çağırın. Segmentere yapılan çağrıları azaltmak için AVCaptureVideoDataOutput'un alwaysDiscardsLateVideoFrames özelliğini true olarak tutun. Segmenter çalışırken yeni bir video karesi kullanılabilir hale gelirse bu kare atlanır.
  • Giriş resmine grafik yer paylaşımı yapmak için segmentörün çıktısını kullanıyorsanız önce ML Kit'ten sonucu alın, ardından resmi ve yer paylaşımını tek bir adımda oluşturun. Böylece, işlenen her giriş çerçevesi için ekran yüzeyinde yalnızca bir kez oluşturma işlemi gerçekleştirirsiniz. Örnek için Makine Öğrenimi Kiti hızlı başlangıç örneğindeki previewOverlayView ve CameraViewController sınıflarına bakın.