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

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

جرّبه الآن

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

قبل البدء

  1. أدرِج مكتبات ML Kit التالية في ملف Podfile:

    pod 'GoogleMLKit/SegmentationSelfie', '8.0.0'
    
  2. بعد تثبيت أو تعديل Pods في مشروعك، افتح مشروع Xcode باستخدام ملف xcworkspace. يتوافق ML Kit مع الإصدار 13.2.1 من Xcode أو الإصدارات الأحدث.

1. إنشاء مثيل من Segmenter

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

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

وضع التقسيم

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

STREAM_MODE (default)

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

SINGLE_IMAGE_MODE (default)

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

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

يطلب من أداة تقسيم الصور عرض قناع الحجم الأولي الذي يتطابق مع حجم الناتج من النموذج.

يكون حجم القناع الأولي (مثل 256x256) عادةً أصغر من حجم الصورة المُدخَلة.

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

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

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.

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

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

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

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

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