פילוח סלפי באמצעות ערכת ML ב-iOS

ערכת ML מספקת SDK אופטימלי לפילוח סלפי. הנכסים של הכלי לפילוח תמונות סלפי מקושרים סטטיים לאפליקציה שלך בזמן השימוש בהם. פעולה זו תגדיל את גודל האפליקציה ב-24MB ואת זמן האחזור של ה-API עשוי להשתנות מ-7 אלפיות השנייה לכ-12 אלפיות השנייה, בהתאם לגודל תמונת הקלט, כפי שנמדד ב-iPhone X.

לפני שמתחילים

  1. יש לכלול את הספריות הבאות של למידת המכונה ב-Podfile:

    pod 'GoogleMLKit/SegmentationSelfie', '3.2.0'
    
  2. אחרי ההתקנה או העדכון של ה-Pods של הפרויקט, פותחים את פרויקט Xcode באמצעות ה-xcworkspace שלו. ערכת ה-ML Kit נתמכת בגרסה Xcode 13.2.1 ואילך.

1. יצירת מכונה של מפלח

כדי לבצע פילוח בתמונת סלפי, קודם צריך ליצור מכונה של Segmenter עם SelfieSegmenterOptions ולציין את הגדרות הפילוח.

אפשרויות פילוח

מצב פילוח

ה-Segmenter פועל בשני מצבים. הקפידו לבחור את סוג ההתאמה המתאים לתרחיש לדוגמה שלכם.

STREAM_MODE (default)

המצב הזה מיועד לסטרימינג של מסגרות מהסרטון או המצלמה. במצב כזה, הפלח ממנף תוצאות ממסגרות קודמות כדי להחזיר תוצאות פילוח חלקות יותר.

SINGLE_IMAGE_MODE (default)

המצב הזה מיועד לתמונות בודדות שאינן קשורות. במצב זה, הפילוח יעבד כל תמונה בנפרד, ללא החלקת מסגרת.

הפעלת מסכה גולמית של גודל

מבקש מהפילוח להחזיר את מסכת הגולמיות בגודל שתואמת לפלט הדגם.

הגודל של המסכה הגולמית (למשל, 256x256) קטן יותר מגודל תמונת הקלט.

ללא ציון האפשרות הזו, כלי הסינון משנה את גודל המסכה הגולמית בהתאם לגודל תמונת הקלט. כדאי להשתמש באפשרות הזו אם אתם רוצים להחיל לוגיקת התאמה אישית של קנה המידה או שינוי גודל בהתאם לתרחיש לדוגמה.

מציינים את אפשרויות הפילוח:

Swift

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

Objective-C

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

לבסוף, יש לקבל מכונה של Segmenter. העברת האפשרויות שציינת:

Swift

let segmenter = Segmenter.segmenter(options: options)

Objective-C

MLKSegmenter *segmenter = [MLKSegmenter segmenterWithOptions:options];

2. הכנת תמונת הקלט

כדי לפלח תמונות סלפי, צריך לבצע את הפעולות הבאות לכל תמונה או מסגרת של סרטון. אם הפעלת את מצב הסטרימינג, עליך ליצור VisionImage אובייקטים מתוך CMSampleBuffer שנ'.

יצירת אובייקט VisionImage באמצעות UIImage או CMSampleBuffer.

אם נעשה שימוש ב-UIImage, צריך לבצע את השלבים הבאים:

  • יצירת אובייקט VisionImage באמצעות UIImage. חשוב לציין את .orientation הנכון.

    Swift

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

    Objective-C

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

אם נעשה שימוש ב-CMSampleBuffer, צריך לבצע את השלבים הבאים:

  • צריך לציין את הכיוון של נתוני התמונות ב-CMSampleBuffer.

    כדי לקבל את כיוון התמונה:

    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;
      }
    }
          
  • יצירת אובייקט VisionImage באמצעות האובייקט והכיוון של CMSampleBuffer:

    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. עיבוד התמונה

יש להעביר את האובייקט VisionImage לאחת משיטות עיבוד התמונות של Segmenter. יש לך אפשרות להשתמש בשיטת process(image:) האסינכרונית או בשיטת results(in:) הסינכרונית.

כדי לבצע פילוח בתמונת סלפי באופן סינכרוני:

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.

כדי לבצע פילוח על תמונת סלפי באופן אסינכרוני:

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. קבלת מסכת פילוח

כך תוכלו לקבל את תוצאת הפילוח:

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

דוגמה מלאה לשימוש בתוצאות הפילוח זמינה בדוגמה למתחילים של ML Kit.

טיפים לשיפור הביצועים

האיכות של התוצאות תלויה באיכות של תמונת הקלט:

  • כדי שלמידת המכונה של ML תקבל תוצאת פילוח מדויקת, התמונה צריכה להיות בגודל של 256x256 פיקסלים לפחות.
  • אם מבצעים פילוח תמונות סלפי באפליקציה בזמן אמת, מומלץ גם לשקול את המידות הכוללות של תמונות הקלט. עיבוד של תמונות קטנות מתבצע מהר יותר, לכן כדי לקצר את זמן האחזור, כדאי לצלם תמונות ברזולוציות נמוכות יותר, אבל חשוב לזכור דרישות הסף שלמעלה ולוודא שהתמונה תופסת כמה שיותר.
  • גם מיקוד בתמונה באיכות נמוכה עשוי להשפיע על הדיוק. אם לא מתקבלות תוצאות מקובלות, מבקשים מהמשתמש לצלם מחדש את התמונה.

אם רוצים להשתמש בפילוח באפליקציה בזמן אמת, יש לפעול לפי ההנחיות הבאות כדי להשיג את קצב הפריימים הטוב ביותר: