אפשר לתייג תמונות באמצעות מודל שעבר אימון AutoML בפלטפורמות של Apple

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

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

אפשרויות של חבילות מודלים
חבילה באפליקציה
  • המודל הוא חלק מהחבילה
  • המודל זמין באופן מיידי, גם כשמכשיר Apple במצב אופליין
  • אין צורך בפרויקט Firebase
אירוח ב-Firebase
  • מעלים את המודל ל-למידת מכונה ב-Firebase כדי לארח אותו.
  • הקטנת גודל חבילת האפליקציות
  • המודל מוריד על פי דרישה
  • איך שולחים עדכונים של מודלים בלי לפרסם מחדש את האפליקציה
  • בדיקות A/B פשוטות באמצעות הגדרת תצורה מרחוק ב-Firebase
  • נדרש פרויקט Firebase

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

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

    כדי לצרף מודל לאפליקציה:

    pod 'GoogleMLKit/ImageLabelingCustom'
    

    כדי להוריד מודל באופן דינמי מ-Firebase, מוסיפים את התלות LinkFirebase:

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

  3. אם רוצים להוריד מודל, צריך לוודא שמוסיפים את Firebase לפרויקט Android, אם עדיין לא עשיתם זאת. אין צורך לעשות זאת כשמקבצים את המודל.

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

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

כדי לצרף את המודל לאפליקציה:

  1. מחלצים את המודל ואת המטא-נתונים שלו מקובץ ה-zip שהורדתם ממסוף Firebase לתיקייה:

    your_model_directory
      |____dict.txt
      |____manifest.json
      |____model.tflite
    

    כל שלושת הקבצים חייבים להיות באותה תיקייה. מומלץ להשתמש בקבצים כפי שהורדת אותם, ללא שינוי (כולל שמות הקבצים).

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

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

    Swift

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

    Objective-C

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

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

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

Swift

// Initialize the model source with the name you assigned in
// the Firebase console.
let remoteModelSource = FirebaseModelSource(name: "your_remote_model")
let remoteModel = CustomRemoteModel(remoteModelSource: remoteModelSource)

Objective-C

// Initialize the model source with the name you assigned in
// the Firebase console.
MLKFirebaseModelSource *firebaseModelSource =
    [[MLKFirebaseModelSource alloc] initWithName:@"your_remote_model"];
MLKCustomRemoteModel *remoteModel =
    [[MLKCustomRemoteModel alloc] initWithRemoteModelSource:firebaseModelSource];

לאחר מכן, מפעילים את המשימה של הורדת המודל ומציינים את התנאים שבהם רוצים לאפשר הורדה. אם המודל לא נמצא במכשיר, או אם יש גרסה חדשה יותר של המודל, המשימה תוריד את המודל מ-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] downloadRemoteModel:remoteModel
                                             conditions:downloadConditions];

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

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

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

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

Swift

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

Objective-C

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

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

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

Swift

var options: CustomImageLabelerOptions
if (ModelManager.modelManager().isModelDownloaded(remoteModel)) {
  options = CustomImageLabelerOptions(remoteModel: remoteModel)
} else {
  options = CustomImageLabelerOptions(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

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

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

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

Swift

NotificationCenter.default.addObserver(
    forName: .mlkitMLModelDownloadDidSucceed,
    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: .mlkitMLModelDownloadDidFail,
    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 או CMSampleBufferRef.

אם אתם משתמשים ב-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;

אם אתם משתמשים ב-CMSampleBufferRef, עליכם לפעול לפי השלבים הבאים:

  • ציון הכיוון של נתוני התמונה שמכיל מאגר CMSampleBufferRef.

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

    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 position == AVCaptureDevicePositionFront ? UIImageOrientationLeftMirrored
                                                          : UIImageOrientationRight;
    
        case UIDeviceOrientationLandscapeLeft:
          return position == AVCaptureDevicePositionFront ? UIImageOrientationDownMirrored
                                                          : UIImageOrientationUp;
        case UIDeviceOrientationPortraitUpsideDown:
          return position == AVCaptureDevicePositionFront ? UIImageOrientationRightMirrored
                                                          : UIImageOrientationLeft;
        case UIDeviceOrientationLandscapeRight:
          return position == AVCaptureDevicePositionFront ? UIImageOrientationUpMirrored
                                                          : UIImageOrientationDown;
        case UIDeviceOrientationUnknown:
        case UIDeviceOrientationFaceUp:
        case UIDeviceOrientationFaceDown:
          return UIImageOrientationUp;
      }
    }
          
  • יוצרים אובייקט VisionImage באמצעות האובייקט CMSampleBufferRef והכיוון:

    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<MLKImageLabel *> *_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<MLKImageLabel *> *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;
}

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

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

  • צמצום מספר הקריאות למזהה. אם מסגרת וידאו חדשה זמינה בזמן שהגלאי פועל, צריך להסיר את המסגרת.
  • אם אתם משתמשים בפלט של הגלאי כדי להוסיף שכבת-על של גרפיקה לתמונה הקלט, תחילה צריך לקבל את התוצאה, ואז לבצע עיבוד תמונה ולהוסיף את שכבת-העל בשלב אחד. כך תוכלו לבצע עיבוד (render) למשטח התצוגה רק פעם אחת לכל מסגרת קלט. דוגמה לכך מופיעה במחלקות previewOverlayView ו-FIRDetectionOverlayView באפליקציית הדוגמה.