使用 Apple 平台上的 Firebase 驗證和函式,透過 Cloud Vision 安全地辨識圖片中的文字

如要從應用程式呼叫 Google Cloud API,您需要建立中介 REST API,處理授權並保護 API 金鑰等密鑰值。接著,您需要在行動應用程式中編寫程式碼,以驗證及與這個中介服務通訊。

如要建立這項 REST API,其中一種方法是使用 Firebase 驗證和函式,這會提供 Google Cloud API 的代管無伺服器閘道,可處理驗證作業,並透過預先建構的 SDK 從行動應用程式呼叫。

本指南說明如何使用這項技術,從應用程式呼叫 Cloud Vision API。這個方法可讓所有通過驗證的使用者透過雲端專案存取 Cloud Vision 付費服務,因此請先評估這個驗證機制是否符合您的用途,再繼續操作。

事前準備

設定專案

如果尚未將 Firebase 新增至應用程式,請按照入門指南中的步驟操作。

使用 Swift Package Manager 安裝及管理 Firebase 依附元件。

  1. 在 Xcode 中保持開啟應用程式專案,然後依序點選「File」(檔案) 和「Add Packages」(新增 Package)
  2. 系統提示時,請新增 Firebase Apple 平台 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,請立即啟用:

    1. Firebase 控制台中開啟「Firebase ML APIs」(API) 頁面
    2. 如果尚未將專案升級至即付即用 Blaze 定價方案,請按一下「升級」。只有在專案未採用 Blaze 定價方案時,系統才會提示您升級。

      只有採用 Blaze 定價方案的專案才能使用雲端 API。

    3. 如果尚未啟用雲端 API,請按一下「啟用雲端 API」
  2. 設定現有的 Firebase API 金鑰,禁止存取 Cloud Vision API:
    1. 開啟 Cloud 控制台的「憑證」頁面。
    2. 針對清單中的每個 API 金鑰,開啟編輯檢視畫面,然後在「金鑰限制」專區中,將所有可用的 API (Cloud Vision API 除外) 新增至清單。

部署可呼叫函式

接著,部署用於連結應用程式和 Cloud Vision API 的 Cloud Function。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. vision-annotate-image 目錄中初始化 Firebase 專案。系統顯示提示訊息時,請在清單中選取您的專案。
    firebase init
  5. 部署函式:
    firebase deploy --only functions:annotateImage

將 Firebase Authentication 新增至應用程式

上述部署的可呼叫函式會拒絕來自應用程式未經驗證使用者的任何要求。如尚未完成,您需要在應用程式中新增 Firebase Authentication

在應用程式中新增必要依附元件

使用 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_DETECTIONDOCUMENT_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. 從辨識出的文字區塊擷取文字

如果文字辨識作業成功,工作結果會傳回 BatchAnnotateImagesResponse 的 JSON 回應。文字註解位於 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"]);

你也可以取得圖片特定區域的資訊。針對每個 blockparagraphwordsymbol,您可以取得該區域中辨識到的文字,以及該區域的邊界座標。例如:

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