تصنيف الصور الذاتية باستخدام حزمة تعلّم الآلة على نظام التشغيل iOS

توفّر حزمة تعلّم الآلة حزمة تطوير برامج (SDK) محسّنة لتصنيف الصور الذاتية. تكون مواد عرض "أداة تقسيم الصور الذاتية" مرتبطة بشكل ثابت بتطبيقك في وقت الإصدار. سيؤدي ذلك إلى زيادة حجم التطبيق بمقدار 24 ميغابايت، ويمكن أن يختلف وقت استجابة واجهة برمجة التطبيقات من 7 ملي ثانية إلى ما يقرب من 12 ملي ثانية حسب حجم الصورة التي يتم إدخالها، كما يتم قياسه على هاتف iPhone X.

جرّبه الآن

  • يمكنك تجربة نموذج التطبيق من أجل يمكنك الاطّلاع على مثال حول استخدام واجهة برمجة التطبيقات هذه.

قبل البدء

  1. تضمين مكتبات ML Kit التالية في Podfile:

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

1. إنشاء مثيل لأداة تقسيم

لإجراء تصنيف على صورة ذاتية، عليك أولاً إنشاء مثيل Segmenter باستخدام SelfieSegmenterOptions، ثم تحديد إعدادات التصنيف إلى قطاعات أو شرائح.

خيارات أداة التقسيم

وضع أداة تقسيم البيانات

تعمل Segmenter بوضعَين. احرص على اختيار الحالة التي تتطابق مع حالة استخدامك.

STREAM_MODE (default)

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

SINGLE_IMAGE_MODE (default)

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

تفعيل قناع حجم البيانات الأولية

تطلب من المقسِّم عرض قناع الحجم الأولي الذي يتطابق مع حجم إخراج النموذج.

عادةً ما يكون حجم القناع الأولي (على سبيل المثال 256×256) أصغر من حجم الصورة التي تم إدخالها.

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

حدد خيارات أداة التقسيم:

Swift

let options = SelfieSegmenterOptions()
options.segmenterMode = .singleImage
options.shouldEnableRawSizeMask = true

Objective-C

MLKSelfieSegmenterOptions *options = [[MLKSelfieSegmenterOptions alloc] init];
options.segmenterMode = MLKSegmenterModeSingleImage;
options.shouldEnableRawSizeMask = YES;

وأخيرًا، احصل على مثيل لـ Segmenter. مرر الخيارات التي حددتها:

Swift

let segmenter = Segmenter.segmenter(options: options)

Objective-C

MLKSegmenter *segmenter = [MLKSegmenter segmenterWithOptions: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 إلى إحدى طرق معالجة الصور في Segmenter. يمكنك استخدام طريقة process(image:) غير المتزامنة أو طريقة results(in:) المتزامنة.

لإجراء تقسيم على صورة سيلفي بشكل متزامن:

Swift

var mask: [SegmentationMask]
do {
  mask = try segmenter.results(in: image)
} catch let error {
  print("Failed to perform segmentation with error: \(error.localizedDescription).")
  return
}

// Success. Get a segmentation mask here.

Objective-C

NSError *error;
MLKSegmentationMask *mask =
    [segmenter resultsInImage:image error:&error];
if (error != nil) {
  // Error.
  return;
}

// Success. Get a segmentation mask here.

لإجراء تقسيم على صورة ذاتية بشكل غير متزامن:

Swift

segmenter.process(image) { mask, error in
  guard error == nil else {
    // Error.
    return
  }
  // Success. Get a segmentation mask here.

Objective-C

[segmenter processImage:image
             completion:^(MLKSegmentationMask * _Nullable mask,
                          NSError * _Nullable error) {
               if (error != nil) {
                 // Error.
                 return;
               }
               // Success. Get a segmentation mask here.
             }];

4. الحصول على قناع التصنيف إلى شرائح

يمكنك الحصول على نتيجة التقسيم على النحو التالي:

Swift

let maskWidth = CVPixelBufferGetWidth(mask.buffer)
let maskHeight = CVPixelBufferGetHeight(mask.buffer)

CVPixelBufferLockBaseAddress(mask.buffer, CVPixelBufferLockFlags.readOnly)
let maskBytesPerRow = CVPixelBufferGetBytesPerRow(mask.buffer)
var maskAddress =
    CVPixelBufferGetBaseAddress(mask.buffer)!.bindMemory(
        to: Float32.self, capacity: maskBytesPerRow * maskHeight)

for _ in 0...(maskHeight - 1) {
  for col in 0...(maskWidth - 1) {
    // Gets the confidence of the pixel in the mask being in the foreground.
    let foregroundConfidence: Float32 = maskAddress[col]
  }
  maskAddress += maskBytesPerRow / MemoryLayout<Float32>.size
}

Objective-C

size_t width = CVPixelBufferGetWidth(mask.buffer);
size_t height = CVPixelBufferGetHeight(mask.buffer);

CVPixelBufferLockBaseAddress(mask.buffer, kCVPixelBufferLock_ReadOnly);
size_t maskBytesPerRow = CVPixelBufferGetBytesPerRow(mask.buffer);
float *maskAddress = (float *)CVPixelBufferGetBaseAddress(mask.buffer);

for (int row = 0; row < height; ++row) {
  for (int col = 0; col < width; ++col) {
    // Gets the confidence of the pixel in the mask being in the foreground.
    float foregroundConfidence = maskAddress[col];
  }
  maskAddress += maskBytesPerRow / sizeof(float);
}

للاطلاع على مثال كامل عن كيفية استخدام نتائج التقسيم، يُرجى مراجعة نموذج للبدء السريع في حزمة تعلّم الآلة

نصائح لتحسين الأداء

تعتمد جودة نتائجك على جودة الصورة التي تم إدخالها:

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

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

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