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

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

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

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

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

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

frame CGRect שמציין את המיקום של האובייקט תמונה.
trackingID מספר שלם שמזהה את האובייקט בתמונות, או 'nil' ב- SINGLE_IMAGE_מצב.
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][showcase-link]{: .external } וגם עיצוב חומר אוסף תבניות לתכונות מבוססות-למידת מכונה.

Improving performance

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

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

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