Catch up on highlights from Firebase at Google I/O 2023. Learn more

Phát hiện các đối tượng trong hình ảnh bằng mô hình được đào tạo bởi AutoML trên nền tảng Apple

Sau khi bạn đào tạo mô hình của riêng mình bằng AutoML Vision Edge , bạn có thể sử dụng nó trong ứng dụng của mình để phát hiện các đối tượng trong hình ảnh.

Có hai cách để tích hợp các mô hình được đào tạo từ AutoML Vision Edge. Bạn có thể gói mô hình bằng cách sao chép các tệp của mô hình vào dự án Xcode của mình hoặc bạn có thể tự động tải xuống từ Firebase.

Tùy chọn gói mô hình
Gói trong ứng dụng của bạn
  • Mô hình là một phần của gói
  • Mô hình có sẵn ngay lập tức, ngay cả khi thiết bị Apple ngoại tuyến
  • Không cần dự án Firebase
Được lưu trữ với Firebase
  • Lưu trữ mô hình bằng cách tải nó lên Firebase Machine Learning
  • Giảm kích thước gói ứng dụng
  • Mô hình được tải xuống theo yêu cầu
  • Đẩy các bản cập nhật mô hình mà không xuất bản lại ứng dụng của bạn
  • Thử nghiệm A/B dễ dàng với Firebase Remote Config
  • Yêu cầu một dự án Firebase

Trước khi bắt đầu

  1. Nếu bạn muốn tải xuống một mô hình , hãy đảm bảo rằng bạn đã thêm Firebase vào dự án Apple của mình , nếu bạn chưa làm như vậy. Điều này là không cần thiết khi bạn đóng gói mô hình.

  2. Bao gồm các thư viện TensorFlow và Firebase trong Podfile của bạn:

    Để gói một mô hình với ứng dụng của bạn:

    Nhanh

    pod 'TensorFlowLiteSwift'
    

    Mục tiêu-C

    pod 'TensorFlowLiteObjC'
    

    Để tự động tải xuống một mô hình từ Firebase, hãy thêm phần phụ thuộc Firebase/MLModelInterpreter :

    Nhanh

    pod 'TensorFlowLiteSwift'
    pod 'Firebase/MLModelInterpreter'
    

    Mục tiêu-C

    pod 'TensorFlowLiteObjC'
    pod 'Firebase/MLModelInterpreter'
    
  3. Sau khi bạn cài đặt hoặc cập nhật Pods của dự án, hãy mở dự án Xcode của bạn bằng cách sử dụng .xcworkspace của nó.

1. Tải mô hình

Định cấu hình nguồn mô hình cục bộ

Để gói mô hình với ứng dụng của bạn, hãy sao chép tệp mô hình và nhãn vào dự án Xcode của bạn, cẩn thận chọn Tạo tham chiếu thư mục khi bạn làm như vậy. Tệp mô hình và nhãn sẽ được bao gồm trong gói ứng dụng.

Ngoài ra, hãy xem tệp tflite_metadata.json đã được tạo cùng với mô hình. Bạn cần hai giá trị:

  • Kích thước đầu vào của mô hình. Đây là 320x320 theo mặc định.
  • Phát hiện tối đa của mô hình. Đây là 40 theo mặc định.

Định cấu hình nguồn mô hình được lưu trữ trên Firebase

Để sử dụng mô hình được lưu trữ từ xa, hãy tạo một đối tượng CustomRemoteModel , chỉ định tên mà bạn đã gán cho mô hình khi xuất bản:

Nhanh

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

Mục tiêu-C

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

Sau đó, bắt đầu tác vụ tải xuống mô hình, chỉ định các điều kiện mà bạn muốn cho phép tải xuống. Nếu mô hình không có trên thiết bị hoặc nếu có sẵn phiên bản mới hơn của mô hình, thì tác vụ sẽ tải xuống mô hình từ Firebase một cách không đồng bộ:

Nhanh

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

Mục tiêu-C

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

Nhiều ứng dụng bắt đầu tác vụ tải xuống trong mã khởi tạo của chúng nhưng bạn có thể thực hiện việc này bất kỳ lúc nào trước khi cần sử dụng mô hình.

Tạo một máy dò đối tượng từ mô hình của bạn

Sau khi bạn định cấu hình các nguồn mô hình của mình, hãy tạo một đối tượng Interpreter TensorFlow Lite từ một trong số chúng.

Nếu bạn chỉ có một mô hình cục bộ, chỉ cần tạo một trình thông dịch từ tệp mô hình:

Nhanh

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

Mục tiêu-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; }

Nếu bạn có một mô hình được lưu trữ từ xa, bạn sẽ phải kiểm tra xem nó đã được tải xuống chưa trước khi chạy. Bạn có thể kiểm tra trạng thái của tác vụ tải xuống mô hình bằng phương thức isModelDownloaded(remoteModel:) của trình quản lý mô hình.

Mặc dù bạn chỉ phải xác nhận điều này trước khi chạy trình thông dịch, nhưng nếu bạn có cả mô hình được lưu trữ từ xa và mô hình được đóng gói cục bộ, bạn nên thực hiện kiểm tra này khi khởi tạo Interpreter : tạo một trình thông dịch từ mô hình từ xa nếu đó là đã được tải xuống và từ mô hình cục bộ nếu không.

Nhanh

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

Mục tiêu-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; }

Nếu bạn chỉ có một mô hình được lưu trữ từ xa, thì bạn nên tắt chức năng liên quan đến mô hình—ví dụ: tô xám hoặc ẩn một phần giao diện người dùng của bạn—cho đến khi bạn xác nhận mô hình đã được tải xuống.

Bạn có thể nhận trạng thái tải xuống mô hình bằng cách đính kèm trình quan sát vào Trung tâm thông báo mặc định. Đảm bảo sử dụng một tham chiếu yếu đến self trong khối người quan sát, vì quá trình tải xuống có thể mất một chút thời gian và đối tượng ban đầu có thể được giải phóng khi quá trình tải xuống kết thúc. Ví dụ:

Nhanh

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

Mục tiêu-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. Chuẩn bị ảnh đầu vào

Tiếp theo, bạn cần chuẩn bị hình ảnh của mình cho trình thông dịch TensorFlow Lite.

  1. Cắt và chia tỷ lệ hình ảnh theo kích thước đầu vào của mô hình, như được chỉ định trong tệp tflite_metadata.json (320x320 pixel theo mặc định). Bạn có thể làm điều này với Core Image hoặc thư viện của bên thứ ba

  2. Sao chép dữ liệu hình ảnh vào đối tượng Data ( NSData ):

    Nhanh

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

    Mục tiêu-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. Chạy máy dò đối tượng

Tiếp theo, chuyển đầu vào đã chuẩn bị cho trình thông dịch:

Nhanh

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

Mục tiêu-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. Nhận thông tin về các đối tượng được phát hiện

Nếu phát hiện đối tượng thành công, mô hình sẽ tạo ra dưới dạng đầu ra ba mảng gồm 40 phần tử (hoặc bất kỳ thứ gì được chỉ định trong tệp tflite_metadata.json ) mỗi mảng. Mỗi yếu tố tương ứng với một đối tượng tiềm năng. Mảng đầu tiên là một mảng các hộp giới hạn; cái thứ hai, một mảng nhãn; và thứ ba, một mảng các giá trị độ tin cậy. Để có được kết quả đầu ra mô hình:

Nhanh

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)

Mục tiêu-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; }

Sau đó, bạn có thể kết hợp đầu ra nhãn với từ điển nhãn của mình:

Nhanh

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

Mục tiêu-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);
    }
}

Mẹo để cải thiện hiệu suất thời gian thực

Nếu bạn muốn gắn nhãn hình ảnh trong ứng dụng thời gian thực, hãy làm theo các hướng dẫn sau để đạt được tốc độ khung hình tốt nhất:

  • Van tiết lưu gọi đến máy dò. Nếu khung hình video mới khả dụng trong khi trình phát hiện đang chạy, hãy bỏ khung hình đó.
  • Nếu bạn đang sử dụng đầu ra của bộ phát hiện để tạo lớp phủ đồ họa trên hình ảnh đầu vào, trước tiên hãy lấy kết quả, sau đó hiển thị hình ảnh và lớp phủ trong một bước duy nhất. Bằng cách đó, bạn chỉ hiển thị lên bề mặt hiển thị một lần cho mỗi khung hình đầu vào. Xem các lớp previewOverlayViewFIRDetectionOverlayView trong ứng dụng mẫu giới thiệu để biết ví dụ.