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

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

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

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

قبل البدء

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

  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. بعد تثبيت لوحات مشروعك أو تحديثها، افتح مشروع Xcode الخاص بك. باستخدام .xcworkspace.

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

إعداد مصدر نموذج محلي

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

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

  • أبعاد إدخال النموذج. وتكون هذه الصورة 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 الفئات في نموذج تطبيق العرض كمثال.