Mendeteksi objek pada gambar dengan model yang dilatih AutoML di platform Apple

Setelah melatih model sendiri menggunakan AutoML Vision Edge, Anda dapat menggunakannya di aplikasi untuk mendeteksi objek dalam gambar.

Ada dua cara untuk mengintegrasikan model yang dilatih dari AutoML Vision Edge. Anda dapat memaketkan model dengan menyalin file model ke project Xcode, atau Anda dapat mendownloadnya secara dinamis dari Firebase.

Opsi pemaketan model
Dipaketkan dalam aplikasi Anda
  • Model merupakan bagian dari paket
  • Model akan langsung tersedia, bahkan saat perangkat Apple sedang offline
  • Tidak memerlukan project Firebase
Dihosting dengan Firebase
  • Hosting model dengan menguploadnya ke Firebase Machine Learning
  • Mengurangi ukuran app bundle
  • Model didownload sesuai permintaan
  • Update model dapat dikirim tanpa memublikasikan ulang aplikasi
  • Pengujian A/B yang mudah dengan Firebase Remote Config
  • Memerlukan project Firebase

Sebelum memulai

  1. Jika ingin mendownload model, pastikan Anda menambahkan Firebase ke project Apple, jika belum melakukannya. Langkah ini tidak diperlukan jika Anda memaketkan model.

  2. Sertakan library TensorFlow dan Firebase di Podfile Anda:

    Untuk memaketkan model dengan aplikasi Anda:

    Swift

    pod 'TensorFlowLiteSwift'
    

    Objective-C

    pod 'TensorFlowLiteObjC'
    

    Untuk mendownload model dari Firebase secara dinamis, tambahkan dependensi Firebase/MLModelInterpreter:

    Swift

    pod 'TensorFlowLiteSwift'
    pod 'Firebase/MLModelInterpreter'
    

    Objective-C

    pod 'TensorFlowLiteObjC'
    pod 'Firebase/MLModelInterpreter'
    
  3. Setelah Anda menginstal atau mengupdate Pod project, buka project Xcode menggunakan .xcworkspace-nya.

1. Muat model

Mengonfigurasi sumber model lokal

Untuk memaketkan model dengan aplikasi Anda, salin file model dan label ke project Xcode. Pastikan untuk memilih Create folder references ketika Anda melakukannya. File model dan label akan disertakan dalam app bundle.

Selain itu, lihat file tflite_metadata.json yang dibuat bersama model tersebut. Anda memerlukan dua nilai:

  • Dimensi input model. Dimensinya adalah 320x320 secara default.
  • Deteksi maksimum model. Nilai ini disetel ke 40 secara default.

Mengonfigurasi sumber model yang dihosting Firebase

Untuk menggunakan model yang dihosting dari jarak jauh, buat objek CustomRemoteModel, dengan menentukan nama yang ditetapkan pada model saat memublikasikannya:

Swift

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

Objective-C

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

Kemudian, mulai tugas download model, dengan menentukan kondisi yang Anda inginkan untuk mengizinkan download. Jika model tidak ada di perangkat, atau jika versi model yang lebih baru tersedia, tugas ini akan mendownload model dari Firebase secara asinkron:

Swift

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

Objective-C

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

Banyak aplikasi memulai tugas download dalam kode inisialisasinya, tetapi Anda dapat melakukannya kapan saja sebelum menggunakan model.

Membuat detektor objek dari model Anda

Setelah mengonfigurasi sumber model, buat objek Interpreter TensorFlow Lite dari salah satu sumber model tersebut.

Jika Anda hanya memiliki model yang dipaketkan secara lokal, cukup buat penafsir dari file model:

Swift

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

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

Jika Anda memiliki model yang dihosting dari jarak jauh, Anda harus memeriksa apakah model tersebut sudah didownload sebelum menjalankannya. Anda dapat memeriksa status tugas download model menggunakan metode isModelDownloaded(remoteModel:) pengelola model.

Meskipun Anda hanya perlu mengonfirmasi hal ini sebelum menjalankan penafsir, jika Anda memiliki model yang dihosting dari jarak jauh dan model yang dipaketkan secara lokal, sebaiknya lakukan pemeriksaan ini saat membuat instance Interpreter: buat penafsir dari model jarak jauh jika model tersebut sudah didownload, dan dari model lokal jika belum didownload.

Swift

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

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

Jika Anda hanya memiliki model yang dihosting dari jarak jauh, Anda harus menonaktifkan fungsi yang terkait dengan model—misalnya, menyamarkan atau menyembunyikan sebagian UI—sampai Anda mengonfirmasi bahwa model telah didownload.

Anda dapat memperoleh status download model dengan menambahkan observer ke Pusat Notifikasi default. Pastikan untuk menggunakan referensi lemah ke self di blok observer, karena proses download memerlukan waktu beberapa saat, dan objek asalnya dapat dibebaskan pada saat download selesai. Contoh:

Swift

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

Objective-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. Menyiapkan gambar input

Selanjutnya, Anda perlu mempersiapkan gambar untuk penafsir TensorFlow Lite.

  1. Pangkas dan skalakan gambar ke dimensi input model, seperti yang ditentukan dalam file tflite_metadata.json (320x320 piksel secara default). Anda dapat melakukannya dengan Core Image atau library pihak ketiga

  2. Salin data gambar ke dalam Data (objek NSData):

    Swift

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

    Objective-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. Menjalankan detektor objek

Berikutnya, teruskan input yang telah disiapkan ke penafsir:

Swift

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

Objective-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. Mendapatkan informasi tentang objek yang terdeteksi

Jika deteksi objek berhasil, setiap model menghasilkan tiga array yang terdiri dari 40 elemen (atau apa pun yang ditentukan dalam file tflite_metadata.json) sebagai output. Setiap elemen sesuai dengan satu objek potensial. Array pertama adalah array kotak pembatas; yang kedua adalah array label; dan yang ketiga adalah array tingkat keyakinan. Untuk mendapatkan output model:

Swift

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)

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

Kemudian, Anda dapat menggabungkan output label dengan kamus label:

Swift

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

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

Tips untuk meningkatkan performa real-time

Jika Anda ingin memberikan label pada gambar dalam aplikasi real-time, ikuti panduan ini untuk mencapai frekuensi gambar terbaik:

  • Batasi panggilan ke detektor. Jika frame video baru tersedia saat detektor sedang berjalan, hapus frame tersebut.
  • Jika Anda menggunakan output detektor untuk menempatkan grafis pada gambar input, pertama-tama dapatkan hasilnya, lalu render gambar dan tempatkan grafis dalam satu langkah. Dengan demikian, Anda hanya merender ke permukaan tampilan sekali untuk setiap frame input. Lihat class previewOverlayView dan FIRDetectionOverlayView dalam aplikasi contoh showcase untuk mengetahui contohnya.