Catch up on everthing we announced at this year's Firebase Summit. Learn more

זיהוי אובייקטים בתמונות עם דגם מאומן AutoML בפלטפורמות של Apple

לאחר הרכבת דגם משלך באמצעות אדג חזון AutoML , אתה יכול להשתמש בו באפליקציה לזיהוי אובייקטים בתמונות.

ישנן שתי דרכים לשלב דגמים שהוכשרו מ-AutoML Vision Edge. אתה יכול לאגד את המודל על ידי העתקת קבצי המודל לפרויקט Xcode שלך, או שאתה יכול להוריד אותו באופן דינמי מ-Firebase.

אפשרויות חבילת דגמים
מאגד באפליקציה שלך
  • הדגם הוא חלק מהצרור
  • הדגם זמין באופן מיידי, גם כאשר מכשיר אפל במצב לא מקוון
  • אין צורך בפרויקט Firebase
מתארח עם Firebase
  • ארח את המודל על ידי העלאתו Firebase מכונית למידה
  • מקטין את גודל חבילת האפליקציות
  • הורדת הדגם מתבצעת לפי דרישה
  • דחף עדכוני מודל מבלי לפרסם מחדש את האפליקציה שלך
  • Easy A / B ובדיקה עם Firebase מרחוק Config
  • דורש פרויקט Firebase

לפני שאתה מתחיל

  1. אם אתה רוצה להוריד מודל, הקפד להוסיף Firebase לפרויקט האפל שלך , אם לא עשה זאת. זה לא נדרש כאשר אתה מצרף את הדגם.

  2. כלול את ספריות TensorFlow ו-Firebase ב-Podfile שלך:

    לצירוף דגם עם האפליקציה שלך:

    מָהִיר

    pod 'TensorFlowLiteSwift'
    

    Objective-C

    pod 'TensorFlowLiteObjC'
    

    בשביל להוריד מודל דינמי Firebase, להוסיף את Firebase/MLModelInterpreter תלות:

    מָהִיר

    pod 'TensorFlowLiteSwift'
    pod 'Firebase/MLModelInterpreter'
    

    Objective-C

    pod 'TensorFlowLiteObjC'
    pod 'Firebase/MLModelInterpreter'
    
  3. לאחר התקנת או לעדכן שקיקים של הפרויקט שלך, לפתוח פרויקט Xcode שלך באמצעות שלה .xcworkspace .

1. טען את הדגם

הגדר מקור דגם מקומי

כדי לקבץ את הדגם עם האפליקציה שלך, להעתיק את קובץ המודל ותוויות לפרויקט Xcode שלך, מקפיד לבחור יצירת הפניות תיקייה כאשר אתה עושה כך. קובץ הדגם והתוויות ייכללו ב-App Bundle.

כמו כן, להסתכל tflite_metadata.json הקובץ שנוצר לצד המודל. אתה צריך שני ערכים:

  • מידות הקלט של הדגם. זה 320x320 כברירת מחדל.
  • הזיהוי המקסימלי של הדגם. זה 40 כברירת מחדל.

הגדר מקור מודל שמתארח ב-Firebase

כדי להשתמש במודל מרחוק המארחת, ליצור CustomRemoteModel אובייקט, המפרט את השם שהקצית המודל כשאתה שפורסמת זה:

מָהִיר

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:

מָהִיר

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];

אפליקציות רבות מתחילות את משימת ההורדה בקוד האתחול שלהן, אך תוכל לעשות זאת בכל שלב לפני שתצטרך להשתמש במודל.

צור גלאי אובייקטים מהדגם שלך

לאחר שהגדרת מקורות המודל שלך, ליצור לייט TensorFlow Interpreter חפץ מאחד מהם.

אם יש לך רק מודל מקובץ מקומית, פשוט צור מתורגמן מקובץ המודל:

מָהִיר

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 : ליצור מתורגמן מהמודל מרחוק אם הוא הורד, ומהדגם המקומי אחרת.

מָהִיר

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 בגוש המתבונן, מאז הורדות יכולות לקחת קצת זמן, לבין אובייקט שמקורם יכול להיות משוחרר עד שההורדה הסתיים. לדוגמה:

מָהִיר

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.

  1. לחתוך את גודל התמונה לממדי הקלט של המודל, כמפורט tflite_metadata.json הקובץ (320x320 פיקסלים כברירת מחדל). אתה יכול לעשות זאת עם Core Image או ספריית צד שלישי

  2. העתק את נתוני התמונה לתוך Data ( NSData אובייקט):

    מָהִיר

    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. הפעל את גלאי האובייקטים

לאחר מכן, העבר את הקלט המוכן למתורגמן:

מָהִיר

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 קובץ) כל אחד. כל אלמנט מתאים לאובייקט פוטנציאלי אחד. המערך הראשון הוא מערך של תיבות תוחמות; השני, מערך של תוויות; והשלישי, מערך של ערכי ביטחון. כדי לקבל את פלטי הדגם:

מָהִיר

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

לאחר מכן, תוכל לשלב את פלטי התווית עם מילון התוויות שלך:

מָהִיר

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 כיתות ביישום מדגם ראווה עבור דוגמה.