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

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

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

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

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

  1. יש לכלול את ספריות ML Kit ב-Podfile:

    כדי לקבץ מודל באפליקציה:

    pod 'GoogleMLKit/ImageLabelingCustom', '3.2.0'
    

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

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

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

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

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

כדי לקבץ את המודל עם האפליקציה שלך:

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

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

    Swift

    let localModel = LocalModel(path: localModelFilePath)

    Objective-C

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

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

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

Swift

let firebaseModelSource = FirebaseModelSource(
    name: "your_remote_model") // The name you assigned in
                               // the Firebase console.
let remoteModel = CustomRemoteModel(remoteModelSource: firebaseModelSource)

Objective-C

MLKFirebaseModelSource *firebaseModelSource =
    [[MLKFirebaseModelSource alloc]
        initWithName:@"your_remote_model"]; // The name you assigned in
                                            // the Firebase console.
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] downloadModel:remoteModel
                                       conditions:downloadConditions];

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

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

אחרי שמגדירים את מקורות המודל, צריך ליצור אובייקט 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];

אם יש לכם מודל שמתארח מרחוק, תצטרכו לבדוק שהוא הורד לפני הפעלתו. ניתן לבדוק את הסטטוס של משימת ההורדה של המודל בשיטה 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)
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.0);
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. הפעלה של מתייג התמונות

כדי לסמן אובייקטים בתמונה, מעבירים את האובייקט image אל השיטה ImageLabeler#39;sprocess().

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

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