Apple platformlarında AutoML ile eğitilmiş bir modelle görüntülerdeki nesneleri algılama

AutoML Vision Edge'i kullanarak kendi modelinizi eğittikten sonra bunu uygulamanızda görüntülerdeki nesneleri algılamak için kullanabilirsiniz.

AutoML Vision Edge'den eğitilen modelleri entegre etmenin iki yolu vardır. Modelin dosyalarını Xcode projenize kopyalayarak modeli paketleyebilir veya Firebase'den dinamik olarak indirebilirsiniz.

Model paketleme seçenekleri
Uygulamanızda paketlenmiştir
  • Model paketin bir parçasıdır
  • Model, Apple cihazı çevrimdışı olduğunda bile anında kullanılabilir
  • Firebase projesine gerek yok
Firebase'de barındırılıyor
  • Modeli Firebase Machine Learning'e yükleyerek barındırın
  • Uygulama paketi boyutunu azaltır
  • Model talep üzerine indirilir
  • Uygulamanızı yeniden yayınlamadan model güncellemelerini aktarın
  • Firebase Remote Config ile kolay A/B testi
  • Firebase projesi gerektirir

Sen başlamadan önce

  1. Bir model indirmek istiyorsanız , henüz yapmadıysanız Firebase'i Apple projenize eklediğinizden emin olun. Modeli paketlediğinizde buna gerek yoktur.

  2. TensorFlow ve Firebase kitaplıklarını Pod dosyanıza ekleyin:

    Bir modeli uygulamanızla birlikte paketlemek için:

    Süratli

    pod 'TensorFlowLiteSwift'
    

    Amaç-C

    pod 'TensorFlowLiteObjC'
    

    Firebase'den dinamik olarak bir model indirmek için Firebase/MLModelInterpreter bağımlılığını ekleyin:

    Süratli

    pod 'TensorFlowLiteSwift'
    pod 'Firebase/MLModelInterpreter'
    

    Amaç-C

    pod 'TensorFlowLiteObjC'
    pod 'Firebase/MLModelInterpreter'
    
  3. Projenizin Pod'larını yükledikten veya güncelledikten sonra Xcode projenizi .xcworkspace kullanarak açın.

1. Modeli yükleyin

Yerel bir model kaynağı yapılandırma

Modeli uygulamanızla birlikte paketlemek için modeli ve etiketler dosyasını Xcode projenize kopyalayın ve bunu yaparken Klasör referansları oluştur'u seçmeye dikkat edin. Model dosyası ve etiketler uygulama paketine dahil edilecektir.

Ayrıca modelle birlikte oluşturulan tflite_metadata.json dosyasına bakın. İki değere ihtiyacınız var:

  • Modelin giriş boyutları. Bu, varsayılan olarak 320x320'dir.
  • Modelin maksimum algılamaları. Bu varsayılan olarak 40'tır.

Firebase tarafından barındırılan bir model kaynağı yapılandırma

Uzaktan barındırılan modeli kullanmak için, modeli yayınlarken atadığınız adı belirterek bir CustomRemoteModel nesnesi oluşturun:

Süratli

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

Amaç-C

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

Ardından, indirmeye izin vermek istediğiniz koşulları belirterek model indirme görevini başlatın. Model cihazda yoksa veya modelin daha yeni bir sürümü mevcutsa görev, modeli Firebase'den eşzamansız olarak indirir:

Süratli

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

Amaç-C

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

Çoğu uygulama, indirme görevini kendi başlatma kodunda başlatır, ancak bunu, modeli kullanmanız gerekmeden önce herhangi bir noktada yapabilirsiniz.

Modelinizden bir nesne algılayıcı oluşturun

Model kaynaklarınızı yapılandırdıktan sonra bunlardan birinden bir TensorFlow Lite Interpreter nesnesi oluşturun.

Yalnızca yerel olarak paketlenmiş bir modeliniz varsa, model dosyasından bir yorumlayıcı oluşturmanız yeterlidir:

Süratli

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

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

Uzaktan barındırılan bir modeliniz varsa, çalıştırmadan önce indirilip indirilmediğini kontrol etmeniz gerekecektir. Model yöneticisinin isModelDownloaded(remoteModel:) yöntemini kullanarak model indirme görevinin durumunu kontrol edebilirsiniz.

Her ne kadar yorumlayıcıyı çalıştırmadan önce bunu yalnızca onaylamanız gerekse de, hem uzaktan barındırılan bir modeliniz hem de yerel olarak paketlenmiş bir modeliniz varsa, Interpreter başlatırken bu kontrolü gerçekleştirmek mantıklı olabilir: eğer uzak modelden bir yorumlayıcı oluşturun indirilmiş ve aksi takdirde yerel modelden.

Süratli

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

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

Yalnızca uzaktan barındırılan bir modeliniz varsa, modelin indirildiğini onaylayana kadar modelle ilgili işlevleri devre dışı bırakmalısınız (örneğin, kullanıcı arayüzünüzün bir kısmını grileştirme veya gizleme).

Gözlemcileri varsayılan Bildirim Merkezine ekleyerek model indirme durumunu alabilirsiniz. İndirme işlemleri biraz zaman alabileceğinden ve indirme işlemi tamamlandığında kaynak nesne serbest bırakılabileceğinden, gözlemci bloğunda self zayıf bir referans kullandığınızdan emin olun. Örneğin:

Süratli

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

Amaç-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. Giriş görüntüsünü hazırlayın

Daha sonra görüntülerinizi TensorFlow Lite yorumlayıcısı için hazırlamanız gerekir.

  1. Görüntüyü, tflite_metadata.json dosyasında belirtildiği gibi (varsayılan olarak 320x320 piksel) modelin giriş boyutlarına göre kırpın ve ölçeklendirin. Bunu Core Image veya üçüncü taraf bir kitaplıkla yapabilirsiniz.

  2. Görüntü verilerini bir Data ( NSData nesnesine) kopyalayın:

    Süratli

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

    Amaç-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. Nesne dedektörünü çalıştırın

Daha sonra hazırlanan girişi tercümana iletin:

Süratli

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

Amaç-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. Algılanan nesneler hakkında bilgi alın

Nesne tespiti başarılı olursa, model çıktı olarak her biri 40 öğeden (veya tflite_metadata.json dosyasında belirtilenlerden) oluşan üç dizi üretir. Her öğe bir potansiyel nesneye karşılık gelir. İlk dizi, sınırlayıcı kutulardan oluşan bir dizidir; ikincisi, bir dizi etiket; ve üçüncüsü, bir dizi güven değeri. Model çıktılarını almak için:

Süratli

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)

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

Daha sonra etiket çıktılarını etiket sözlüğünüzle birleştirebilirsiniz:

Süratli

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

Amaç-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);
    }
}

Gerçek zamanlı performansı artırmaya yönelik ipuçları

Görüntüleri gerçek zamanlı bir uygulamada etiketlemek istiyorsanız en iyi kare hızlarına ulaşmak için şu yönergeleri izleyin:

  • Gaz kelebeği dedektöre çağrı yapar. Dedektör çalışırken yeni bir video karesi kullanılabilir hale gelirse kareyi bırakın.
  • Dedektörün çıkışını grafikleri giriş görüntüsüne yerleştirmek için kullanıyorsanız, önce sonucu alın, ardından tek adımda görüntüyü oluşturun ve üst üste koyun. Bunu yaparak, her giriş karesi için ekran yüzeyini yalnızca bir kez görüntüleyebilirsiniz. Örnek için vitrin örnek uygulamasındaki önizlemeOverlayView ve FIRDetectionOverlayView sınıflarına bakın.