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

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

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

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

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

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

  2. کتابخانه های TensorFlow و Firebase را در فایل پادفایل خود قرار دهید:

    برای بسته‌بندی یک مدل با برنامه‌تان:

    سریع

    pod 'TensorFlowLiteSwift'
    

    هدف-C

    pod 'TensorFlowLiteObjC'
    

    برای دانلود پویا یک مدل از Firebase، وابستگی Firebase/MLModelInterpreter را اضافه کنید:

    سریع

    pod 'TensorFlowLiteSwift'
    pod 'Firebase/MLModelInterpreter'
    

    هدف-C

    pod 'TensorFlowLiteObjC'
    pod 'Firebase/MLModelInterpreter'
    
  3. پس از نصب یا به روز رسانی Pods پروژه خود، پروژه Xcode خود را با استفاده از .xcworkspace . آن باز کنید.

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

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

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

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

  • ابعاد ورودی مدل این به طور پیش فرض 320x320 است.
  • حداکثر تشخیص مدل این به طور پیش فرض 40 است.

یک منبع مدل میزبانی شده توسط Firebase را پیکربندی کنید

برای استفاده از مدل میزبانی شده از راه دور، یک شی CustomRemoteModel ایجاد کنید و نامی را که به مدل اختصاص داده اید در هنگام انتشار آن مشخص کنید:

سریع

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

هدف-C

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

سپس، با مشخص کردن شرایطی که می‌خواهید اجازه دانلود را بدهید، کار دانلود مدل را شروع کنید. اگر مدل در دستگاه نباشد، یا اگر نسخه جدیدتری از مدل موجود باشد، این کار به صورت ناهمزمان مدل را از Firebase دانلود می‌کند:

سریع

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

هدف-C

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

بسیاری از برنامه‌ها وظیفه دانلود را در کد اولیه خود شروع می‌کنند، اما شما می‌توانید این کار را در هر زمانی قبل از نیاز به استفاده از مدل انجام دهید.

یک آشکارساز شی از مدل خود ایجاد کنید

پس از اینکه منابع مدل خود را پیکربندی کردید، یک شیء Interpreter TensorFlow Lite از یکی از آنها ایجاد کنید.

اگر فقط یک مدل بومی همراه دارید، فقط یک مفسر از فایل مدل ایجاد کنید:

سریع

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

هدف-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 منطقی باشد: اگر مفسر از مدل راه دور است، یک مفسر ایجاد کنید. دانلود شده است، و در غیر این صورت از مدل محلی.

سریع

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

هدف-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 در بلوک ناظر استفاده کنید، زیرا دانلودها ممکن است مدتی طول بکشد، و شی مبدا می‌تواند تا پایان دانلود آزاد شود. مثلا:

سریع

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

هدف-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 (به طور پیش فرض 320x320 پیکسل) مشخص شده است، تصویر را به ابعاد ورودی مدل برش داده و مقیاس دهید. می توانید این کار را با 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)
      }
    }
    

    هدف-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. آشکارساز شی را اجرا کنید

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

سریع

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

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

سریع

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)

هدف-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; }

سپس، می توانید خروجی های برچسب را با فرهنگ لغت برچسب خود ترکیب کنید:

سریع

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

هدف-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 را در برنامه نمونه ویترینی ببینید.