ב-ML Kit יש ערכת SDK לאופטימיזציה של פילוח תמונות סלפי. הנכסים של Selfie Segmenter מקושרים באופן סטטי לאפליקציה שלכם בזמן ה-build. הפעולה הזו תגדיל את גודל האפליקציה ב-24MB לכל היותר, וזמן האחזור של ה-API עשוי להשתנות מ-7ms עד 12ms, בהתאם לגודל קובץ הקלט, כפי שנמדד ב-iPhone X.
רוצה לנסות?
- כדאי לנסות את האפליקציה לדוגמה כדי לראות דוגמה לשימוש ב-API הזה.
לפני שמתחילים
מוסיפים את ספריות ML Kit הבאות ל-Podfile:
pod 'GoogleMLKit/SegmentationSelfie', '7.0.0'
אחרי שמתקינים או מעדכנים את ה-Pods של הפרויקט, פותחים את פרויקט Xcode באמצעות הקובץ
xcworkspace
שלו. ML Kit נתמך ב-Xcode בגרסה 13.2.1 ואילך.
1. יצירת מכונה של Segmenter
כדי לבצע פילוח בתמונה של סלפי, קודם יוצרים מכונה של Segmenter
באמצעות SelfieSegmenterOptions
, ואז אפשר לציין את הגדרות הפילוח.
אפשרויות של פילוח
מצב פילוח
ה-Segmenter
פועל בשני מצבים. חשוב לבחור את התרחיש שמתאים לתרחיש לדוגמה שלכם.
STREAM_MODE (default)
המצב הזה מיועד לשידור מסגרות מסרטון או ממצלמה. במצב הזה, הכלי ליצירת פלחים ישתמש בתוצאות מסגרות קודמות כדי להחזיר תוצאות חלוקה חלקות יותר.
SINGLE_IMAGE_MODE (default)
המצב הזה מיועד לתמונות בודדות שלא קשורות זו לזו. במצב הזה, הכלי לחלוקת הפריימים יעבד כל תמונה בנפרד, ללא החלקה על פני הפריימים.
הפעלת מסכת גודל גולמי
הבקשה ממערכת חלוקת הפריימים להחזיר את מסכת הגודל הגולמי שתואמת לגודל הפלט של המודל.
בדרך כלל, גודל המסכה הגולמי (למשל 256x256) קטן יותר מגודל התמונה להזנה.
בלי לציין את האפשרות הזו, הכלי לחלוקת התמונה לפלחים ישנה את קנה המידה של המסכה הגולמית כך שיתאים לגודל של תמונה הקלט. כדאי להשתמש באפשרות הזו אם רוצים להחיל לוגיקה מותאמת אישית של שינוי קנה מידה, או אם שינוי קנה המידה לא נדרש בתרחיש לדוגמה.
מציינים את האפשרויות של הכלי ליצירת פלחים:
let options = SelfieSegmenterOptions() options.segmenterMode = .singleImage options.shouldEnableRawSizeMask = true
MLKSelfieSegmenterOptions *options = [[MLKSelfieSegmenterOptions alloc] init]; options.segmenterMode = MLKSegmenterModeSingleImage; options.shouldEnableRawSizeMask = YES;
לבסוף, מקבלים מופע של Segmenter
. מעבירים את האפשרויות שציינתם:
let segmenter = Segmenter.segmenter(options: options)
MLKSegmenter *segmenter = [MLKSegmenter segmenterWithOptions:options];
2. הכנת קובץ הקלט
כדי לפלח סלפי, מבצעים את הפעולות הבאות לכל תמונה או פריים בסרטון.
אם הפעלתם את מצב הסטרימינג, עליכם ליצור אובייקטים מסוג VisionImage
מ-CMSampleBuffer
.
יוצרים אובייקט VisionImage
באמצעות UIImage
או CMSampleBuffer
.
אם אתם משתמשים ב-UIImage
, עליכם לפעול לפי השלבים הבאים:
- יוצרים אובייקט
VisionImage
באמצעותUIImage
. חשוב לציין את הערך הנכון של.orientation
.let image = VisionImage(image: UIImage) visionImage.orientation = image.imageOrientation
MLKVisionImage *visionImage = [[MLKVisionImage alloc] initWithImage:image]; visionImage.orientation = image.imageOrientation;
אם אתם משתמשים ב-CMSampleBuffer
, עליכם לפעול לפי השלבים הבאים:
-
מציינים את הכיוון של נתוני התמונה שמכיל השדה
CMSampleBuffer
.כדי לקבל את כיוון התמונה:
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; } }
- יוצרים אובייקט
VisionImage
באמצעות האובייקטCMSampleBuffer
והכיוון: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. עיבוד התמונה
מעבירים את האובייקט VisionImage
לאחת משיטות עיבוד התמונות של Segmenter
. אפשר להשתמש בשיטה האסינכרונית process(image:)
או בשיטה הסינכרונית results(in:)
.
כדי לבצע חלוקה זמנית של תמונת סלפי למקטעים:
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.
כדי לבצע פילוח של תמונת סלפי באופן אסינכרוני:
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. אחזור של מסכת הפיצול
אפשר לקבל את תוצאת הפיצול באופן הבא:
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); }
דוגמה מלאה לשימוש בתוצאות הפילוח מופיעה בדוגמה למתחילים ב-ML Kit.
טיפים לשיפור הביצועים
איכות התוצאות תלויה באיכות של קובץ הקלט:
- כדי ש-ML Kit ייצור תוצאה מדויקת של פילוח, התמונה צריכה להיות בגודל 256x256 פיקסלים לפחות.
- אם מבצעים פילוח של תמונות סלפי באפליקציה בזמן אמת, כדאי גם להביא בחשבון את המאפיינים הכוללים של תמונות הקלט. קל יותר לעבד תמונות קטנות יותר, ולכן כדי לקצר את זמן האחזור, כדאי לצלם תמונות ברזולוציות נמוכות יותר. עם זאת, חשוב לזכור את דרישות הרזולוציה שלמעלה ולוודא שהאובייקט תופס כמה שיותר מהתמונה.
- גם מיקוד לקוי של התמונה יכול להשפיע על הדיוק. אם התוצאות לא יהיו טובות, בקשו מהמשתמש לצלם מחדש את התמונה.
אם אתם רוצים להשתמש בפילוח באפליקציה בזמן אמת, כדאי לפעול לפי ההנחיות הבאות כדי להשיג את שיעורי הפריימים הטובים ביותר:
- משתמשים במצב הפילוח
stream
. - כדאי לצלם תמונות ברזולוציה נמוכה יותר. עם זאת, חשוב לזכור גם את הדרישות לגבי מידות התמונות ב-API הזה.
- כדי לעבד פריימים של סרטונים, משתמשים ב-API הסינכרוני
results(in:)
של הכלי לחלוקת סרטונים לקטעים. כדי לקבל תוצאות מסונכרנות מהפריים הנתון של הסרטון, צריך להפעיל את השיטה הזו מהפונקציה captureOutput(_, didOutput:from:) של AVCaptureVideoDataOutputSampleBufferDelegate. כדי להגביל את הקריאות למחלק הקטעים, משאירים את הערך של alwaysDiscardsLateVideoFrames ב-AVCaptureVideoDataOutput כ-true. אם פריים חדש של סרטון יהיה זמין בזמן שהכלי לחלוקת הסרטון לקטעים פועל, הוא יושמט. - אם משתמשים בפלט של הכלי לחלוקה לפלחים כדי להוסיף שכבת-על של גרפיקה לתמונה הקלט, קודם מקבלים את התוצאה מ-ML Kit ואז מבצעים עיבוד (רנדור) של התמונה ושל שכבת-העל בשלב אחד. כך מבצעים רינדור למשטח התצוגה רק פעם אחת לכל מסגרת קלט שעברה עיבוד. דוגמה לכך מופיעה בכיתות previewOverlayView ו-CameraViewController בדוגמה למתחילים ב-ML Kit.