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

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

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

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

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

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

רוצה לנסות?

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

  1. בספריות Podfile ייכללו הספריות של ערכת ML:

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

    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 בגרסה 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

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

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

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

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

frame CGRect שמציין את מיקום האובייקט בתמונה.
trackingID מספר שלם שמזהה את האובייקט בתמונות, או 'nil' ב-SINGLE_IMAGE_MODE.
labels
label.text תיאור הטקסט של התווית. האפשרות הזו מוחזרת רק אם המטא-נתונים של מודל TensorFlow מכילים תיאורי תוויות.
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

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

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

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