Sau khi bạn huấn luyện mô hình của mình bằng AutoML Vision Edge, bạn có thể dùng mã này 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 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 bạn có thể tự động tải ứng dụng đó xuống từ Firebase.
Tuỳ chọn nhóm mô hình | |
---|---|
Được tích hợp trong ứng dụng của bạn |
|
Được lưu trữ bằng Firebase |
|
Trước khi bắt đầu
Nếu bạn muốn tải một mô hình xuống, hãy đảm bảo bạn thêm Firebase vào dự án Apple của bạn, nếu bạn chưa làm như vậy. Điều này không bắt buộc khi bạn nhóm mô hình.
Đưa thư viện TensorFlow và Firebase vào Podfile của bạn:
Để nhóm mô hình với ứng dụng của bạn:
Swift
pod 'TensorFlowLiteSwift'
Objective-C
pod 'TensorFlowLiteObjC'
Để tự động tải một mô hình xuống từ Firebase, hãy thêm phương thức 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 Nhóm của dự án, hãy mở dự án Xcode của bạn bằng cách sử dụng
.xcworkspace
.
1. Tải mô hình
Định cấu hình nguồn mô hình cục bộ
Cách đóng gói mô hình với ứng dụng: sao chép tệp mô hình và nhãn vào dự án Xcode của bạn, hãy chú ý chọn Tạo tệp tham chiếu thư mục khi bạn làm như vậ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ị:
- Các phương diện đầu vào của mô hình. Kích thước mặc định là 320x320.
- Số lần phát hiện tối đa của mô hình. Theo mặc định, giá trị này là 40.
Đị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 CustomRemoteModel
, chỉ định tên mà bạn đã chỉ định cho mô hình khi xuất bản:
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, xác định các điều kiện mà bạn muốn cho phép tải xuống. Nếu kiểu máy này không có trên thiết bị hoặc nếu là kiểu máy mới hơn phiên bản của mô hình sẵn có, tác vụ sẽ tải xuống không đồng bộ mô hình 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 của bạn
Sau khi định cấu hình các nguồn mô hình, hãy tạo một Interpreter
TensorFlow Lite
khỏi một trong số chúng.
Nếu bạn chỉ có mô hình được gói cục bộ, chỉ cần tạo 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ô hình được lưu trữ từ xa, bạn sẽ phải kiểm tra xem mô hình đó đã được
tải xuống trước khi chạy nó. Bạn có thể kiểm tra trạng thái tải mô hình xuống
bằng cách sử dụ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 phiên dịch, nếu bạn
có cả mô hình được lưu trữ từ xa và mô hình được gói cục bộ,
ý nghĩa để thực hiện bước kiểm tra này khi tạo thực thể cho Interpreter
: tạo một
từ mô hình từ xa nếu mẫu đã đượ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ột mô hình được lưu trữ từ xa, bạn nên tắt tính năng liên quan đến mô hình đó chức năng (ví dụ: chuyển sang màu xám hoặc ẩn một phần giao diện người dùng) cho đến khi bạn xác nhận mô hình đã được tải xuống.
Bạn có thể xem trạng thái tải xuống mô hình bằng cách đính kèm đối tượng tiếp nhận dữ liệu vào giá trị mặc định
Trung tâm thông báo. Hãy nhớ sử dụng tệp tham chiếu yếu đến self
trong 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 gốc có thể
được giải phóng vào thời điểm hoàn tất tải xuống. 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 của mình cho trình thông dịch 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 được với Core Image hoặc thư viện của 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 nội dung đầu vào đã chuẩn bị cho người phiên 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. Lấy thông tin về đố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 40
(hoặc bất kỳ phần tử nào được chỉ định trong tệp tflite_metadata.json
) cho mỗi phần tử.
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; thứ hai là một mảng các nhãn; và phương thức thứ ba là
mảng giá trị tin cậy. Cách nhận dữ liệu đầu ra 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 dữ liệu đầu ra của 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 cho hình ảnh trong một ứng dụng theo thời gian thực, hãy làm theo các bước sau để đạt được tốc độ khung hình tốt nhất:
- Điều tiết lệnh gọi đến trình phát hiện. Nếu một khung video mới trong khi trình phát hiện đang chạy, hãy thả khung hình.
- Nếu bạn đang sử dụng đầu ra của trình phát hiện để phủ đồ 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ủ lên trên trong một bước duy nhất. Nhờ vậy, bạn sẽ kết xuất lên giao diện màn hình một lần cho mỗi khung đầu vào. Xem previewOverlayView và FIRDetectionOverlayView trong ứng dụng mẫu Showcase.