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

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

التجربة الآن

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

قبل البدء

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

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

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

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

أنشئ كائن 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 للحصول على نتائج من إطار الفيديو المحدَّد بشكل متزامن. يجب ضبط قيمة alwaysDiscardsLateVideoFrames في AVCaptureVideoDataOutput على القيمة "صحيح" لتضييق نطاق الطلبات الموجّهة إلى مقسِّم النطاق. في حال توفُّر إطار فيديو جديد أثناء تشغيل المقسِّم، سيتم تجاهله.
  • إذا كنت تستخدم مُخرجات أداة التقسيم لتركيب الرسومات على الصورة المُدخَلة، احصل أولاً على النتيجة من أدوات تعلّم الآلة، ثم اعرض الصورة والتراكب في خطوة واحدة. ومن خلال إجراء ذلك، يتم عرض المحتوى على سطح الشاشة مرة واحدة فقط لكل إطار إدخال تمت معالجته. للاطّلاع على مثال، يمكنك الاطّلاع على الفئتَين previewOverlayView وCameraViewController في نموذج البدء السريع في ML Kit.