بعد تدريب النموذج الخاص بك باستخدام AutoML Vision Edge، يمكنك استخدامه في تطبيقك لرصد الأجسام في الصور.
هناك طريقتان لدمج النماذج التي تم تدريبها من AutoML Vision Edge. يمكنك تجميع النموذج عن طريق نسخ ملفات النموذج إلى مشروع Xcode، أو يمكنك تنزيله ديناميكيًا من Firebase.
خيارات تجميع النماذج | |
---|---|
مُضمَّنة في تطبيقك |
|
مستضافة باستخدام Firebase |
|
قبل البدء
إذا أردت تنزيل نموذج، تأكَّد من إضافة Firebase إلى مشروعك على Apple، إذا لم يسبق لك إجراء ذلك. ولا يُشترط إجراء ذلك عند تجميع النموذج.
أدرِج مكتبتَي TensorFlow وFirebase في ملف Podfile:
لتجميع نموذج مع تطبيقك:
Swift
pod 'TensorFlowLiteSwift'
Objective-C
pod 'TensorFlowLiteObjC'
لتنزيل نموذج ديناميكيًا من Firebase، أضِف التبعية
Firebase/MLModelInterpreter
:Swift
pod 'TensorFlowLiteSwift' pod 'Firebase/MLModelInterpreter'
Objective-C
pod 'TensorFlowLiteObjC' pod 'Firebase/MLModelInterpreter'
بعد تثبيت حِزم Pods في مشروعك أو تحديثها، افتح مشروع Xcode باستخدام
.xcworkspace
.
1. تحميل النموذج
ضبط مصدر نموذج على الجهاز
لتجميع النموذج مع تطبيقك، انسخ ملف النموذج والملصقات إلى مشروع Xcode، مع الحرص على اختيار إنشاء إشارات إلى المجلد عند إجراء ذلك. سيتم تضمين ملف النموذج والملصقات في حِزمة التطبيق.
اطّلِع أيضًا على ملف tflite_metadata.json
الذي تم إنشاؤه إلى جانب
النموذج. ستحتاج إلى قيمتَين:
- سمات إدخال النموذج تكون هذه القيمة 320×320 تلقائيًا.
- الحد الأقصى لعمليات رصد النموذج يكون هذا الرقم 40 تلقائيًا.
ضبط مصدر نموذج مستضاف على Firebase
لاستخدام النموذج المستضاف عن بُعد، أنشئ عنصرًا من النوع CustomRemoteModel
، مع تحديد الاسم الذي منحته للنموذج عند نشره:
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"];
بعد ذلك، ابدأ مهمة تنزيل النموذج، مع تحديد الشروط التي تريد السماح بالتنزيل بموجبها. إذا لم يكن النموذج متوفّرًا على الجهاز أو إذا كان هناك إصدار أحدث من النموذج، ستنزِّل المهمة النموذج بشكل غير متزامن من 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];
تبدأ العديد من التطبيقات مهمة التنزيل في رمز الإعداد، ولكن يمكنك إجراء ذلك في أي وقت قبل الحاجة إلى استخدام النموذج.
إنشاء أداة رصد الأجسام من النموذج
بعد ضبط مصادر النماذج، أنشئ Interpreter
عنصرًا من TensorFlow Lite من أحدها.
إذا كان لديك نموذج مجمّع على الجهاز فقط، ما عليك سوى إنشاء مترجم من ملف النموذج:
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; }
إذا كان لديك نموذج مستضاف عن بُعد، عليك التأكّد من أنّه تم
تنزيله قبل تشغيله. يمكنك التحقّق من حالة مهمة تنزيل النموذج
باستخدام طريقة isModelDownloaded(remoteModel:)
في أداة إدارة النماذج.
على الرغم من أنّه عليك تأكيد ذلك فقط قبل تشغيل المُفسِّر، إذا كان
لديك نموذج مستضاف عن بُعد ونموذج مجمّع محليًا، قد يكون
من المنطقي إجراء هذا التحقّق عند إنشاء مثيل Interpreter
: أنشئ مترجمًا
من النموذج عن بُعد إذا تم تنزيله، ومن النموذج المحلي
بخلاف ذلك.
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; }
إذا كان لديك نموذج مستضاف عن بُعد فقط، عليك إيقاف وظائفه المتعلقة بالنموذج، مثل إخفاء جزء من واجهة المستخدم أو جعله يظهر باللون الرمادي، إلى أن يتم تأكيد تنزيل النموذج.
يمكنك الحصول على حالة تنزيل النموذج من خلال إرفاق مراقبين بتطبيق "مركز الإشعارات" التلقائي. احرص على استخدام إشارة ضعيفة إلى self
في ملف
الملاحظ، لأنّ عمليات التنزيل قد تستغرق بعض الوقت، ويمكن ملف
الملاحظ تحريره بحلول وقت انتهاء التنزيل. على سبيل المثال:
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- تجهيز صورة الإدخال
بعد ذلك، عليك إعداد صورك لمُفسِّر TensorFlow Lite.
اقتَص الصورة وغيِّر حجمها لتتلاءم مع أبعاد إدخال النموذج، كما هو محدّد فيملف
tflite_metadata.json
(320×320 بكسل تلقائيًا). يمكنك إجراء ذلك باستخدام Core Image أو مكتبة تابعة لجهة خارجية.انسخ بيانات الصورة إلى
Data
(عنصر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- تشغيل أداة رصد الأجسام
بعد ذلك، عليك تمرير الإدخال المُعدّ إلى المترجم:
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. الحصول على معلومات عن الأجسام التي تم رصدها
في حال نجاح ميزة "اكتشاف الأجسام"، يُنشئ النموذج ثلاث مصفوفات تتضمّن كلّ منها 40
عنصرًا (أو أيّ عنصر تم تحديده في ملف tflite_metadata.json
).
يرتبط كل عنصر بكائن محتمل واحد. المصفوفة الأولى
هي مصفوفة من المربّعات الحدودية، والمصفوفة الثانية هي مصفوفة من التصنيفات، والمصفوفة الثالثة هي
مصفوفة من قيم الثقة. للحصول على نواتج النموذج:
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; }
بعد ذلك، يمكنك دمج نتائج التصنيفات مع قاموس التصنيفات:
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);
}
}
نصائح لتحسين الأداء في الوقت الفعلي
إذا كنت تريد تصنيف الصور في تطبيق يعمل في الوقت الفعلي، اتّبِع هذه الإرشادات لتحقيق أفضل معدّلات عرض اللقطات:
- الحد من عدد المكالمات إلى الكاشف إذا أصبح إطار فيديو جديد متاحًا أثناء تشغيل أداة رصد المحتوى، يمكنك إسقاط الإطار.
- إذا كنت تستخدِم ناتج أداة الكشف لوضع الرسومات فوق صورة الإدخال، احصل أولاً على النتيجة، ثم أعِد عرض الصورة والعنصر المتراكب في خطوة واحدة. وبذلك، يتم عرض المحتوى على سطح العرض مرّة واحدة فقط لكل إطار إدخال. يمكنك الاطّلاع على فئتَي previewOverlayView وFIRDetectionOverlayView في تطبيق "نموذج العرض" للحصول على مثال.