זיהוי, מעקב וסיווג של אובייקטים באמצעות מודל סיווג בהתאמה אישית ב-iOS

אתם יכולים להשתמש ב-ML Kit כדי לזהות אובייקטים בפריימים עוקבים של וידאו ולעקוב אחריהם.

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

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

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

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

אני רוצה לנסות

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

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

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

    pod 'GoogleMLKit/ObjectDetectionCustom', '3.2.0'
    

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

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

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

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

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

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

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

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

    Swift

    let localModel = LocalModel(path: localModelFilePath)

    Objective-C

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

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

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

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];

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

2. הגדרה של מזהה אובייקטים

אחרי שמגדירים את מקורות המודל, צריך להגדיר את גלאי האובייקטים לתרחיש לדוגמה באמצעות אובייקט CustomObjectDetectorOptions. תוכלו לשנות את ההגדרות הבאות:

הגדרות של מזהה אובייקטים
מצב זיהוי STREAM_MODE (ברירת מחדל) | SINGLE_IMAGE_MODE

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

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

זיהוי אובייקטים מרובים ומעקב אחריהם false (ברירת מחדל) | true

האם לזהות ולעקוב אחרי עד חמישה אובייקטים או רק את האובייקט הבולט ביותר (ברירת מחדל).

סיווג אובייקטים false (ברירת מחדל) | true

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

סף ביטחון בסיווג

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

מספר מקסימלי של תוויות לאובייקט

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

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

Swift

let options = CustomObjectDetectorOptions(localModel: localModel)
options.detectorMode = .singleImage
options.shouldEnableClassification = true
options.shouldEnableMultipleObjects = true
options.classificationConfidenceThreshold = NSNumber(value: 0.5)
options.maxPerObjectLabelCount = 3

Objective-C

MLKCustomObjectDetectorOptions *options =
    [[MLKCustomObjectDetectorOptions alloc] initWithLocalModel:localModel];
options.detectorMode = MLKObjectDetectorModeSingleImage;
options.shouldEnableClassification = YES;
options.shouldEnableMultipleObjects = YES;
options.classificationConfidenceThreshold = @(0.5);
options.maxPerObjectLabelCount = 3;

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

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

Swift

var options: CustomObjectDetectorOptions!
if (ModelManager.modelManager().isModelDownloaded(remoteModel)) {
  options = CustomObjectDetectorOptions(remoteModel: remoteModel)
} else {
  options = CustomObjectDetectorOptions(localModel: localModel)
}
options.detectorMode = .singleImage
options.shouldEnableClassification = true
options.shouldEnableMultipleObjects = true
options.classificationConfidenceThreshold = NSNumber(value: 0.5)
options.maxPerObjectLabelCount = 3

Objective-C

MLKCustomObjectDetectorOptions *options;
if ([[MLKModelManager modelManager] isModelDownloaded:remoteModel]) {
  options = [[MLKCustomObjectDetectorOptions alloc] initWithRemoteModel:remoteModel];
} else {
  options = [[MLKCustomObjectDetectorOptions alloc] initWithLocalModel:localModel];
}
options.detectorMode = MLKObjectDetectorModeSingleImage;
options.shouldEnableClassification = YES;
options.shouldEnableMultipleObjects = YES;
options.classificationConfidenceThreshold = @(0.5);
options.maxPerObjectLabelCount = 3;

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

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

ה-API לזיהוי אובייקטים ולמעקב מותאם לשני תרחישי השימוש העיקריים הבאים:

  • זיהוי ומעקב בזמן אמת של האובייקט הבולט ביותר בעינית המצלמה.
  • זיהוי של מספר אובייקטים מתמונה סטטית.

כדי להגדיר את ה-API לתרחישים לדוגמה הבאים:

Swift

// Live detection and tracking
let options = CustomObjectDetectorOptions(localModel: localModel)
options.shouldEnableClassification = true
options.maxPerObjectLabelCount = 3

// Multiple object detection in static images
let options = CustomObjectDetectorOptions(localModel: localModel)
options.detectorMode = .singleImage
options.shouldEnableMultipleObjects = true
options.shouldEnableClassification = true
options.maxPerObjectLabelCount = 3

Objective-C

// Live detection and tracking
MLKCustomObjectDetectorOptions *options =
    [[MLKCustomObjectDetectorOptions alloc] initWithLocalModel:localModel];
options.shouldEnableClassification = YES;
options.maxPerObjectLabelCount = 3;

// Multiple object detection in static images
MLKCustomObjectDetectorOptions *options =
    [[MLKCustomObjectDetectorOptions alloc] initWithLocalModel:localModel];
options.detectorMode = MLKObjectDetectorModeSingleImage;
options.shouldEnableMultipleObjects = YES;
options.shouldEnableClassification = YES;
options.maxPerObjectLabelCount = 3;

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

יוצרים אובייקט 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];

4. יצירה והפעלה של גלאי אובייקטים

  1. יוצרים גלאי אובייקטים חדש:

    Swift

    let objectDetector = ObjectDetector.objectDetector(options: options)

    Objective-C

    MLKObjectDetector *objectDetector = [MLKObjectDetector objectDetectorWithOptions:options];
  2. לאחר מכן, משתמשים במזהים:

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

    Swift

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

    Objective-C

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

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

    Swift

    var objects: [Object]
    do {
        objects = try objectDetector.results(in: image)
    } catch let error {
        // Handle the error.
        return
    }
    // Show results.

    Objective-C

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

5. קבלת מידע על אובייקטים מתויגים

אם הקריאה למעבד התמונות תסתיים בהצלחה, היא תעביר רשימה של Object ל-handler להשלמה או תחזיר את הרשימה, בהתאם לשיטה האסינכרונית או הסינכרונית שבה השתמשתם.

כל Object מכיל את המאפיינים הבאים:

frame CGRect שמציין את מיקום האובייקט בתמונה.
trackingID מספר שלם שמזהה את האובייקט בתמונות שונות, או 'nil' ב-SINGLE_IMAGE_Mode.
labels
label.text תיאור הטקסט של התווית. מוחזר רק אם המטא-נתונים של דגם TensorFlow Lite מכילים תיאורים של תוויות.
label.index האינדקס של התווית מבין כל התוויות שנתמכות על ידי המסווג.
label.confidence ערך המהימנות של סיווג האובייקטים.

Swift

// objects contains one item if multiple object detection wasn't enabled.
for object in objects {
  let frame = object.frame
  let trackingID = object.trackingID
  let description = object.labels.enumerated().map { (index, label) in
    "Label \(index): \(label.text), \(label.confidence), \(label.index)"
  }.joined(separator: "\n")
}

Objective-C

// The list of detected objects contains one item if multiple object detection
// wasn't enabled.
for (MLKObject *object in objects) {
  CGRect frame = object.frame;
  NSNumber *trackingID = object.trackingID;
  for (MLKObjectLabel *label in object.labels) {
    NSString *labelString =
        [NSString stringWithFormat:@"%@, %f, %lu",
                                   label.text,
                                   label.confidence,
                                   (unsigned long)label.index];
  }
}

הבטחת חוויית משתמש מעולה

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

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

כדאי גם לעיין [באפליקציית ML Kit Material Design][showcase-link]{: .external } ובאוסף תבניות לתכונות שמבוססות על למידת מכונה.

Improving performance

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

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

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