تشخیص اشیاء در تصاویر با یک مدل آموزش دیده با AutoML در پلتفرم های اپل

پس از اینکه مدل خود را با استفاده از AutoML Vision Edge آموزش دادید ، می‌توانید از آن در برنامه خود برای تشخیص اشیاء در تصاویر استفاده کنید.

دو راه برای ادغام مدل‌های آموزش‌دیده از AutoML Vision Edge وجود دارد. می‌توانید مدل را با کپی کردن فایل‌های مدل در پروژه Xcode خود، بسته‌بندی کنید، یا می‌توانید آن را به صورت پویا از Firebase دانلود کنید.

گزینه‌های بسته‌بندی مدل
در برنامه شما گنجانده شده است
  • مدل بخشی از بسته است
  • این مدل بلافاصله در دسترس است، حتی زمانی که دستگاه اپل آفلاین باشد
  • نیازی به پروژه Firebase نیست
میزبانی شده با فایربیس
  • با آپلود مدل در Firebase Machine Learning، آن را میزبانی کنید.
  • حجم بسته برنامه را کاهش می‌دهد
  • مدل بر اساس تقاضا دانلود می‌شود
  • به‌روزرسانی‌های مدل را بدون انتشار مجدد برنامه خود، ارسال کنید
  • تست A/B آسان با پیکربندی از راه دور Firebase
  • نیاز به یک پروژه Firebase دارد

قبل از اینکه شروع کنی

  1. اگر می‌خواهید یک مدل را دانلود کنید ، اگر قبلاً Firebase را به پروژه اپل خود اضافه نکرده‌اید، حتماً آن را اضافه کنید. این کار هنگام بسته‌بندی مدل لازم نیست.

  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 آن باز کنید.

۱. مدل را بارگذاری کنید

پیکربندی یک منبع مدل محلی

برای باندل کردن مدل با برنامه خود، فایل مدل و برچسب‌ها را در پروژه Xcode خود کپی کنید، و هنگام انجام این کار، حتماً گزینه Create folder references را انتخاب کنید. فایل مدل و برچسب‌ها در باندل برنامه قرار خواهند گرفت.

همچنین، به فایل tflite_metadata.json که در کنار مدل ایجاد شده است، نگاهی بیندازید. شما به دو مقدار نیاز دارید:

  • ابعاد ورودی مدل. این ابعاد به طور پیش‌فرض ۳۲۰x۳۲۰ است.
  • حداکثر تشخیص‌های مدل. این مقدار به طور پیش‌فرض ۴۰ است.

پیکربندی یک منبع مدل میزبانی‌شده توسط 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];
            }];

۲. تصویر ورودی را آماده کنید

در مرحله بعد، باید تصاویر خود را برای مفسر TensorFlow Lite آماده کنید.

  1. تصویر را برش داده و مقیاس آن را به ابعاد ورودی مدل، همانطور که در فایل tflite_metadata.json مشخص شده است، تغییر دهید (به طور پیش‌فرض ۳۲۰x۳۲۰ پیکسل). می‌توانید این کار را با 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];
      }
    }
    

۳. آشکارساز شیء را اجرا کنید

سپس، ورودی آماده شده را به مفسر ارسال کنید:

سویفت

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

۴. اطلاعات مربوط به اشیاء شناسایی شده را دریافت کنید

اگر تشخیص شیء با موفقیت انجام شود، مدل سه آرایه با ۴۰ عنصر (یا هر چیزی که در فایل 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 در برنامه نمونه ویترین مراجعه کنید.