זיהוי אובייקטים ומעקב אחריהם באמצעות ML Kit ב-iOS

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Swift

// Live detection and tracking
let options = ObjectDetectorOptions()
options.shouldEnableClassification = true

// Multiple object detection in static images
let options = ObjectDetectorOptions()
options.detectorMode = .singleImage
options.shouldEnableMultipleObjects = true
options.shouldEnableClassification = true

Objective-C

// Live detection and tracking
MLKObjectDetectorOptions *options = [[MLKObjectDetectorOptions alloc] init];
options.shouldEnableClassification = YES;

// Multiple object detection in static images
MLKObjectDetectorOptions *options = [[MLKOptions alloc] init];
options.detectorMode = MLKObjectDetectorModeSingleImage;
options.shouldEnableMultipleObjects = YES;
options.shouldEnableClassification = YES;
  1. מקבלים מופע של ObjectDetector:

Swift

let objectDetector = ObjectDetector.objectDetector()

// Or, to change the default settings:
let objectDetector = ObjectDetector.objectDetector(options: options)

Objective-C

MLKObjectDetector *objectDetector = [MLKObjectDetector objectDetector];

// Or, to change the default settings:
MLKObjectDetector *objectDetector = [MLKObjectDetector objectDetectorWithOptions:options];

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

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

יוצרים אובייקט 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. עיבוד התמונה

מעבירים את VisionImage לאחת משיטות עיבוד התמונה של גלאי האובייקטים. אפשר להשתמש בשיטה process(image:) האסינכרונית או בשיטה results() הסינכרונית.

כדי לזהות אובייקטים באופן אסינכרוני:

Swift

objectDetector.process(image) { objects, error in
  guard error == nil else {
    // Error.
    return
  }
  guard !objects.isEmpty else {
    // No objects detected.
    return
  }

  // Success. Get object info here.
  // ...
}

Objective-C

[objectDetector processImage:image
                  completion:^(NSArray * _Nullable objects,
                               NSError * _Nullable error) {
                    if (error == nil) {
                      return;
                    }
                    if (objects.count == 0) {
                      // No objects detected.
                      return;
                    }

                    // Success. Get object info here.
                  }];

כדי לזהות אובייקטים באופן סינכרוני:

Swift

var objects: [Object]
do {
  objects = try objectDetector.results(in: image)
} catch let error {
  print("Failed to detect object with error: \(error.localizedDescription).")
  return
}
guard !objects.isEmpty else {
  print("Object detector returned no results.")
  return
}

// Success. Get object info here.

Objective-C

NSError *error;
NSArray *objects = [objectDetector resultsInImage:image error:&error];
if (error == nil) {
  return;
}
if (objects.count == 0) {
  // No objects detected.
  return;
}

// Success. Get object info here.

4. קבלת מידע על אובייקטים שזוהו

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

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

frame CGRect שמציין את מיקום האובייקט בתמונה.
trackingID מספר שלם שמזהה את האובייקט בתמונות שונות, או 'nil' במצב תמונה יחידה.
labels מערך של תוויות שמתארות את האובייקט שהמזהה מחזיר. המאפיין יהיה ריק אם אפשרות המזהה shouldEnableClassification מוגדרת לערך false.

Swift

// objects contains one item if multiple object detection wasn't enabled.
for object in objects {
  let frame = object.frame
  let trackingID = object.trackingID

  // If classification was enabled:
  let description = object.labels.enumerated().map { (index, label) in
    "Label \(index): \(label.text), \(label.confidence)"
    }.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];
    ...
  }
}

שיפור השימושיות והביצועים

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

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

כדאי גם לעיין באוסף 'תבניות לתכונות שמבוססות על למידת מכונה' של Material Design.

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

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