Reconoce texto en imágenes de forma segura con Cloud Vision mediante Firebase Auth y Functions en plataformas de Apple

Para llamar a una API de Google Cloud desde tu app, debes crear una API de REST intermedia que maneje la autorización y proteja los valores secretos, como las claves de API. Luego, debes escribir código en tu app para dispositivos móviles a fin de autenticarte en este servicio intermedio y comunicarte con él.

Una forma de crear esta API de REST es usar Firebase Authentication y Functions, lo que te brinda una puerta de enlace administrada y sin servidores a las APIs de Google Cloud que controla la autenticación y se puede llamar desde tu app para dispositivos móviles con SDK precompilados.

En esta guía, se muestra cómo usar esta técnica para llamar a la API de Cloud Vision desde tu app. Este método permitirá que todos los usuarios autenticados accedan a los servicios facturados de Cloud Vision a través de tu proyecto de Cloud, por lo que debes considerar si este mecanismo de autenticación es suficiente para tu caso de uso antes de continuar.

Antes de comenzar

Configura tu proyecto

Si aún no has agregado Firebase a tu app, sigue los pasos en la guía de introducción para hacerlo.

Usa Swift Package Manager para instalar y administrar las dependencias de Firebase.

  1. En Xcode, con tu proyecto de app abierto, navega a File > Add Packages.
  2. Cuando se te solicite, agrega el repositorio del SDK de Firebase para plataformas de Apple:
  3.   https://github.com/firebase/firebase-ios-sdk
  4. Elige la biblioteca de AA de Firebase.
  5. Cuando termines, Xcode comenzará a resolver y descargar automáticamente tus dependencias en segundo plano.

A continuación, realiza la configuración en la app:

  1. En tu app, importa Firebase:

    Swift

    import FirebaseMLModelDownloader

    Objective‑C

    @import FirebaseMLModelDownloader;

Quedan unos pocos pasos de configuración para terminar:

  1. Si aún no habilitaste las API de Cloud en tu proyecto, hazlo de la siguiente manera:

    1. Abre la página de APIs de AA de Firebase de Firebase console.
    2. Si todavía no actualizaste tu proyecto a un plan de precios Blaze, haz clic en Actualizar para hacerlo (se te pedirá que realices la actualización únicamente si tu proyecto no está en el plan Blaze).

      Solo los proyectos con un plan Blaze pueden usar las APIs de Cloud.

    3. Si las APIs de Cloud no están habilitadas, haz clic en Habilitar las APIs de Cloud.
  2. Configura tus claves de API de Firebase existentes para inhabilitar el acceso a la API de Cloud Vision siguiendo los pasos que se indican a continuación:
    1. Abre la página Credenciales de la consola de Cloud.
    2. Para cada clave de API de la lista, abre la vista de edición y, en la sección Restricciones de claves, agrega a la lista todas las APIs disponibles excepto la de Cloud Vision.

Implementa la función que admite llamadas

A continuación, implementa la Cloud Function que usarás para conectar tu app y la API de Cloud Vision. El repositorio functions-samples contiene un ejemplo que puedes usar.

De forma predeterminada, el acceso a la API de Cloud Vision a través de esta función permitirá que solo los usuarios autenticados de tu app accedan a la API de Cloud Vision. Puedes modificar la función para diferentes requisitos.

Sigue estos pasos para implementar la función:

  1. Clona o descarga functions-samples repo y cambia al directorio Node-1st-gen/vision-annotate-image:
    git clone https://github.com/firebase/functions-samples
    cd Node-1st-gen/vision-annotate-image
    
  2. Instala las dependencias:
    cd functions
    npm install
    cd ..
    
  3. Si no tienes Firebase CLI, instálalo.
  4. Inicializa un proyecto de Firebase en el directorio vision-annotate-image. Cuando se te solicite, selecciona tu proyecto en la lista.
    firebase init
  5. Sigue estos pasos para implementar la función:
    firebase deploy --only functions:annotateImage

Agrega Firebase Auth a tu app

La función que admite llamadas implementada anteriormente rechazará todas las solicitudes de usuarios no autenticados de tu app. Si aún no lo has hecho, tendrás que agregar Firebase Auth a tu app.

Agrega las dependencias necesarias a tu app

Usa Swift Package Manager para instalar la biblioteca de Cloud Functions para Firebase.

Ya estás listo para comenzar a reconocer texto en imágenes.

1. Prepara la imagen de entrada

Para llamar a Cloud Vision, la imagen debe tener el formato de una string codificada en base64. Para procesar una UIImage, sigue estos pasos:

Swift

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

Objective-C

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

2. Invoca la función que admite llamadas para reconocer texto

Para reconocer puntos de referencia en una imagen, invoca la función que admite llamadas pasando una solicitud de JSON de Cloud Vision.

  1. Primero, inicializa una instancia de Cloud Functions:

    Swift

    lazy var functions = Functions.functions()
    

    Objective-C

    @property(strong, nonatomic) FIRFunctions *functions;
    
  2. Crea la solicitud. La API de Cloud Vision es compatible con dos Tipos de detección de texto: TEXT_DETECTION y DOCUMENT_TEXT_DETECTION. Consulta la documentación de OCR de Cloud Vision para ver la diferencia entre los dos casos de uso.

    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. Finalmente, invoca la función:

    Swift

    functions.httpsCallable("annotateImage").call(requestData) { (result, error) in
      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]
        }
        // ...
      }
      // Function completed successfully
    }
    

    Objective‑C

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

3. Extrae texto de bloques de texto reconocido

Si la operación de reconocimiento de texto se ejecuta correctamente, se mostrará una respuesta JSON de BatchAnnotateImagesResponse en el resultado de la tarea. Las anotaciones de texto se pueden encontrar en el objeto fullTextAnnotation.

Puedes obtener el texto reconocido como una string en el campo text. Por ejemplo:

Swift

guard let annotation = (result?.data as? [String: Any])?["fullTextAnnotation"] as? [String: Any] else { return }
print("%nComplete annotation:")
let text = annotation["text"] as? String ?? ""
print("%n\(text)")

Objective-C

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

También puedes obtener información específica de las regiones de la imagen. Para cada block, paragraph, word y symbol puedes obtener el texto reconocido en la región y las coordenadas que limitan la región. Por ejemplo:

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