Apple platformlarında AutoML eğitimli bir modelle görüntülerdeki nesneleri tespit edin

AutoML Vision Edge kullanarak kendi modelinizi eğittikten sonra, görüntülerdeki nesneleri algılamak için uygulamanızda 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ş
  • Model, paketin bir parçasıdır
  • Model, Apple cihazı çevrimdışıyken bile hemen kullanılabilir
  • Firebase projesine gerek yok
Firebase ile 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 gönderin
  • Firebase Remote Config ile kolay A/B testi
  • Bir 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 bu gerekli değildir.

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

    Uygulamanızla bir modeli gruplamak 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ırın

Modeli uygulamanızla paketlemek için, model 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 modelin yanında 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ırın

Uzaktan barındırılan modeli kullanmak için, yayınladığınızda modele 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 değilse 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];

Birçok uygulama indirme görevini başlatma kodunda başlatır, ancak bunu modeli kullanmanız gerekmeden önce herhangi bir noktada yapabilirsiniz.

Modelinizden bir nesne dedektörü 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 indirildiğini kontrol etmeniz gerekir. Model yöneticisinin isModelDownloaded(remoteModel:) yöntemini kullanarak model indirme görevinin durumunu kontrol edebilirsiniz.

Bunu yalnızca yorumlayıcıyı çalıştırmadan önce onaylamanız gerekse de, hem uzaktan barındırılan hem de yerel olarak paketlenmiş bir modeliniz varsa, Interpreter örneğini başlatırken bu kontrolü yapmak mantıklı olabilir: indirildi 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 bölümünü grileştirme veya gizleme).

Model indirme durumunu, varsayılan Bildirim Merkezi'ne gözlemciler ekleyerek alabilirsiniz. İndirmeler biraz zaman alabileceğinden ve indirme bittiğinde kaynak nesne serbest bırakılabileceğinden, gözlemci bloğunda self için 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

Ardından, 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 (varsayılan olarak 320x320 piksel) belirtildiği gibi modelin giriş boyutlarına göre kırpın ve ölçekleyin. Bunu Core Image veya bir üçüncü taraf kitaplığı ile yapabilirsiniz.

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

    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

Ardından, hazırlanan girdiyi yorumlayıcıya 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 algılama başarılı olursa, model her biri 40 öğeden oluşan (veya tflite_metadata.json dosyasında belirtilen her neyse) üç dizi çıktı olarak üretir. Her eleman bir potansiyel nesneye karşılık gelir. İlk dizi, bir dizi sınırlayıcı kutudur; 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; }

Ardından, 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ı iyileştirmeye yönelik ipuçları

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

  • Gaz kelebeği dedektörü çağırır. Dedektör çalışırken yeni bir video karesi mevcutsa, kareyi bırakın.
  • Giriş görüntüsünün üzerine grafik bindirmek için dedektörün çıkışını kullanıyorsanız, önce sonucu alın, ardından görüntüyü oluşturun ve tek adımda bindirin. Bunu yaparak, her bir giriş karesi için yalnızca bir kez görüntüleme yüzeyini oluşturursunuz. Bir örnek için vitrin örnek uygulamasında önizlemeOverlayView ve FIRDetectionOverlayView sınıflarına bakın.