اكتشف الكائنات في الصور باستخدام نموذج مدرب على AutoML على منصات Apple

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

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

خيارات تجميع النماذج
المجمعة في التطبيق الخاص بك
  • النموذج جزء من الحزمة
  • النموذج متاح على الفور، حتى عندما يكون جهاز Apple غير متصل بالإنترنت
  • ليست هناك حاجة لمشروع Firebase
مستضاف مع Firebase
  • قم باستضافة النموذج عن طريق تحميله إلى Firebase Machine Learning
  • يقلل من حجم حزمة التطبيق
  • يتم تنزيل النموذج عند الطلب
  • دفع تحديثات النموذج دون إعادة نشر تطبيقك
  • اختبار A/B سهل باستخدام Firebase Remote Config
  • يتطلب مشروع Firebase

قبل ان تبدأ

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

  2. قم بتضمين مكتبات TensorFlow وFirebase في ملف Podfile الخاص بك:

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

    سويفت

    pod 'TensorFlowLiteSwift'
    

    ج موضوعية

    pod 'TensorFlowLiteObjC'
    

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

    سويفت

    pod 'TensorFlowLiteSwift'
    pod 'Firebase/MLModelInterpreter'
    

    ج موضوعية

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

1. قم بتحميل النموذج

تكوين مصدر نموذج محلي

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

انظر أيضًا إلى ملف tflite_metadata.json الذي تم إنشاؤه بجانب النموذج. تحتاج إلى قيمتين:

  • أبعاد مدخلات النموذج هذا هو 320x320 بشكل افتراضي.
  • الحد الأقصى للاكتشافات للنموذج. هذا هو 40 افتراضيا.

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

لاستخدام النموذج المستضاف عن بعد، قم بإنشاء كائن CustomRemoteModel ، مع تحديد الاسم الذي قمت بتعيينه للنموذج عند نشره:

سويفت

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

ج موضوعية

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

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

سويفت

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

ج موضوعية

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

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

قم بإنشاء كاشف كائن من النموذج الخاص بك

بعد تكوين مصادر النموذج الخاص بك، قم بإنشاء كائن TensorFlow Lite Interpreter من أحد هذه المصادر.

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

سويفت

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()

ج موضوعية

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 الفوري: قم بإنشاء مترجم من النموذج البعيد إذا كان تم تحميلها، ومن النموذج المحلي خلاف ذلك.

سويفت

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()

ج موضوعية

__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 في كتلة المراقب، نظرًا لأن التنزيلات قد تستغرق بعض الوقت، ويمكن تحرير الكائن الأصلي عند انتهاء التنزيل. على سبيل المثال:

سويفت

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]
    // ...
}

ج موضوعية

__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 ):

    سويفت

    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)
      }
    }
    

    ج موضوعية

    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. قم بتشغيل كاشف الأشياء

بعد ذلك، قم بتمرير المدخلات المعدة للمترجم:

سويفت

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

ج موضوعية

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 ) لكل منها. كل عنصر يتوافق مع كائن واحد محتمل. المصفوفة الأولى عبارة عن مصفوفة من المربعات المحيطة؛ والثاني، مجموعة من التسميات؛ والثالث، مجموعة من قيم الثقة. للحصول على مخرجات النموذج:

سويفت

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)

ج موضوعية

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; }

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

سويفت

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))")
    }
}

ج موضوعية

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 في نموذج تطبيق العرض للحصول على مثال.