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

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

للتجربة:

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

قبل البدء

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

    pod 'GoogleMLKit/SegmentationSelfie', '8.0.0'
    
  2. بعد تثبيت Pods أو تعديلها في مشروعك، افتح مشروع 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);
}

للاطّلاع على مثال كامل حول كيفية استخدام نتائج التصنيف، يُرجى الاطّلاع على نموذج التشغيل السريع في حزمة تعلّم الآلة.

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

تعتمد جودة النتائج على جودة الصورة المُدخَلة:

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

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

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