הוספת תוויות לתמונות באמצעות מודל בהתאמה אישית ב-iOS

אתם יכולים להשתמש ב-ML Kit כדי לזהות ישויות בתמונה ולהוסיף להן תוויות. ה-API הזה תומך במגוון רחב של מודלים מותאמים אישית לסיווג תמונות. במאמר מודלים בהתאמה אישית עם ML Kit מוסבר על דרישות התאימות של המודלים, איפה אפשר למצוא מודלים מאומנים מראש ואיך לאמן מודלים משלכם.

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

מודל בחבילה מודל מתארח
המודל הוא חלק מחבילת ה-APK של האפליקציה, ולכן הוא מגדיל את הגודל שלה. המודל לא נכלל ב-APK. הוא מתארח על ידי העלאה ל-Cloud Storage. מומלץ להשתמש ב-Cloud Storage for Firebase.
המודל זמין באופן מיידי, גם כשמכשיר Android אופליין האפליקציה צריכה לכלול קוד להורדת המודל לפי דרישה
אין צורך בפרויקט Firebase נדרש פרויקט Firebase (אם משתמשים ב-Cloud Storage for Firebase).
כדי לעדכן את המודל, צריך לפרסם מחדש את האפליקציה עדכון המודל בלי לפרסם מחדש את האפליקציה
אין בדיקות A/B מובנות בדיקות A/B עם הגדרת תצורה מרחוק ב-Firebase

רוצה לנסות?

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

  1. מוסיפים את ספריות ML Kit ל-Podfile:

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

  3. אם רוצים להוריד מודל באמצעות Cloud Storage for Firebase, צריך לוודא שהוספתם את Firebase לפרויקט iOS, אם עדיין לא עשיתם זאת. זה לא נדרש כשמצרפים את המודל לחבילה.

1. טעינת המודל

הגדרת מקור מודל מקומי

כדי לארוז את המודל עם האפליקציה:

  1. מעתיקים את קובץ המודל (בדרך כלל מסתיים ב-.tflite או ב-.lite) לפרויקט Xcode, ומוודאים לבחור באפשרות Copy bundle resources כשעושים זאת. קובץ המודל ייכלל בקובץ AAB ויהיה זמין ל-ML Kit.

  2. יוצרים אובייקט LocalModel ומציינים את הנתיב לקובץ המודל:

    Swift

    let localModel = LocalModel(path: localModelFilePath)

    Objective-C

    MLKLocalModel *localModel =
        [[MLKLocalModel alloc] initWithPath:localModelFilePath];

הגדרת מקור מודל שמתארח מרחוק

כדי להשתמש במודל שמתארח מרחוק, צריך להוריד את קובץ המודל לאחסון המקומי של המכשיר באמצעות הלוגיקה של האפליקציה שלכם, ואז לטעון אותו כמודל מקומי. מומלץ להשתמש ב-Cloud Storage for Firebase כדי לארח מודל. פרטים על ההטמעה מופיעים במדריך להעברת נתונים מ-Firebase ML ל-Cloud Storage.

הגדרת הכלי לתווית תמונות

אחרי שמגדירים את מקורות המודל, יוצרים אובייקט ImageLabeler מאחד מהם.

אלה האפשרויות הזמינות:

אפשרויות
confidenceThreshold

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

maxResultCount

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

אם יש לכם רק מודל שצורף באופן מקומי, פשוט יוצרים תווית מהאובייקט LocalModel:

Swift

let options = CustomImageLabelerOptions(localModel: localModel)
options.confidenceThreshold = NSNumber(value: 0.0)
let imageLabeler = ImageLabeler.imageLabeler(options: options)

Objective-C

MLKCustomImageLabelerOptions *options =
    [[MLKCustomImageLabelerOptions alloc] initWithLocalModel:localModel];
options.confidenceThreshold = @(0.0);
MLKImageLabeler *imageLabeler =
    [MLKImageLabeler imageLabelerWithOptions:options];

אם יש לכם מודל שמתארח מרחוק, תצטרכו לוודא שהוא הורד לפני שתפעילו פתרונות חכמים.

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

Swift

// Path where your download logic saves the model
let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let localModelURL = documentDirectory.appendingPathComponent("my_remote_model.tflite")

let model: LocalModel
if FileManager.default.fileExists(atPath: localModelURL.path) {
  // Use the downloaded model
  model = LocalModel(path: localModelURL.path)
} else {
  // Fall back to bundled model
  guard let bundledModelPath = Bundle.main.path(forResource: "model", ofType: "tflite") else { return }
  model = LocalModel(path: bundledModelPath)
}

let options = CustomImageLabelerOptions(localModel: model)
let imageLabeler = ImageLabeler.imageLabeler(options: options)

Objective-C

NSString *documentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
NSString *localModelPath = [documentsDirectory stringByAppendingPathComponent:@"my_remote_model.tflite"];

MLKLocalModel *model;
if ([NSFileManager.defaultManager fileExistsAtPath:localModelPath]) {
  // Use the downloaded model
  model = [[MLKLocalModel alloc] initWithPath:localModelPath];
} else {
  // Fall back to bundled model
  NSString *bundledModelPath = [NSBundle.mainBundle pathForResource:@"model" ofType:@"tflite"];
  model = [[MLKLocalModel alloc] initWithPath:bundledModelPath];
}

MLKCustomImageLabelerOptions *options = [[MLKCustomImageLabelerOptions alloc] initWithLocalModel:model];
MLKImageLabeler *imageLabeler = [MLKImageLabeler imageLabelerWithOptions:options];

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

Swift

let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let localModelURL = documentDirectory.appendingPathComponent("my_remote_model.tflite")
if FileManager.default.fileExists(atPath: localModelURL.path) {
  // Model is already cached, initialize immediately
  self.initializeLabeler(with: localModelURL)
} else {
  // Model is not yet available, show loading UI and start download
  self.showLoadingUI()
  let storage = Storage.storage()
  let modelRef = storage.reference(forURL: "gs://YOUR_BUCKET/path/to/model.tflite")
  modelRef.write(toFile: localModelURL) { url, error in
    self.hideLoadingUI()
    if let error = error {
      // Handle download error
      self.showErrorUI()
    } else if let modelURL = url {
      // Download success, initialize labeler
      self.initializeLabeler(with: modelURL)
    }
  }
}

func initializeLabeler(with modelURL: URL) {
  let localModel = LocalModel(path: modelURL.path)
  let options = CustomImageLabelerOptions(localModel: localModel)
  self.imageLabeler = ImageLabeler.imageLabeler(options: options)
  // Enable ML-related UI features here
  self.enableMLFeatures()
}

Objective-C

NSString *documentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
NSString *localModelPath = [documentsDirectory stringByAppendingPathComponent:@"my_remote_model.tflite"];
NSURL *localModelURL = [NSURL fileURLWithPath:localModelPath];

if ([NSFileManager.defaultManager fileExistsAtPath:localModelPath]) {
  // Model is already cached, initialize immediately
  [self initializeLabelerWithURL:localModelURL];
} else {
  // Model is not yet available, show loading UI and start download
  [self showLoadingUI];

  FIRStorage *storage = [FIRStorage storage];
  FIRStorageReference *modelRef = [storage referenceForURL:@"gs://YOUR_BUCKET/path/to/model.tflite"];

  [modelRef writeToFile:localModelURL
             completion:^(NSURL * _Nullable URL, NSError * _Nullable error) {
               [self hideLoadingUI];
               if (error != nil) {
                 // Handle download error
                 [self showErrorUI];
               } else {
                 // Download success, initialize labeler
                 [self initializeLabelerWithURL:URL];
               }
             }];
}

- (void)initializeLabelerWithURL:(NSURL *)modelURL {
  MLKLocalModel *localModel = [[MLKLocalModel alloc] initWithPath:modelURL.path];
  MLKCustomImageLabelerOptions *options = [[MLKCustomImageLabelerOptions alloc] initWithLocalModel:localModel];
  self.imageLabeler = [MLKImageLabeler imageLabelerWithOptions:options];

  // Enable ML-related UI features here
  [self enableMLFeatures];
}

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

יוצרים אובייקט 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. הפעלת הכלי להוספת תוויות לתמונות

כדי להוסיף תוויות לאובייקטים בתמונה, מעבירים את image האובייקט לשיטה process() של ImageLabeler.

באופן אסינכרוני:

Swift

imageLabeler.process(image) { labels, error in
    guard error == nil, let labels = labels, !labels.isEmpty else {
        // Handle the error.
        return
    }
    // Show results.
}

Objective-C

[imageLabeler
    processImage:image
      completion:^(NSArray *_Nullable labels,
                   NSError *_Nullable error) {
        if (label.count == 0) {
            // Handle the error.
            return;
        }
        // Show results.
     }];

באופן סינכרוני:

Swift

var labels: [ImageLabel]
do {
    labels = try imageLabeler.results(in: image)
} catch let error {
    // Handle the error.
    return
}
// Show results.

Objective-C

NSError *error;
NSArray *labels =
    [imageLabeler resultsInImage:image error:&error];
// Show results or handle the error.

4. קבלת מידע על ישויות שסומנו בתווית

אם פעולת התיוג של התמונה מצליחה, היא מחזירה מערך של ImageLabel. כל ImageLabel מייצג משהו שסומן בתמונה. אפשר לקבל את תיאור הטקסט של כל תווית (אם הוא זמין במטא-נתונים של קובץ מודל LiteRT), ציון מהימנות ואינדקס. לדוגמה:

Swift

for label in labels {
  let labelText = label.text
  let confidence = label.confidence
  let index = label.index
}

Objective-C

for (MLKImageLabel *label in labels) {
  NSString *labelText = label.text;
  float confidence = label.confidence;
  NSInteger index = label.index;
}

טיפים לשיפור הביצועים בזמן אמת

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

  • כדי לעבד פריימים של סרטונים, משתמשים ב-API הסינכרוני של הגלאי results(in:). קוראים לפונקציה הזו מהפונקציה AVCaptureVideoDataOutputSampleBufferDelegate's captureOutput(_, didOutput:from:) כדי לקבל תוצאות באופן סינכרוני מפריים נתון של סרטון. שומרים על AVCaptureVideoDataOutput alwaysDiscardsLateVideoFrames בתור true כדי להגביל את השיחות לגלאי. אם פריים חדש של סרטון יהיה זמין בזמן שהגלאי פועל, הוא ייפסל.
  • אם משתמשים בפלט של הגלאי כדי להוסיף גרפיקה לתמונת הקלט, צריך קודם לקבל את התוצאה מ-ML Kit, ואז לעבד את התמונה ולהוסיף את הגרפיקה בשלב אחד. כך, הרינדור מתבצע רק פעם אחת לכל פריים קלט שעבר עיבוד. דוגמה מופיעה ב-updatePreviewOverlayViewWithLastFrame במדריך לתחילת העבודה עם ML Kit.