Sau khi huấn luyện mô hình của riêng mình bằng AutoML Vision Edge, bạn có thể sử dụng mô hình đó trong ứng dụng để phát hiện đối tượng trong hình ảnh.
Có hai cách để tích hợp các mô hình được huấn luyện 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 hoặc tải mô hình xuống một cách linh động từ Firebase.
Tuỳ chọn gói mô hình | |
---|---|
Được đóng gói trong ứng dụng |
|
Được lưu trữ bằng Firebase |
|
Trước khi bắt đầu
Nếu bạn muốn tải mô hình xuống, hãy nhớ thêm Firebase vào dự án Apple nếu bạn chưa thực hiện việc này. Bạn không cần thực hiện việc này khi gói mô hình.
Đưa thư viện TensorFlow và Firebase vào Podfile:
Cách gói một mô hình với ứng dụng:
Swift
pod 'TensorFlowLiteSwift'
Objective-C
pod 'TensorFlowLiteObjC'
Để tải mô hình xuống một cách linh động từ Firebase, hãy thêm phần phụ thuộc
Firebase/MLModelInterpreter
:Swift
pod 'TensorFlowLiteSwift' pod 'Firebase/MLModelInterpreter'
Objective-C
pod 'TensorFlowLiteObjC' pod 'Firebase/MLModelInterpreter'
Sau khi bạn cài đặt hoặc cập nhật Pods của dự án, hãy mở dự án Xcode bằng
.xcworkspace
.
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, hãy sao chép mô hình và tệp nhãn vào dự án Xcode, nhớ chọn Create folder references (Tạo tham chiếu thư mục) khi bạn thực hiện việc này. Tệp mô hình và nhãn sẽ được đưa vào 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ị:
- Phương diện đầu vào của mô hình. Kích thước này là 320x320 theo mặc định.
- Số lần phát hiện tối đa của mô hình. Giá trị này là 40 theo mặc định.
Định cấu hình nguồn mô hình do Firebase lưu trữ
Để 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 phát hành:
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"];
Sau đó, hãy bắt đầu tác vụ tải mô hình xuống, 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ó phiên bản mô hình mới hơn, thì tác vụ sẽ tải mô hình xuống không đồng bộ từ Firebase:
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];
Nhiều ứng dụng bắt đầu tác vụ tải xuống trong mã khởi chạy, nhưng bạn có thể làm như vậy bất cứ lúc nào trước khi cần sử dụng mô hình.
Tạo trình phát hiện đối tượng từ mô hình
Sau khi bạn định cấu hình nguồn mô hình, hãy tạo một đối tượng Interpreter
TensorFlow Lite từ một trong các nguồn đó.
Nếu bạn chỉ có một mô hình được đóng gói cục bộ, bạn chỉ cần tạo một trình thông dịch từ tệp mô hình:
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; }
Nếu có một mô hình được lưu trữ từ xa, bạn sẽ phải kiểm tra để đảm bảo rằng mô hình đó đã được tải xuống trước khi chạy. Bạn có thể kiểm tra trạng thái của tác vụ tải mô hình xuống 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 diễn giải, 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ộ, thì bạn nên thực hiện việc kiểm tra này khi tạo bản sao Interpreter
: tạo trình diễn giải từ mô hình từ xa nếu mô hình đó đã được tải xuống và từ mô hình cục bộ nếu không.
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; }
Nếu chỉ có mô hình được lưu trữ từ xa, bạn nên tắt chức năng liên quan đến mô hình (ví dụ: làm mờ hoặc ẩn một phần giao diện người dùng) cho đến khi xác nhận rằng mô hình đã được tải xuống.
Bạn có thể xem trạng thái tải mô hình xuống bằng cách đính kèm trình quan sát vào Trung tâm thông báo mặc định. Hãy nhớ sử dụng tệp tham chiếu yếu đến self
trong khối trình 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ụ:
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. Chuẩn bị hình ảnh đầu vào
Tiếp theo, bạn cần chuẩn bị hình ảnh cho trình diễn giải TensorFlow Lite.
Cắt và điều chỉnh 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 việc này bằng Core Image hoặc thư viện bên thứ baSao chép dữ liệu hình ảnh vào
Data
(đối tượngNSData
):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. Chạy trình phát hiện đối tượng
Tiếp theo, hãy truyền dữ liệu đầu vào đã chuẩn bị cho trình thông dịch:
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. 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 3 mảng gồm 40 phần tử (hoặc bất kỳ giá trị nào được chỉ định trong tệp tflite_metadata.json
).
Mỗi phần 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; mảng thứ hai là một mảng các nhãn; và mảng thứ ba là một mảng các giá trị tin cậy. Cách lấy kết quả của mô hình:
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; }
Sau đó, bạn có thể kết hợp kết quả nhãn với từ điển nhãn:
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);
}
}
Mẹo cải thiện hiệu suất theo thời gian thực
Nếu bạn muốn gắn nhãn hình ảnh trong một ứng dụng theo thời gian thực, hãy làm theo các nguyên tắc sau để đạt được tốc độ khung hình tốt nhất:
- Điều tiết các lệnh gọi đến trình phát hiện. Nếu có khung video mới trong khi trình phát hiện đang chạy, hãy thả khung đó.
- Nếu bạn đang sử dụng đầu ra của trình phát hiện để phủ hình ảnh đồ hoạ lên hình ảnh đầu vào, trước tiên, hãy lấy kết quả, sau đó kết xuất hình ảnh và phủ trong một bước. Bằng cách này, bạn chỉ kết xuất một lần cho mỗi khung đầu vào trên bề mặt hiển thị. Xem các lớp previewOverlayView và FIRDetectionOverlayView trong ứng dụng mẫu giới thiệu để biết ví dụ.