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

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

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

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

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

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

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

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

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

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

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

    Swift

    import FirebaseMLModelDownloader

    Objective-C

    @import FirebaseMLModelDownloader;

עוד כמה שלבי הגדרה, והכול יהיה מוכן:

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

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

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

    3. אם ממשקי API מבוססי-ענן עדיין לא הופעלו, לוחצים על הפעלת ממשקי API מבוססי-ענן.
  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. אם לא מותקן Firebase CLI, מתקינים אותו.
  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. ההבדל בין שני תרחישי השימוש מוסבר במסמכי ה-OCR של Cloud Vision.

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