رصد العناصر وتتبعها وتصنيفها باستخدام نموذج تصنيف مخصّص على iOS

يمكنك استخدام "حزمة تعلّم الآلة" لرصد العناصر وتتبُّعها في إطارات الفيديو المتتالية.

عند تمرير صورة إلى "حزمة تعلّم الآلة"، ترصد الحزمة ما يصل إلى خمسة عناصر في الصورة بالإضافة إلى موضع كل عنصر فيها. عند رصد العناصر في بث الفيديو، يكون لكل عنصر معرّف فريد يمكنك استخدامه لتتبُّع العنصر من إطار إلى آخر.

يمكنك استخدام نموذج مخصّص لتصنيف الصور من أجل تصنيف العناصر التي يتم رصدها. يُرجى الرجوع إلى المقالة النماذج المخصّصة باستخدام "حزمة تعلّم الآلة" للحصول على إرشادات حول متطلبات توافق النماذج، ومكان العثور على النماذج المدرَّبة مسبقًا، وكيفية تدريب نماذجك الخاصة.

هناك طريقتان لدمج نموذج مخصّص. يمكنك تجميع النموذج من خلال وضعه داخل مجلد مواد العرض في تطبيقك، أو يمكنك تنزيله ديناميكيًا من Cloud Storage. يقارن الجدول التالي بين الخيارَين.

النموذج المُجمَّع النموذج المُستضاف
النموذج جزء من ملف ‎.ipa الخاص بتطبيقك، ما يزيد من حجمه. النموذج ليس جزءًا من ملف ‎.ipa الخاص بتطبيقك. يتم استضافته من خلال تحميله إلى Cloud Storage. ننصحك باستخدام Cloud Storage لبرنامج Firebase.
النموذج متاح على الفور، حتى عندما يكون جهاز Android غير متصل بالإنترنت يجب أن يتضمّن تطبيقك رمزًا لتنزيل النموذج عند الطلب
لا حاجة إلى مشروع Firebase يتطلب مشروع Firebase (في حال استخدام مساحة تخزين سحابية لـ Firebase).
يجب إعادة نشر تطبيقك لتعديل النموذج يمكنك إرسال تعديلات النموذج بدون إعادة نشر تطبيقك
ما مِن اختبار A/B مضمّن اختبار A/B باستخدام ميزة "الإعداد عن بُعد عبر Firebase"

للتجربة:

قبل البدء

  1. أضِف مكتبات "حزمة تعلّم الآلة" إلى ملف Podfile:

    pod 'GoogleMLKit/ObjectDetectionCustom', '8.0.0'
    
  2. بعد تثبيت حزم Pods في مشروعك أو تعديلها، افتح مشروع Xcode باستخدام ملف ‎.xcworkspace الخاص به. تتوافق "حزمة تعلّم الآلة" مع الإصدار 13.2.1 من Xcode أو الإصدارات الأحدث.

  3. إذا كنت تريد تنزيل نموذج باستخدام مساحة تخزين سحابية لـ Firebase، تأكَّد من إضافة Firebase إلى مشروعك على iOS، إذا لم يسبق لك إجراء ذلك. لا يكون ذلك مطلوبًا عند تجميع النموذج.

1- تحميل النموذج

إعداد مصدر نموذج محلي

لتجميع النموذج مع تطبيقك، اتّبِع الخطوات التالية:

  1. انسخ ملف النموذج (الذي ينتهي عادةً باللاحقة ‎.tflite أو ‎.lite) إلى مشروع Xcode، مع الحرص على اختيار Copy bundle resources عند إجراء ذلك. سيتم تضمين ملف النموذج في حزمة التطبيق وسيكون متاحًا لـ "حزمة تعلّم الآلة".

  2. أنشئ الكائن LocalModel مع تحديد مسار ملف النموذج:

    Swift

    let localModel = LocalModel(path: localModelFilePath)

    Objective-C

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

إعداد مصدر نموذج مُستضاف عن بُعد

لاستخدام النموذج المُستضاف عن بُعد، عليك تنزيل ملف النموذج إلى وحدة التخزين المحلية للجهاز باستخدام منطق تطبيقك الخاص، ثم تحميله كنموذج محلي. ننصحك باستخدام Cloud Storage لبرنامج Firebase لاستضافة نموذج. للحصول على تفاصيل التنفيذ، يُرجى الاطّلاع على دليل نقل البيانات من Firebase ML إلى Cloud Storage.

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;

إذا كان لديك نموذج مُستضاف عن بُعد، عليك التأكّد من تنزيله قبل تشغيله.

على الرغم من أنّه عليك تأكيد ذلك قبل تشغيل أداة رصد العناصر فقط، إذا كان لديك نموذج مُستضاف عن بُعد ونموذج مُجمَّع محليًا، قد يكون من المنطقي إجراء هذا التحقّق عند إنشاء مثيل ObjectDetector: أنشئ أداة رصد من النموذج المُستضاف عن بُعد إذا تم تنزيله، ومن النموذج المحلي بخلاف ذلك.

Swift

// Path where your download logic saves the model
let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let localModelURL = documentDirectory.appendingPathComponent("my_remote_model.tflite")

let model: LocalModel
if FileManager.default.fileExists(atPath: localModelURL.path) {
  // Use the downloaded model
  model = LocalModel(path: localModelURL.path)
} else {
  // Fall back to bundled model
  guard let bundledModelPath = Bundle.main.path(forResource: "model", ofType: "tflite") else { return }
  model = LocalModel(path: bundledModelPath)
}

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

Objective-C

NSString *documentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
NSString *localModelPath = [documentsDirectory stringByAppendingPathComponent:@"my_remote_model.tflite"];

MLKLocalModel *model;
if ([NSFileManager.defaultManager fileExistsAtPath:localModelPath]) {
  // Use the downloaded model
  model = [[MLKLocalModel alloc] initWithPath:localModelPath];
} else {
  // Fall back to bundled model
  NSString *bundledModelPath = [NSBundle.mainBundle pathForResource:@"model" ofType:@"tflite"];
  model = [[MLKLocalModel alloc] initWithPath:bundledModelPath];
}

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

إذا كان لديك نموذج مُستضاف عن بُعد فقط، عليك إيقاف الوظائف المتعلقة بالنموذج، مثلاً تعتيم جزء من واجهة المستخدم أو إخفاؤه، إلى أن تؤكّد تنزيل النموذج.

Swift

let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let localModelURL = documentDirectory.appendingPathComponent("my_remote_model.tflite")
if FileManager.default.fileExists(atPath: localModelURL.path) {
  // Model is already cached, initialize immediately
  self.initializeDetector(with: localModelURL)
} else {
  // Model is not yet available, show loading UI and start download
  self.showLoadingUI()
  let storage = Storage.storage()
  let modelRef = storage.reference(forURL: "gs://YOUR_BUCKET/path/to/model.tflite")
  modelRef.write(toFile: localModelURL) { url, error in
    self.hideLoadingUI()
    if let error = error {
      // Handle download error
      self.showErrorUI()
    } else if let modelURL = url {
      // Download success, initialize detector
      self.initializeDetector(with: modelURL)
    }
  }
}

func initializeDetector(with modelURL: URL) {
  let localModel = LocalModel(path: modelURL.path)
  let options = CustomObjectDetectorOptions(localModel: localModel)
  options.detectorMode = .singleImage
  options.shouldEnableClassification = true
  options.shouldEnableMultipleObjects = true
  self.objectDetector = ObjectDetector.objectDetector(options: options)
  // Enable ML features in UI
  self.enableMLFeatures()
}

Objective-C

NSString *documentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
NSString *localModelPath = [documentsDirectory stringByAppendingPathComponent:@"my_remote_model.tflite"];
NSURL *localModelURL = [NSURL fileURLWithPath:localModelPath];

if ([NSFileManager.defaultManager fileExistsAtPath:localModelPath]) {
  // Model is already cached, initialize immediately
  [self initializeDetectorWithURL:localModelURL];
} else {
  // Model is not yet available, show loading UI and start download
  [self showLoadingUI];

  FIRStorage *storage = [FIRStorage storage];
  FIRStorageReference *modelRef = [storage referenceForURL:@"gs://YOUR_BUCKET/path/to/model.tflite"];

  [modelRef writeToFile:localModelURL
             completion:^(NSURL * _Nullable URL, NSError * _Nullable error) {
               [self hideLoadingUI];
               if (error != nil) {
                 // Handle download error
                 [self showErrorUI];
               } else {
                 // Download success, initialize detector
                 [self initializeDetectorWithURL:URL];
               }
             }];
}

- (void)initializeDetectorWithURL:(NSURL *)modelURL {
  MLKLocalModel *localModel = [[MLKLocalModel alloc] initWithPath:modelURL.path];
  MLKCustomObjectDetectorOptions *options = [[MLKCustomObjectDetectorOptions alloc] initWithLocalModel:localModel];
  options.detectorMode = MLKObjectDetectorModeSingleImage;
  options.shouldEnableClassification = YES;
  options.shouldEnableMultipleObjects = YES;
  self.objectDetector = [MLKObjectDetector objectDetectorWithOptions:options];

  // Enable ML features in UI
  [self enableMLFeatures];
}

تم تحسين واجهة برمجة التطبيقات لرصد العناصر وتتبُّعها لحالتَي الاستخدام الأساسيتَين التاليتَين:

  • الرصد والتتبُّع المباشرَين للعنصر الأبرز في عدسة الكاميرا
  • رصد عناصر متعدّدة من صورة ثابتة

لإعداد واجهة برمجة التطبيقات لحالتَي الاستخدام هاتَين، اتّبِع الخطوات التالية:

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 إلى معالج الإكمال أو تعرض القائمة، وذلك حسب ما إذا كنت قد استدعيت الطريقة غير المتزامنة أو المتزامنة.

يحتوي كل Object على الخصائص التالية:

frame CGRect يشير إلى موضع العنصر في الـ صورة.
trackingID عدد صحيح يحدّد العنصر في جميع الصور، أو `nil` في `SINGLE_IMAGE_MODE`.
labels
label.text الوصف النصي للتصنيف لا يتم عرض هذا الخيار إلا إذا كانت البيانات الوصفية لنموذج LiteRT تحتوي على أوصاف التصنيفات.
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];
  }
}

ضمان تجربة رائعة للمستخدم

للحصول على أفضل تجربة مستخدم، اتّبِع الإرشادات التالية في تطبيقك:

  • يعتمد نجاح رصد العناصر على درجة وضوحها البصرية. لكي يتم رصد العناصر التي تحتوي على عدد قليل من الميزات المرئية، قد تحتاج إلى أن تشغل جزءًا أكبر من الصورة. عليك تزويد المستخدمين بإرشادات حول التقاط الإدخالات التي تعمل بشكل جيد مع نوع العناصر التي تريد رصدها.
  • عند استخدام التصنيف، إذا كنت تريد رصد العناصر التي لا تندرج بشكل واضح ضمن الفئات المتوافقة، نفِّذ معالجة خاصة للعناصر غير المعروفة.

يمكنك أيضًا الاطّلاع على تطبيق Material Design showcase في "حزمة تعلّم الآلة" و مجموعة أنماط Material Design للميزات المستندة إلى تعلّم الآلة.

تحسين الأداء

إذا كنت تريد استخدام ميزة رصد العناصر في تطبيق في الوقت الفعلي، اتّبِع الإرشادات التالية للحصول على أفضل معدلات الإطارات:

  • عند استخدام وضع البث في تطبيق في الوقت الفعلي، لا تستخدِم ميزة رصد عناصر متعدّدة، لأنّ معظم الأجهزة لن تتمكّن من إنتاج معدلات إطارات مناسبة.

  • لمعالجة إطارات الفيديو، استخدِم واجهة برمجة التطبيقات المتزامنة results(in:) لأداة الرصد. استدعِ هذه الطريقة من الـ AVCaptureVideoDataOutputSampleBufferDelegate captureOutput(_, didOutput:from:) للحصول على النتائج بشكل متزامن من إطار الفيديو المحدّد. احتفِظ بقيمة AVCaptureVideoDataOutput في alwaysDiscardsLateVideoFrames على true للحدّ من عمليات استدعاء أداة الرصد. إذا أصبح إطار فيديو جديد متاحًا أثناء تشغيل أداة الرصد، سيتم إسقاطه.
  • إذا كنت تستخدم ناتج أداة الرصد لتراكب الرسومات على الصورة المُدخَلة، احصل أولاً على النتيجة من "حزمة تعلّم الآلة"، ثم اعرض الصورة والتراكب في خطوة واحدة. من خلال إجراء ذلك، يمكنك العرض على سطح العرض مرة واحدة فقط لكل إطار إدخال تتم معالجته. يمكنك الاطّلاع على updatePreviewOverlayViewWithLastFrame في نموذج التشغيل السريع في "حزمة تعلّم الآلة" للحصول على مثال.