ارصد الأجسام في الصور باستخدام نموذج مدرَّب على AutoML على منصات Apple.

بعد تدريب النموذج الخاص بك باستخدام AutoML Vision Edge، يمكنك استخدامه في تطبيقك لرصد الأجسام في الصور.

هناك طريقتان لدمج النماذج التي تم تدريبها من AutoML Vision Edge. يمكنك تجميع النموذج عن طريق نسخ ملفات النموذج إلى مشروع Xcode، أو يمكنك تنزيله ديناميكيًا من Firebase.

خيارات تجميع النماذج
مُضمَّنة في تطبيقك
  • النموذج جزء من الحزمة
  • يتوفّر التصميم على الفور، حتى عندما يكون جهاز Apple غير متصل بالإنترنت.
  • لا حاجة إلى مشروع على Firebase
مستضافة باستخدام Firebase

قبل البدء

  1. إذا أردت تنزيل نموذج، تأكَّد من إضافة Firebase إلى مشروعك على Apple، إذا لم يسبق لك إجراء ذلك. ولا يُشترط إجراء ذلك عند تجميع النموذج.

  2. أدرِج مكتبتَي TensorFlow وFirebase في ملف Podfile:

    لتجميع نموذج مع تطبيقك:

    Swift

    pod 'TensorFlowLiteSwift'
    

    Objective-C

    pod 'TensorFlowLiteObjC'
    

    لتنزيل نموذج ديناميكيًا من Firebase، أضِف التبعية Firebase/MLModelInterpreter:

    Swift

    pod 'TensorFlowLiteSwift'
    pod 'Firebase/MLModelInterpreter'
    

    Objective-C

    pod 'TensorFlowLiteObjC'
    pod 'Firebase/MLModelInterpreter'
    
  3. بعد تثبيت حِزم Pods في مشروعك أو تحديثها، افتح مشروع Xcode باستخدام .xcworkspace.

1- تحميل النموذج

ضبط مصدر نموذج على الجهاز

لتجميع النموذج مع تطبيقك، انسخ ملف النموذج والملصقات إلى مشروع Xcode، مع الحرص على اختيار إنشاء إشارات إلى المجلد عند إجراء ذلك. سيتم تضمين ملف النموذج والملصقات في حِزمة التطبيق.

اطّلِع أيضًا على ملف tflite_metadata.json الذي تم إنشاؤه إلى جانب ملف النموذج. ستحتاج إلى قيمتَين:

  • سمات إدخال النموذج تكون هذه القيمة 320×320 تلقائيًا.
  • الحد الأقصى لعمليات رصد النموذج وتتراوح هذه القيمة تلقائيًا 40.

ضبط مصدر نموذج مستضاف على Firebase

لاستخدام النموذج المستضاف عن بُعد، أنشئ عنصرًا من النوع CustomRemoteModel ، مع تحديد الاسم الذي منحته للنموذج عند نشره:

Swift

let remoteModel = CustomRemoteModel(
    name: "your_remote_model"  // The name you assigned in the Google Cloud console.
)

Objective-C

FIRCustomRemoteModel *remoteModel = [[FIRCustomRemoteModel alloc]
                                     initWithName:@"your_remote_model"];

بعد ذلك، ابدأ مهمة تنزيل النموذج، مع تحديد الشروط التي تريد السماح بالتنزيل بموجبها. إذا لم يكن النموذج متوفّرًا على الجهاز أو إذا كان هناك إصدار أحدث من النموذج، ستنزِّل المهمة النموذج بشكل غير متزامن من Firebase:

Swift

let downloadProgress = ModelManager.modelManager().download(
    remoteModel,
    conditions: ModelDownloadConditions(
        allowsCellularAccess: true,
        allowsBackgroundDownloading: true
    )
)

Objective-C

FIRModelDownloadConditions *conditions =
        [[FIRModelDownloadConditions alloc] initWithAllowsCellularAccess:YES
                                             allowsBackgroundDownloading:YES];
NSProgress *progress = [[FIRModelManager modelManager] downloadModel:remoteModel
                                                          conditions:conditions];

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

إنشاء أداة رصد عناصر من نموذجك

بعد ضبط مصادر النماذج، أنشئ Interpreter عنصرًا من TensorFlow Lite من أحدها.

إذا كان لديك نموذج مجمّع على الجهاز فقط، ما عليك سوى إنشاء مترجم من ملف النموذج:

Swift

guard let modelPath = Bundle.main.path(
    forResource: "model",
    ofType: "tflite"
) else {
  print("Failed to load the model file.")
  return true
}
let interpreter = try Interpreter(modelPath: modelPath)
try interpreter.allocateTensors()

Objective-C

NSString *modelPath = [[NSBundle mainBundle] pathForResource:@"model"
                                                      ofType:@"tflite"];

NSError *error;
TFLInterpreter *interpreter = [[TFLInterpreter alloc] initWithModelPath:modelPath
                                                                  error:&error];
if (error != NULL) { return; }

[interpreter allocateTensorsWithError:&error];
if (error != NULL) { return; }

إذا كان لديك نموذج مستضاف عن بُعد، عليك التأكّد من أنّه قد تم تنزيله قبل تشغيله. يمكنك التحقّق من حالة مهمة تنزيل النموذج باستخدام طريقة isModelDownloaded(remoteModel:) لمدير النموذج.

على الرغم من أنّه عليك تأكيد ذلك فقط قبل تشغيل المُفسِّر، إذا كان لديك نموذج مستضاف عن بُعد ونموذج مجمّع محليًا، قد يكون من المنطقي إجراء هذا التحقّق عند إنشاء مثيل Interpreter: أنشئ مترجمًا من النموذج عن بُعد إذا تم تنزيله، ومن النموذج المحلي بخلاف ذلك.

Swift

var modelPath: String?
if ModelManager.modelManager().isModelDownloaded(remoteModel) {
    ModelManager.modelManager().getLatestModelFilePath(remoteModel) { path, error in
        guard error == nil else { return }
        guard let path = path else { return }
        modelPath = path
    }
} else {
    modelPath = Bundle.main.path(
        forResource: "model",
        ofType: "tflite"
    )
}

guard modelPath != nil else { return }
let interpreter = try Interpreter(modelPath: modelPath)
try interpreter.allocateTensors()

Objective-C

__block NSString *modelPath;
if ([[FIRModelManager modelManager] isModelDownloaded:remoteModel]) {
    [[FIRModelManager modelManager] getLatestModelFilePath:remoteModel
                                                completion:^(NSString * _Nullable filePath,
                                                             NSError * _Nullable error) {
        if (error != NULL) { return; }
        if (filePath == NULL) { return; }
        modelPath = filePath;
    }];
} else {
    modelPath = [[NSBundle mainBundle] pathForResource:@"model"
                                                ofType:@"tflite"];
}

NSError *error;
TFLInterpreter *interpreter = [[TFLInterpreter alloc] initWithModelPath:modelPath
                                                                  error:&error];
if (error != NULL) { return; }

[interpreter allocateTensorsWithError:&error];
if (error != NULL) { return; }

إذا كان لديك نموذج مستضاف عن بُعد فقط، عليك إيقاف وظائفه المتعلقة بالنموذج، مثل إخفاء جزء من واجهة المستخدم أو جعله يظهر باللون الرمادي، إلى أن يتم تأكيد تنزيل النموذج.

يمكنك الحصول على حالة تنزيل النموذج من خلال إرفاق مراقبين بتطبيق "مركز الإشعارات" التلقائي. احرص على استخدام إشارة ضعيفة إلى self في ملف الملاحظ، لأنّ عمليات التنزيل قد تستغرق بعض الوقت، ويمكن ملف الملاحظ تحريره بحلول وقت انتهاء التنزيل. على سبيل المثال:

Swift

NotificationCenter.default.addObserver(
    forName: .firebaseMLModelDownloadDidSucceed,
    object: nil,
    queue: nil
) { [weak self] notification in
    guard let strongSelf = self,
        let userInfo = notification.userInfo,
        let model = userInfo[ModelDownloadUserInfoKey.remoteModel.rawValue]
            as? RemoteModel,
        model.name == "your_remote_model"
        else { return }
    // The model was downloaded and is available on the device
}

NotificationCenter.default.addObserver(
    forName: .firebaseMLModelDownloadDidFail,
    object: nil,
    queue: nil
) { [weak self] notification in
    guard let strongSelf = self,
        let userInfo = notification.userInfo,
        let model = userInfo[ModelDownloadUserInfoKey.remoteModel.rawValue]
            as? RemoteModel
        else { return }
    let error = userInfo[ModelDownloadUserInfoKey.error.rawValue]
    // ...
}

Objective-C

__weak typeof(self) weakSelf = self;

[NSNotificationCenter.defaultCenter
    addObserverForName:FIRModelDownloadDidSucceedNotification
                object:nil
                 queue:nil
            usingBlock:^(NSNotification *_Nonnull note) {
              if (weakSelf == nil | note.userInfo == nil) {
                return;
              }
              __strong typeof(self) strongSelf = weakSelf;

              FIRRemoteModel *model = note.userInfo[FIRModelDownloadUserInfoKeyRemoteModel];
              if ([model.name isEqualToString:@"your_remote_model"]) {
                // The model was downloaded and is available on the device
              }
            }];

[NSNotificationCenter.defaultCenter
    addObserverForName:FIRModelDownloadDidFailNotification
                object:nil
                 queue:nil
            usingBlock:^(NSNotification *_Nonnull note) {
              if (weakSelf == nil | note.userInfo == nil) {
                return;
              }
              __strong typeof(self) strongSelf = weakSelf;

              NSError *error = note.userInfo[FIRModelDownloadUserInfoKeyError];
            }];

2- تحضير صورة الإدخال

بعد ذلك، عليك إعداد صورك لمُفسِّر TensorFlow Lite.

  1. اقتَص الصورة وغيِّر حجمها لتتلاءم مع أبعاد إدخال النموذج، كما هو محدّد فيملفtflite_metadata.json (320×320 بكسل تلقائيًا). يمكنك إجراء ذلك باستخدام Core Image أو مكتبة تابعة لجهة خارجية.

  2. انسخ بيانات الصورة إلى عنصر Data (كائن NSData):

    Swift

    guard let image: CGImage = // Your input image
    guard let context = CGContext(
      data: nil,
      width: image.width, height: image.height,
      bitsPerComponent: 8, bytesPerRow: image.width * 4,
      space: CGColorSpaceCreateDeviceRGB(),
      bitmapInfo: CGImageAlphaInfo.noneSkipFirst.rawValue
    ) else {
      return nil
    }
    
    context.draw(image, in: CGRect(x: 0, y: 0, width: image.width, height: image.height))
    guard let imageData = context.data else { return nil }
    
    var inputData = Data()
    for row in 0 ..< 320 {    // Model takes 320x320 pixel images as input
      for col in 0 ..< 320 {
        let offset = 4 * (col * context.width + row)
        // (Ignore offset 0, the unused alpha channel)
        var red = imageData.load(fromByteOffset: offset+1, as: UInt8.self)
        var green = imageData.load(fromByteOffset: offset+2, as: UInt8.self)
        var blue = imageData.load(fromByteOffset: offset+3, as: UInt8.self)
    
        inputData.append(&red, count: 1)
        inputData.append(&green, count: 1)
        inputData.append(&blue, count: 1)
      }
    }
    

    Objective-C

    CGImageRef image = // Your input image
    long imageWidth = CGImageGetWidth(image);
    long imageHeight = CGImageGetHeight(image);
    CGContextRef context = CGBitmapContextCreate(nil,
                                                 imageWidth, imageHeight,
                                                 8,
                                                 imageWidth * 4,
                                                 CGColorSpaceCreateDeviceRGB(),
                                                 kCGImageAlphaNoneSkipFirst);
    CGContextDrawImage(context, CGRectMake(0, 0, imageWidth, imageHeight), image);
    UInt8 *imageData = CGBitmapContextGetData(context);
    
    NSMutableData *inputData = [[NSMutableData alloc] initWithCapacity:0];
    
    for (int row = 0; row < 300; row++) {
      for (int col = 0; col < 300; col++) {
        long offset = 4 * (row * imageWidth + col);
        // (Ignore offset 0, the unused alpha channel)
        UInt8 red = imageData[offset+1];
        UInt8 green = imageData[offset+2];
        UInt8 blue = imageData[offset+3];
    
        [inputData appendBytes:&red length:1];
        [inputData appendBytes:&green length:1];
        [inputData appendBytes:&blue length:1];
      }
    }
    

3- تشغيل أداة رصد الأجسام

بعد ذلك، عليك تمرير الإدخال المُعدّ إلى المترجم:

Swift

try interpreter.copy(inputData, toInputAt: 0)
try interpreter.invoke()

Objective-C

TFLTensor *input = [interpreter inputTensorAtIndex:0 error:&error];
if (error != nil) { return; }

[input copyData:inputData error:&error];
if (error != nil) { return; }

[interpreter invokeWithError:&error];
if (error != nil) { return; }

4. الحصول على معلومات عن العناصر المرصودة

إذا نجح اكتشاف الكائنات، ينتج النموذج ثلاث صفيفات من كل منها مؤلف من 40 عنصرًا (أو أي عنصر تم تحديده في ملف tflite_metadata.json). يرتبط كل عنصر بكائن محتمل واحد. المصفوفة الأولى هي صفيف من المربّعات الحدودية، والمصفوفة الثانية هي صفيف من التصنيفات، والمصفوفة الثالثة هي صفيف من قيم الثقة. للحصول على نواتج النموذج:

Swift

var output = try interpreter.output(at: 0)
let boundingBoxes =
    UnsafeMutableBufferPointer<Float32>.allocate(capacity: 4 * 40)
output.data.copyBytes(to: boundingBoxes)

output = try interpreter.output(at: 1)
let labels =
    UnsafeMutableBufferPointer<Float32>.allocate(capacity: 40)
output.data.copyBytes(to: labels)

output = try interpreter.output(at: 2)
let probabilities =
    UnsafeMutableBufferPointer<Float32>.allocate(capacity: 40)
output.data.copyBytes(to: probabilities)

Objective-C

TFLTensor *output = [interpreter outputTensorAtIndex:0 error:&error];
if (error != nil) { return; }
NSData *boundingBoxes = [output dataWithError:&error];
if (error != nil) { return; }

output = [interpreter outputTensorAtIndex:1 error:&error];
if (error != nil) { return; }
NSData *labels = [output dataWithError:&error];
if (error != nil) { return; }

output = [interpreter outputTensorAtIndex:2 error:&error];
if (error != nil) { return; }
NSData *probabilities = [output dataWithError:&error];
if (error != nil) { return; }

وبعد ذلك، يمكنك دمج مخرجات التصنيفات في قاموس التصنيفات الذي تستخدمه:

Swift

guard let labelPath = Bundle.main.path(
    forResource: "dict",
    ofType: "txt"
) else { return true }
let fileContents = try? String(contentsOfFile: labelPath)
guard let labelText = fileContents?.components(separatedBy: "\n") else { return true }

for i in 0 ..< 40 {
    let top = boundingBoxes[0 * i]
    let left = boundingBoxes[1 * i]
    let bottom = boundingBoxes[2 * i]
    let right = boundingBoxes[3 * i]

    let labelIdx = Int(labels[i])
    let label = labelText[labelIdx]
    let confidence = probabilities[i]

    if confidence > 0.66 {
        print("Object found: \(label) (confidence: \(confidence))")
        print("  Top-left: (\(left),\(top))")
        print("  Bottom-right: (\(right),\(bottom))")
    }
}

Objective-C

NSString *labelPath = [NSBundle.mainBundle pathForResource:@"dict"
                                                    ofType:@"txt"];
NSString *fileContents = [NSString stringWithContentsOfFile:labelPath
                                                   encoding:NSUTF8StringEncoding
                                                      error:&error];
if (error != nil || fileContents == NULL) { return; }
NSArray<NSString*> *labelText = [fileContents componentsSeparatedByString:@"\n"];

for (int i = 0; i < 40; i++) {
    Float32 top, right, bottom, left;
    Float32 labelIdx;
    Float32 confidence;

    [boundingBoxes getBytes:&top range:NSMakeRange(16 * i + 0, 4)];
    [boundingBoxes getBytes:&left range:NSMakeRange(16 * i + 4, 4)];
    [boundingBoxes getBytes:&bottom range:NSMakeRange(16 * i + 8, 4)];
    [boundingBoxes getBytes:&right range:NSMakeRange(16 * i + 12, 4)];

    [labels getBytes:&labelIdx range:NSMakeRange(4 * i, 4)];
    [probabilities getBytes:&confidence range:NSMakeRange(4 * i, 4)];

    if (confidence > 0.5f) {
        NSString *label = labelText[(int)labelIdx];
        NSLog(@"Object detected: %@", label);
        NSLog(@"  Confidence: %f", confidence);
        NSLog(@"  Top-left: (%f,%f)", left, top);
        NSLog(@"  Bottom-right: (%f,%f)", right, bottom);
    }
}

نصائح لتحسين الأداء في الوقت الفعلي

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

  • الحد من عدد المكالمات إلى الكاشف إذا أصبح إطار فيديو جديد متاحًا أثناء تشغيل أداة الرصد، أفلِت الإطار.
  • إذا كنت تستخدِم ناتج أداة الكشف لوضع الرسومات فوق صورة الإدخال، احصل أولاً على النتيجة، ثم أعِد عرض الصورة والعنصر المتراكب في خطوة واحدة. وبذلك، يتم عرض المحتوى على سطح العرض مرّة واحدة فقط لكل إطار إدخال. يمكنك الاطّلاع على فئتَي previewOverlayView وFIRDetectionOverlayView في تطبيق "عرض النموذج" كمثال.