זיהוי טקסט בתמונות באופן מאובטח באמצעות Cloud Vision באמצעות 'אימות ופונקציות של Firebase' בפלטפורמות של Apple

כדי לשלוח קריאה ל-Google Cloud API מהאפליקציה, צריך ליצור אמצעי ביניים API ל-REST שמטפל בהרשאות ומגן על ערכים סודיים, כמו מפתחות API. לאחר מכן תצטרכו לכתוב קוד באפליקציה לנייד כדי לבצע אימות מול שירות הביניים הזה ולתקשר איתו.

אחת מהדרכים ליצור את ה-API ל-REST הוא באמצעות אימות ופונקציות ב-Firebase, שמאפשרים גישה מנוהלת ללא שרת (serverless) אל ממשקי API של Google Cloud שמטפלים באימות ואפשר לקרוא להם מהאפליקציה לנייד באמצעות וערכות SDK מוכנות מראש.

במדריך הזה נדגים איך להשתמש בשיטה הזו כדי לשלוח קריאה ל-Cloud Vision API מהאפליקציה שלכם. השיטה הזו תאפשר לכל המשתמשים המאומתים לגשת לשירותי החיוב של Cloud Vision דרך הפרויקט ב-Cloud, וכך לפני שממשיכים, צריך לבדוק אם מנגנון האימות הזה מספיק לתרחיש לדוגמה שלכם.

לפני שמתחילים

הגדרת הפרויקט

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

אפשר להשתמש ב-Swift Package Manager כדי להתקין ולנהל יחסי תלות של Firebase.

  1. ב-Xcode, כשפרויקט האפליקציה פתוח, עוברים אל קובץ > הוספת חבילות.
  2. כשמופיעה בקשה, מוסיפים את המאגר של Firebase SDK לפלטפורמות של Apple:
  3.   https://github.com/firebase/firebase-ios-sdk.git
  4. בוחרים את הספרייה Firebase ML.
  5. מוסיפים את הדגל -ObjC לקטע Other Linker Flags (דגלים אחרים של קישור) בהגדרות ה-build של היעד.
  6. בסיום, Xcode יתחיל לפתור את הבעיה ותוריד את של יחסי התלות ברקע.

עכשיו מבצעים הגדרה בתוך האפליקציה:

  1. מייבאים את Firebase לאפליקציה:

    Swift

    import FirebaseMLModelDownloader

    Objective-C

    @import FirebaseMLModelDownloader;

עוד כמה שלבים בהגדרה, ואנחנו מוכנים:

  1. אם עדיין לא הפעלתם ממשקי API מבוססי-ענן בפרויקט שלכם, אתם צריכים לעשות זאת עכשיו:

    1. פותחים את דף ממשקי ה-API של Firebase ML במסוף Firebase.
    2. אם עדיין לא שדרגתם את הפרויקט לתוכנית התמחור והתשלומים של Blaze, לוחצים על כדי לעשות זאת, אפשר לשדרג. (תתבקש לשדרג רק אם הוא לא בתוכנית Blaze.)

      רק בפרויקטים ברמת Blaze אפשר להשתמש בממשקי API מבוססי-ענן.

    3. אם ממשקי ה-API מבוססי-הענן עדיין לא מופעלים, לוחצים על Enable Cloud-based APIs.
  2. מגדירים את מפתחות ה-API הקיימים של Firebase כך שלא יאפשרו גישה ל-Cloud Vision API:
    1. פותחים את הדף Credentials במסוף Cloud.
    2. עבור כל מפתח API ברשימה, פותחים את תצוגת העריכה ובקטע 'מפתח' בקטע 'הגבלות', מוסיפים את כל ממשקי ה-API הזמינים מלבד Cloud Vision API אל הרשימה.

פריסת הפונקציה שניתן להפעיל

בשלב הבא, פורסים את הפונקציה של Cloud Functions שתשמש לגשר בין האפליקציה לבין Cloud Vision API המאגר functions-samples מכיל דוגמה שאפשר להשתמש בהם.

כברירת מחדל, הגישה ל-Cloud Vision API דרך הפונקציה הזו תאפשר רק משתמשים מאומתים של האפליקציה שלכם יכולים לגשת ל-Cloud Vision API. אפשר לשנות את הפונקציה לדרישות שונות.

כדי לפרוס את הפונקציה:

  1. שכפול או הורדה של מאגר פונקציות-דוגמאות ומשנים לספרייה Node-1st-gen/vision-annotate-image:
    git clone https://github.com/firebase/functions-samples
    cd Node-1st-gen/vision-annotate-image
    
  2. יחסי תלות של התקנות:
    cd functions
    npm install
    cd ..
  3. אם ה-CLI של Firebase לא מותקן, מתקינים אותו.
  4. מאתחלים פרויקט Firebase בספרייה vision-annotate-image. כשמופיעה בקשה, בוחרים את הפרויקט מהרשימה.
    firebase init
  5. פורסים את הפונקציה:
    firebase deploy --only functions:annotateImage

הוספת אימות מ-Firebase לאפליקציה

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

הוספת יחסי התלות הנדרשים לאפליקציה

כדי להתקין את הספרייה Cloud Functions for Firebase, משתמשים ב-Swift Package Manager.

עכשיו אתם מוכנים להתחיל לזהות טקסט בתמונות.

1. הכנת תמונת הקלט

כדי לקרוא ל-Cloud Vision, התמונה צריכה להיות בפורמט של קידוד base64 String. כדי לעבד UIImage:

Swift

guard let imageData = uiImage.jpegData(compressionQuality: 1.0) else { return }
let base64encodedImage = imageData.base64EncodedString()

Objective-C

NSData *imageData = UIImageJPEGRepresentation(uiImage, 1.0f);
NSString *base64encodedImage =
  [imageData base64EncodedStringWithOptions:NSDataBase64Encoding76CharacterLineLength];

2. הפעלת הפונקציה הניתנת לקריאה כדי לזהות טקסט

כדי לזהות ציוני דרך בתמונה, מפעילים את הפונקציה הניתנת לקריאה ומעבירים בקשת JSON ל-Cloud Vision.

  1. קודם צריך לאתחל מכונה של Cloud Functions:

    Swift

    lazy var functions = Functions.functions()
    

    Objective-C

    @property(strong, nonatomic) FIRFunctions *functions;
    
  2. יוצרים את הבקשה. Cloud Vision API תומך בשני סוגים של זיהוי טקסט: TEXT_DETECTION ו-DOCUMENT_TEXT_DETECTION. עיון במסמכי התיעוד של Cloud Vision OCR לגבי ההבדל בין שני התרחישים לדוגמה.

    Swift

    let requestData = [
      "image": ["content": base64encodedImage],
      "features": ["type": "TEXT_DETECTION"],
      "imageContext": ["languageHints": ["en"]]
    ]
    

    Objective-C

    NSDictionary *requestData = @{
      @"image": @{@"content": base64encodedImage},
      @"features": @{@"type": @"TEXT_DETECTION"},
      @"imageContext": @{@"languageHints": @[@"en"]}
    };
    
  3. לבסוף, מפעילים את הפונקציה:

    Swift

    do {
      let result = try await functions.httpsCallable("annotateImage").call(requestData)
      print(result)
    } catch {
      if let error = error as NSError? {
        if error.domain == FunctionsErrorDomain {
          let code = FunctionsErrorCode(rawValue: error.code)
          let message = error.localizedDescription
          let details = error.userInfo[FunctionsErrorDetailsKey]
        }
        // ...
      }
    }
    

    Objective-C

    [[_functions HTTPSCallableWithName:@"annotateImage"]
                              callWithObject:requestData
                                  completion:^(FIRHTTPSCallableResult * _Nullable result, NSError * _Nullable error) {
            if (error) {
              if ([error.domain isEqualToString:@"com.firebase.functions"]) {
                FIRFunctionsErrorCode code = error.code;
                NSString *message = error.localizedDescription;
                NSObject *details = error.userInfo[@"details"];
              }
              // ...
            }
            // Function completed succesfully
            // Get information about labeled objects
    
          }];
    

3. חילוץ טקסט מקטעי טקסט מזוהים

אם פעולת זיהוי הטקסט מצליחה, תגובת JSON של BatchAnnotateImagesResponse יוחזר בתוצאה של המשימה. הערות הטקסט נמצאות אובייקט fullTextAnnotation.

הטקסט המזוהה יכול להופיע כמחרוזת בשדה text. לדוגמה:

Swift

let annotation = result.flatMap { $0.data as? [String: Any] }
    .flatMap { $0["fullTextAnnotation"] }
    .flatMap { $0 as? [String: Any] }
guard let annotation = annotation else { return }

if let text = annotation["text"] as? String {
  print("Complete annotation: \(text)")
}

Objective-C

NSDictionary *annotation = result.data[@"fullTextAnnotation"];
if (!annotation) { return; }
NSLog(@"\nComplete annotation:");
NSLog(@"\n%@", annotation[@"text"]);

אפשר גם לקבל מידע ספציפי לגבי אזורים בתמונה. לכל block, paragraph, word וגם symbol, אפשר לקבל את הטקסט שמזוהה באזור ואת הקואורדינטות התוחמות של האזור. לדוגמה:

Swift

guard let pages = annotation["pages"] as? [[String: Any]] else { return }
for page in pages {
  var pageText = ""
  guard let blocks = page["blocks"] as? [[String: Any]] else { continue }
  for block in blocks {
    var blockText = ""
    guard let paragraphs = block["paragraphs"] as? [[String: Any]] else { continue }
    for paragraph in paragraphs {
      var paragraphText = ""
      guard let words = paragraph["words"] as? [[String: Any]] else { continue }
      for word in words {
        var wordText = ""
        guard let symbols = word["symbols"] as? [[String: Any]] else { continue }
        for symbol in symbols {
          let text = symbol["text"] as? String ?? ""
          let confidence = symbol["confidence"] as? Float ?? 0.0
          wordText += text
          print("Symbol text: \(text) (confidence: \(confidence)%n")
        }
        let confidence = word["confidence"] as? Float ?? 0.0
        print("Word text: \(wordText) (confidence: \(confidence)%n%n")
        let boundingBox = word["boundingBox"] as? [Float] ?? [0.0, 0.0, 0.0, 0.0]
        print("Word bounding box: \(boundingBox.description)%n")
        paragraphText += wordText
      }
      print("%nParagraph: %n\(paragraphText)%n")
      let boundingBox = paragraph["boundingBox"] as? [Float] ?? [0.0, 0.0, 0.0, 0.0]
      print("Paragraph bounding box: \(boundingBox)%n")
      let confidence = paragraph["confidence"] as? Float ?? 0.0
      print("Paragraph Confidence: \(confidence)%n")
      blockText += paragraphText
    }
    pageText += blockText
  }
}

Objective-C

for (NSDictionary *page in annotation[@"pages"]) {
  NSMutableString *pageText = [NSMutableString new];
  for (NSDictionary *block in page[@"blocks"]) {
    NSMutableString *blockText = [NSMutableString new];
    for (NSDictionary *paragraph in block[@"paragraphs"]) {
      NSMutableString *paragraphText = [NSMutableString new];
      for (NSDictionary *word in paragraph[@"words"]) {
        NSMutableString *wordText = [NSMutableString new];
        for (NSDictionary *symbol in word[@"symbols"]) {
          NSString *text = symbol[@"text"];
          [wordText appendString:text];
          NSLog(@"Symbol text: %@ (confidence: %@\n", text, symbol[@"confidence"]);
        }
        NSLog(@"Word text: %@ (confidence: %@\n\n", wordText, word[@"confidence"]);
        NSLog(@"Word bounding box: %@\n", word[@"boundingBox"]);
        [paragraphText appendString:wordText];
      }
      NSLog(@"\nParagraph: \n%@\n", paragraphText);
      NSLog(@"Paragraph bounding box: %@\n", paragraph[@"boundingBox"]);
      NSLog(@"Paragraph Confidence: %@\n", paragraph[@"confidence"]);
      [blockText appendString:paragraphText];
    }
    [pageText appendString:blockText];
  }
}