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

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

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

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

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

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

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

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

  1. ב-Xcode, כשהפרויקט של האפליקציה פתוח, עוברים אל File > Add Packages (קובץ > הוספת חבילות).
  2. כשמוצגת בקשה, מוסיפים את מאגר ה-SDK של Firebase לפלטפורמות של אפל:
  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 בתשלום לפי שימוש, לחצו על Upgrade כדי לעשות זאת. (תתבקשו לשדרג רק אם הפרויקט שלכם לא נמצא בתוכנית התמחור Blaze).

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

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

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

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

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

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

  1. משכפלים או מורידים את מאגר functions-samples ועוברים לספרייה 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 Auth לאפליקציה

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

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

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

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

1. הכנת התמונה שרוצים להעלות

כדי לשלוח קריאה ל-Cloud Vision, התמונה צריכה להיות בפורמט של מחרוזת בקידוד base64. כדי לעבד 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];
  }
}