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

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

للتجربة:

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

قبل البدء

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

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

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

لإجراء عملية التقسيم على صورة سيلفي، عليك أولاً إنشاء مثيل من 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);
}

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

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

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

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

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

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