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

אחרי שמאמנים מודל משלכם באמצעות AutoML Vision Edge, אפשר להשתמש בו באפליקציה כדי לתייג תמונות.

יש שתי דרכים לשלב מודלים שאומנו באמצעות AutoML Vision Edge. אפשר לארוז את המודל על ידי העתקת הקבצים של המודל לפרויקט Xcode, או להוריד אותו באופן דינמי מ-Firebase.

אפשרויות של חבילות מודלים
כלול באפליקציה
  • המודל הוא חלק מהחבילה
  • המודל זמין באופן מיידי, גם כשמכשיר ה-iOS נמצא במצב אופליין
  • אין צורך בפרויקט Firebase
באירוח של Firebase

רוצה לנסות?

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

1. כוללים את ספריות ML Kit בקובץ Podfile:

כדי לארוז מודל עם האפליקציה:
    pod 'GoogleMLKit/ImageLabelingAutoML'
    
כדי להוריד באופן דינמי מודל מ-Firebase, מוסיפים את התלות LinkFirebase:
    pod 'GoogleMLKit/ImageLabelingAutoML'
    pod 'GoogleMLKit/LinkFirebase'
    
2. אחרי שמתקינים או מעדכנים את ה-Pods של הפרויקט, פותחים את פרויקט Xcode באמצעות .xcworkspacecode>. ‏ ML Kit נתמך ב-Xcode בגרסה 13.2.1 ואילך. 3. אם רוצים להוריד מודל, צריך לוודא שהוספתם את Firebase לפרויקט iOS, אם עדיין לא עשיתם זאת. זה לא נדרש כשמצרפים את המודל לחבילה.

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

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

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

‫1. מחלקים את המודל ואת המטא-נתונים שלו מארכיון ה-ZIP שהורדתם ממסוף Firebase לתיקייה:
    your_model_directory
      |____dict.txt
      |____manifest.json
      |____model.tflite
    
כל שלושת הקבצים צריכים להיות באותה תיקייה. מומלץ להשתמש בקבצים כמו שהורדתם אותם, בלי לבצע שינויים (כולל שמות הקבצים).

‫2. מעתיקים את התיקייה לפרויקט Xcode, ומוודאים שבוחרים באפשרות Create folder references (יצירת הפניות לתיקייה) כשמעתיקים. קובץ המודל והמטא-נתונים ייכללו בחבילת האפליקציה ויהיו זמינים ל-ML Kit.

‫3. יוצרים אובייקט AutoMLImageLabelerLocalModel ומציינים את הנתיב לקובץ המניפסט של המודל:

Swift

guard let manifestPath = Bundle.main.path(
    forResource: "manifest",
    ofType: "json",
    inDirectory: "your_model_directory"
) else { return }
let localModel = AutoMLImageLabelerLocalModel(manifestPath: manifestPath)

Objective-C

NSString *manifestPath =
    [NSBundle.mainBundle pathForResource:@"manifest"
                                  ofType:@"json"
                             inDirectory:@"your_model_directory"];
MLKAutoMLImageLabelerLocalModel *localModel =
    [[MLKAutoMLImageLabelerLocalModel alloc] initWithManifestPath:manifestPath];

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

כדי להשתמש במודל שמתארח מרחוק, יוצרים אובייקט AutoMLImageLabelerRemoteModel ומציינים את השם שהקציתם למודל כשפרסמתם אותו:

Swift

let remoteModel = AutoMLImageLabelerRemoteModel(
    name: "your_remote_model"  // The name you assigned in
                               // the Firebase console.
)

Objective-C

MLKAutoMLImageLabelerRemoteModel *remoteModel =
    [[MLKAutoMLImageLabelerRemoteModel alloc]
        initWithName:@"your_remote_model"];  // The name you assigned in
                                             // the Firebase console.

לאחר מכן, מתחילים את משימת ההורדה של המודל ומציינים את התנאים שבהם רוצים לאפשר הורדה. אם המודל לא נמצא במכשיר, או אם יש גרסה חדשה יותר של המודל, המשימה תוריד את המודל מ-Firebase באופן אסינכרוני:

Swift

let downloadConditions = ModelDownloadConditions(
  allowsCellularAccess: true,
  allowsBackgroundDownloading: true
)

let downloadProgress = ModelManager.modelManager().download(
  remoteModel,
  conditions: downloadConditions
)

Objective-C

MLKModelDownloadConditions *downloadConditions =
    [[MLKModelDownloadConditions alloc] initWithAllowsCellularAccess:YES
                                         allowsBackgroundDownloading:YES];

NSProgress *downloadProgress =
    [[MLKModelManager modelManager] downloadModel:remoteModel
                                       conditions:downloadConditions];

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

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

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

אם יש לכם רק מודל שמאוגד באופן מקומי, פשוט יוצרים תוויתן מהאובייקט AutoMLImageLabelerLocalModel ומגדירים את סף ציון המהימנות שרוצים לדרוש (ראו הערכת המודל):

Swift

let options = AutoMLImageLabelerOptions(localModel: localModel)
options.confidenceThreshold = NSNumber(value: 0.0)  // Evaluate your model in the Firebase console
                                                    // to determine an appropriate value.
let imageLabeler = ImageLabeler.imageLabeler(options: options)

Objective-C

MLKAutoMLImageLabelerOptions *options =
    [[MLKAutoMLImageLabelerOptions alloc] initWithLocalModel:localModel];
options.confidenceThreshold = @(0.0);  // Evaluate your model in the Firebase console
                                       // to determine an appropriate value.
MLKImageLabeler *imageLabeler =
    [MLKImageLabeler imageLabelerWithOptions:options];

אם יש לכם מודל שמתארח מרחוק, תצטרכו לוודא שהוא הורד לפני שתפעילו אותו. אפשר לבדוק את הסטטוס של משימת ההורדה של המודל באמצעות השיטה isModelDownloaded(remoteModel:) של הכלי לניהול מודלים.

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

Swift

var options: AutoMLImageLabelerOptions!
if (ModelManager.modelManager().isModelDownloaded(remoteModel)) {
  options = AutoMLImageLabelerOptions(remoteModel: remoteModel)
} else {
  options = AutoMLImageLabelerOptions(localModel: localModel)
}
options.confidenceThreshold = NSNumber(value: 0.0)  // Evaluate your model in the Firebase console
                                                    // to determine an appropriate value.
let imageLabeler = ImageLabeler.imageLabeler(options: options)

Objective-C

MLKAutoMLImageLabelerOptions *options;
if ([[MLKModelManager modelManager] isModelDownloaded:remoteModel]) {
  options = [[MLKAutoMLImageLabelerOptions alloc] initWithRemoteModel:remoteModel];
} else {
  options = [[MLKAutoMLImageLabelerOptions alloc] initWithLocalModel:localModel];
}
options.confidenceThreshold = @(0.0);  // Evaluate your model in the Firebase console
                                       // to determine an appropriate value.
MLKImageLabeler *imageLabeler =
    [MLKImageLabeler imageLabelerWithOptions:options];

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

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

Swift

NotificationCenter.default.addObserver(
    forName: .mlkitModelDownloadDidSucceed,
    object: nil,
    queue: nil
) { [weak self] notification in
    guard let strongSelf = self,
        let userInfo = notification.userInfo,
        let model = userInfo[ModelDownloadUserInfoKey.remoteModel.rawValue]
            as? RemoteModel,
        model.name == "your_remote_model"
        else { return }
    // The model was downloaded and is available on the device
}

NotificationCenter.default.addObserver(
    forName: .mlkitModelDownloadDidFail,
    object: nil,
    queue: nil
) { [weak self] notification in
    guard let strongSelf = self,
        let userInfo = notification.userInfo,
        let model = userInfo[ModelDownloadUserInfoKey.remoteModel.rawValue]
            as? RemoteModel
        else { return }
    let error = userInfo[ModelDownloadUserInfoKey.error.rawValue]
    // ...
}

Objective-C

__weak typeof(self) weakSelf = self;

[NSNotificationCenter.defaultCenter
    addObserverForName:MLKModelDownloadDidSucceedNotification
                object:nil
                 queue:nil
            usingBlock:^(NSNotification *_Nonnull note) {
              if (weakSelf == nil | note.userInfo == nil) {
                return;
              }
              __strong typeof(self) strongSelf = weakSelf;

              MLKRemoteModel *model = note.userInfo[MLKModelDownloadUserInfoKeyRemoteModel];
              if ([model.name isEqualToString:@"your_remote_model"]) {
                // The model was downloaded and is available on the device
              }
            }];

[NSNotificationCenter.defaultCenter
    addObserverForName:MLKModelDownloadDidFailNotification
                object:nil
                 queue:nil
            usingBlock:^(NSNotification *_Nonnull note) {
              if (weakSelf == nil | note.userInfo == nil) {
                return;
              }
              __strong typeof(self) strongSelf = weakSelf;

              NSError *error = note.userInfo[MLKModelDownloadUserInfoKeyError];
            }];

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. הפעלת הכלי להוספת תוויות לתמונות

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

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 (labels.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 מייצג משהו שסומן בתמונה. אפשר לקבל את תיאור הטקסט של כל תווית (אם הוא זמין במטא-נתונים של קובץ המודל של TensorFlow Lite), את ציון הביטחון ואת האינדקס. לדוגמה:

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:). קוראים לשיטה הזו מהפונקציה captureOutput(_, didOutput:from:) של AVCaptureVideoDataOutputSampleBufferDelegate כדי לקבל תוצאות באופן סינכרוני מפריים נתון של סרטון. שומרים על AVCaptureVideoDataOutput בתור true כדי להגביל את השיחות לגלאי.alwaysDiscardsLateVideoFrames אם פריים חדש של סרטון יהיה זמין בזמן שהגלאי פועל, הוא ייפסל.
  • אם משתמשים בפלט של הגלאי כדי להוסיף גרפיקה לתמונת הקלט, קודם צריך לקבל את התוצאה מ-ML Kit, ואז לעבד את התמונה ולהוסיף את הגרפיקה בשלב אחד. כך, הרינדור מתבצע רק פעם אחת לכל פריים קלט שעבר עיבוד. דוגמה מופיעה ב-updatePreviewOverlayViewWithLastFrame במדריך למתחילים של ML Kit.