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

אפשר להשתמש בלמידת מכונה (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. אחרי ההתקנה או העדכון של ה-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

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

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

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

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

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

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

Improving performance

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

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

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