تصنيف الصور باستخدام نموذج مخصص على نظام التشغيل iOS

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

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

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

للتجربة:

قبل البدء

  1. أدرِج مكتبات حزمة تعلّم الآلة في ملف Podfile:

    pod 'GoogleMLKit/ImageLabelingCustom', '8.0.0'
    
  2. بعد تثبيت أو تعديل Pods في مشروعك، افتح مشروع Xcode باستخدام .xcworkspace. تتوافق حزمة ML Kit مع الإصدار 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];

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

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

ضبط أداة تصنيف الصور

بعد ضبط مصادر النموذج، أنشئ عنصر ImageLabeler من أحدها.

تتوفّر الخيارات التالية:

الخيارات
confidenceThreshold

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

maxResultCount

الحد الأقصى لعدد التصنيفات المطلوب عرضها. في حال عدم ضبطها، سيتم استخدام القيمة التلقائية 10.

إذا كان لديك نموذج مجمّع محليًا فقط، ما عليك سوى إنشاء أداة تصنيف من عنصر LocalModel:

Swift

let options = CustomImageLabelerOptions(localModel: localModel)
options.confidenceThreshold = NSNumber(value: 0.0)
let imageLabeler = ImageLabeler.imageLabeler(options: options)

Objective-C

MLKCustomImageLabelerOptions *options =
    [[MLKCustomImageLabelerOptions alloc] initWithLocalModel:localModel];
options.confidenceThreshold = @(0.0);
MLKImageLabeler *imageLabeler =
    [MLKImageLabeler imageLabelerWithOptions:options];

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

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

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 = CustomImageLabelerOptions(localModel: model)
let imageLabeler = ImageLabeler.imageLabeler(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];
}

MLKCustomImageLabelerOptions *options = [[MLKCustomImageLabelerOptions alloc] initWithLocalModel:model];
MLKImageLabeler *imageLabeler = [MLKImageLabeler imageLabelerWithOptions: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.initializeLabeler(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 labeler
      self.initializeLabeler(with: modelURL)
    }
  }
}

func initializeLabeler(with modelURL: URL) {
  let localModel = LocalModel(path: modelURL.path)
  let options = CustomImageLabelerOptions(localModel: localModel)
  self.imageLabeler = ImageLabeler.imageLabeler(options: options)
  // Enable ML-related UI features here
  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 initializeLabelerWithURL: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 labeler
                 [self initializeLabelerWithURL:URL];
               }
             }];
}

- (void)initializeLabelerWithURL:(NSURL *)modelURL {
  MLKLocalModel *localModel = [[MLKLocalModel alloc] initWithPath:modelURL.path];
  MLKCustomImageLabelerOptions *options = [[MLKCustomImageLabelerOptions alloc] initWithLocalModel:localModel];
  self.imageLabeler = [MLKImageLabeler imageLabelerWithOptions:options];

  // Enable ML-related UI features here
  [self enableMLFeatures];
}

2. تجهيز الصورة المصدر

أنشئ عنصر 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- تشغيل أداة تصنيف الصور

لتصنيف العناصر في صورة، مرِّر العنصر image إلى طريقة ImageLabelerprocess().

بشكل غير متزامن:

Swift

imageLabeler.process(image) { labels, error in
    guard error == nil, let labels = labels, !labels.isEmpty else {
        // Handle the error.
        return
    }
    // Show results.
}

Objective-C

[imageLabeler
    processImage:image
      completion:^(NSArray *_Nullable labels,
                   NSError *_Nullable error) {
        if (label.count == 0) {
            // Handle the error.
            return;
        }
        // Show results.
     }];

بشكل متزامن:

Swift

var labels: [ImageLabel]
do {
    labels = try imageLabeler.results(in: image)
} catch let error {
    // Handle the error.
    return
}
// Show results.

Objective-C

NSError *error;
NSArray *labels =
    [imageLabeler resultsInImage:image error:&error];
// Show results or handle the error.

4. الحصول على معلومات عن الكيانات المصنَّفة

في حال نجاح عملية تصنيف الصور، سيتم عرض مصفوفة من ImageLabel. يمثّل كل ImageLabel عنصرًا تم تصنيفه في الصورة. يمكنك الحصول على الوصف النصي لكل تصنيف (إذا كان متاحًا في البيانات الوصفية لملف نموذج LiteRT) ونسبة الثقة والفهرس. على سبيل المثال:

Swift

for label in labels {
  let labelText = label.text
  let confidence = label.confidence
  let index = label.index
}

Objective-C

for (MLKImageLabel *label in labels) {
  NSString *labelText = label.text;
  float confidence = label.confidence;
  NSInteger index = label.index;
}

نصائح لتحسين الأداء في الوقت الفعلي

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

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