在 Apple 平台上使用 Firebase Auth 和 Firebase Functions 通过 Cloud Vision 安全地识别图片中的文本

如需从应用中调用 Google Cloud API,您需要创建一个中间 REST API 来处理授权并保护 API 密钥等密钥值。然后,您需要在移动应用中编写代码,用于向此中间服务进行身份验证并与其通信。

您可以使用 Firebase Authentication 和 Firebase Functions 来创建此 REST API,这样您便有了一个连接到 Google Cloud API 的代管式无服务器网关来处理身份验证,而且您可以通过预构建的 SDK 从自己的移动应用中调用此网关。

本指南演示了如何使用此方法从应用中调用 Cloud Vision API。此方法将允许所有经过身份验证的用户通过您的 Cloud 项目访问 Cloud Vision 收费服务,因此请考虑这种身份验证机制是否满足您的使用场景,然后再继续操作。

准备工作

配置您的项目

如果您尚未将 Firebase 添加到自己的应用中,请按照入门指南中的步骤进行添加。

使用 Swift Package Manager 安装和管理 Firebase 依赖项。

  1. 在 Xcode 中打开您的应用项目,依次点击 File(文件)> Add Packages(添加软件包)
  2. 出现提示时,添加 Firebase Apple 平台 SDK 代码库:
  3.   https://github.com/firebase/firebase-ios-sdk.git
  4. 选择 Firebase ML 库。
  5. -ObjC 标志添加到目标 build 设置的“其他链接器标志”部分。
  6. 完成之后,Xcode 将会自动开始在后台解析和下载您的依赖项。

接下来,执行一些应用内设置:

  1. 在您的应用中导入 Firebase:

    Swift

    import FirebaseMLModelDownloader

    Objective-C

    @import FirebaseMLModelDownloader;

只需要再完成几个配置步骤,就可以开始使用了:

  1. 如果您尚未为项目启用云端 API,请立即完成以下操作:

    1. 打开 Firebase 控制台的 Firebase ML API 页面
    2. 如果您尚未将项目升级到 Blaze 定价方案,请点击升级以执行此操作。(只有在您的项目未采用 Blaze 方案时,系统才会提示您进行升级。)

      只有 Blaze 级项目才能使用基于 Cloud 的 API。

    3. 如果尚未启用基于 Cloud 的 API,请点击启用基于 Cloud 的 API
  2. 配置您现有的 Firebase API 密钥以禁止访问 Cloud Vision API:
    1. 打开 Cloud 控制台中的凭据页面。
    2. 对于列表中的每个 API 密钥,打开修改视图,然后在“密钥限制”部分中,向列表中添加除了 Cloud Vision API 之外的所有可用 API

部署 Callable 函数

接下来,部署将用于衔接您的应用与 Cloud Vision API 的 Cloud Functions 函数。functions-samples 代码库包含一个您可以使用的示例。

默认情况下,此函数将仅允许通过身份验证的应用用户访问 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 Auth 添加到您的应用

上面部署的 Callable 函数将拒绝未经身份验证的应用用户的任何请求。如果您尚未将 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. 调用 Callable 函数来识别文本

如需识别图片中的地标,请调用传递 JSON Cloud Vision 请求的 Callable 函数。

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