Riconosci il testo nelle immagini in modo sicuro con Cloud Vision utilizzando Firebase Auth e Functions su Android

Per chiamare un'API Google Cloud dalla tua app, devi creare un'API REST intermedia che gestisca l'autorizzazione e protegga i valori secret come le chiavi API. Dopodiché, devi scrivere il codice nella tua app mobile per l'autenticazione e la comunicazione con questo servizio intermedio.

Un modo per creare questa API REST è utilizzare Firebase Authentication e Functions, che ti offre un gateway serverless gestito per le API Cloud che gestisce l'autenticazione e può essere chiamato dalla tua app mobile con SDK predefiniti.

Questa guida mostra come utilizzare questa tecnica per chiamare l'API Cloud Vision dalla tua app. Questo metodo consentirà a tutti gli utenti autenticati di accedere ai servizi Cloud Vision fatturati tramite il tuo progetto Cloud, quindi valuta se questo meccanismo di autenticazione è sufficiente per il tuo caso d'uso prima di procedere.

Prima di iniziare

Configura il tuo progetto

  1. Se non l'hai ancora fatto, aggiungi Firebase al tuo progetto Android.
  2. Se non hai ancora abilitato le API basate sul cloud per il tuo progetto, fallo ora:

    1. Apri la pagina Firebase ML API nella console Firebase.
    2. Se non hai ancora eseguito l'upgrade del progetto al piano tariffario Blaze con pagamento a consumo, fai clic su Esegui upgrade. (Ti verrà chiesto di eseguire l'upgrade solo se il tuo progetto non è incluso nel piano tariffario Blaze.)

      Solo i progetti con il piano tariffario Blaze possono utilizzare API basate sul cloud.

    3. Se le API basate sul cloud non sono già abilitate, fai clic su Abilita API basate sul cloud.
  3. Configura le chiavi API Firebase esistenti per impedire l'accesso all'API Cloud Vision:
    1. Apri la pagina Credenziali della console Cloud.
    2. Per ogni chiave API nell'elenco, apri la visualizzazione di modifica e nella sezione Key Restrictions (Limitazioni chiave), aggiungi all'elenco tutte le API disponibili tranne l'API Cloud Vision.

Esegui il deployment della funzione richiamabile

Dopodiché, esegui il deployment della funzione Cloud che utilizzerai per collegare la tua app e l'API Cloud Vision. Il repository functions-samples contiene un esempio che puoi utilizzare.

Per impostazione predefinita, l'accesso all'API Cloud Vision tramite questa funzione consentirà solo agli utenti autenticati della tua app di accedere all'API Cloud Vision. Puoi modificare la funzione in base a requisiti diversi.

Per eseguire il deployment della funzione:

  1. Clona o scarica il repository functions-samples e passa alla directory Node-1st-gen/vision-annotate-image:
    git clone https://github.com/firebase/functions-samples
    cd Node-1st-gen/vision-annotate-image
    
  2. Installa le dipendenze:
    cd functions
    npm install
    cd ..
  3. Se non hai l'interfaccia a riga di comando di Firebase, installala.
  4. Inizializza un progetto Firebase nella directory vision-annotate-image. Quando richiesto, seleziona il progetto nell'elenco.
    firebase init
  5. Esegui il deployment della funzione:
    firebase deploy --only functions:annotateImage

Aggiungere Firebase Auth alla tua app

La funzione chiamabile di cui è stato eseguito il deployment sopra rifiuterà qualsiasi richiesta da utenti non autenticati della tua app. Se non l'hai ancora fatto, devi aggiungere Firebase Auth alla tua app.

Aggiungere le dipendenze necessarie alla tua app

  • Aggiungi le dipendenze per le librerie Android Cloud Functions for Firebase (client) e gson al file Gradle (a livello di app) del modulo (solitamente <project>/<app-module>/build.gradle.kts o <project>/<app-module>/build.gradle):
    implementation("com.google.firebase:firebase-functions:22.1.0")
    implementation("com.google.code.gson:gson:2.8.6")
  • Ora puoi iniziare a riconoscere il testo nelle immagini.

    1. Prepara l'immagine di input

    Per chiamare Cloud Vision, l'immagine deve essere formattata come stringa con codifica base64. Per elaborare un'immagine da un URI di file salvato:
    1. Recupera l'immagine come oggetto Bitmap:

      Kotlin

      var bitmap: Bitmap = MediaStore.Images.Media.getBitmap(contentResolver, uri)

      Java

      Bitmap bitmap = MediaStore.Images.Media.getBitmap(getContentResolver(), uri);
    2. Se vuoi, ridimensiona l'immagine per risparmiare larghezza di banda. Consulta le dimensioni delle immagini consigliate per Cloud Vision.

      Kotlin

      private fun scaleBitmapDown(bitmap: Bitmap, maxDimension: Int): Bitmap {
          val originalWidth = bitmap.width
          val originalHeight = bitmap.height
          var resizedWidth = maxDimension
          var resizedHeight = maxDimension
          if (originalHeight > originalWidth) {
              resizedHeight = maxDimension
              resizedWidth =
                  (resizedHeight * originalWidth.toFloat() / originalHeight.toFloat()).toInt()
          } else if (originalWidth > originalHeight) {
              resizedWidth = maxDimension
              resizedHeight =
                  (resizedWidth * originalHeight.toFloat() / originalWidth.toFloat()).toInt()
          } else if (originalHeight == originalWidth) {
              resizedHeight = maxDimension
              resizedWidth = maxDimension
          }
          return Bitmap.createScaledBitmap(bitmap, resizedWidth, resizedHeight, false)
      }

      Java

      private Bitmap scaleBitmapDown(Bitmap bitmap, int maxDimension) {
          int originalWidth = bitmap.getWidth();
          int originalHeight = bitmap.getHeight();
          int resizedWidth = maxDimension;
          int resizedHeight = maxDimension;
      
          if (originalHeight > originalWidth) {
              resizedHeight = maxDimension;
              resizedWidth = (int) (resizedHeight * (float) originalWidth / (float) originalHeight);
          } else if (originalWidth > originalHeight) {
              resizedWidth = maxDimension;
              resizedHeight = (int) (resizedWidth * (float) originalHeight / (float) originalWidth);
          } else if (originalHeight == originalWidth) {
              resizedHeight = maxDimension;
              resizedWidth = maxDimension;
          }
          return Bitmap.createScaledBitmap(bitmap, resizedWidth, resizedHeight, false);
      }

      Kotlin

      // Scale down bitmap size
      bitmap = scaleBitmapDown(bitmap, 640)

      Java

      // Scale down bitmap size
      bitmap = scaleBitmapDown(bitmap, 640);
    3. Converti l'oggetto bitmap in una stringa con codifica base64:

      Kotlin

      // Convert bitmap to base64 encoded string
      val byteArrayOutputStream = ByteArrayOutputStream()
      bitmap.compress(Bitmap.CompressFormat.JPEG, 100, byteArrayOutputStream)
      val imageBytes: ByteArray = byteArrayOutputStream.toByteArray()
      val base64encoded = Base64.encodeToString(imageBytes, Base64.NO_WRAP)

      Java

      // Convert bitmap to base64 encoded string
      ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
      bitmap.compress(Bitmap.CompressFormat.JPEG, 100, byteArrayOutputStream);
      byte[] imageBytes = byteArrayOutputStream.toByteArray();
      String base64encoded = Base64.encodeToString(imageBytes, Base64.NO_WRAP);
    4. L'immagine rappresentata dall'oggetto Bitmap deve essere verticale, senza richiedere rotazioni aggiuntive.

    2. Richiama la funzione richiamabile per riconoscere il testo

    Per riconoscere il testo in un'immagine, richiama la funzione chiamabile passando una richiesta JSON Cloud Vision.

    1. Innanzitutto, inizializza un'istanza di Cloud Functions:

      Kotlin

      private lateinit var functions: FirebaseFunctions
      // ...
      functions = Firebase.functions
      

      Java

      private FirebaseFunctions mFunctions;
      // ...
      mFunctions = FirebaseFunctions.getInstance();
      
    2. Definisci un metodo per richiamare la funzione:

      Kotlin

      private fun annotateImage(requestJson: String): Task<JsonElement> {
          return functions
              .getHttpsCallable("annotateImage")
              .call(requestJson)
              .continueWith { task ->
                  // This continuation runs on either success or failure, but if the task
                  // has failed then result will throw an Exception which will be
                  // propagated down.
                  val result = task.result?.data
                  JsonParser.parseString(Gson().toJson(result))
              }
      }
      

      Java

      private Task<JsonElement> annotateImage(String requestJson) {
          return mFunctions
                  .getHttpsCallable("annotateImage")
                  .call(requestJson)
                  .continueWith(new Continuation<HttpsCallableResult, JsonElement>() {
                      @Override
                      public JsonElement then(@NonNull Task<HttpsCallableResult> task) {
                          // This continuation runs on either success or failure, but if the task
                          // has failed then getResult() will throw an Exception which will be
                          // propagated down.
                          return JsonParser.parseString(new Gson().toJson(task.getResult().getData()));
                      }
                  });
      }
      
    3. Crea la richiesta JSON. L'API Cloud Vision supporta due tipi di rilevamento del testo: TEXT_DETECTION e DOCUMENT_TEXT_DETECTION. Consulta la documentazione sull'OCR di Cloud Vision per conoscere la differenza tra i due casi d'uso.

      Kotlin

      // Create json request to cloud vision
      val request = JsonObject()
      // Add image to request
      val image = JsonObject()
      image.add("content", JsonPrimitive(base64encoded))
      request.add("image", image)
      // Add features to the request
      val feature = JsonObject()
      feature.add("type", JsonPrimitive("TEXT_DETECTION"))
      // Alternatively, for DOCUMENT_TEXT_DETECTION:
      // feature.add("type", JsonPrimitive("DOCUMENT_TEXT_DETECTION"))
      val features = JsonArray()
      features.add(feature)
      request.add("features", features)
      

      Java

      // Create json request to cloud vision
      JsonObject request = new JsonObject();
      // Add image to request
      JsonObject image = new JsonObject();
      image.add("content", new JsonPrimitive(base64encoded));
      request.add("image", image);
      //Add features to the request
      JsonObject feature = new JsonObject();
      feature.add("type", new JsonPrimitive("TEXT_DETECTION"));
      // Alternatively, for DOCUMENT_TEXT_DETECTION:
      //feature.add("type", new JsonPrimitive("DOCUMENT_TEXT_DETECTION"));
      JsonArray features = new JsonArray();
      features.add(feature);
      request.add("features", features);
      

      Se vuoi, fornisci suggerimenti sulla lingua per facilitare il rilevamento della lingua (vedi lingue supportate):

      Kotlin

      val imageContext = JsonObject()
      val languageHints = JsonArray()
      languageHints.add("en")
      imageContext.add("languageHints", languageHints)
      request.add("imageContext", imageContext)
      

      Java

      JsonObject imageContext = new JsonObject();
      JsonArray languageHints = new JsonArray();
      languageHints.add("en");
      imageContext.add("languageHints", languageHints);
      request.add("imageContext", imageContext);
      
    4. Infine, richiama la funzione:

      Kotlin

      annotateImage(request.toString())
          .addOnCompleteListener { task ->
              if (!task.isSuccessful) {
                  // Task failed with an exception
                  // ...
              } else {
                  // Task completed successfully
                  // ...
              }
          }
      

      Java

      annotateImage(request.toString())
              .addOnCompleteListener(new OnCompleteListener<JsonElement>() {
                  @Override
                  public void onComplete(@NonNull Task<JsonElement> task) {
                      if (!task.isSuccessful()) {
                          // Task failed with an exception
                          // ...
                      } else {
                          // Task completed successfully
                          // ...
                      }
                  }
              });
      

    3. Estrarre testo da blocchi di testo riconosciuto

    Se l'operazione di riconoscimento del testo va a buon fine, nel risultato dell'attività viene restituita una risposta JSON di BatchAnnotateImagesResponse. Le annotazioni di testo si trovano nell'oggetto fullTextAnnotation.

    Puoi ottenere il testo riconosciuto come stringa nel campo text. Ad esempio:

    Kotlin

    val annotation = task.result!!.asJsonArray[0].asJsonObject["fullTextAnnotation"].asJsonObject
    System.out.format("%nComplete annotation:")
    System.out.format("%n%s", annotation["text"].asString)
    

    Java

    JsonObject annotation = task.getResult().getAsJsonArray().get(0).getAsJsonObject().get("fullTextAnnotation").getAsJsonObject();
    System.out.format("%nComplete annotation:%n");
    System.out.format("%s%n", annotation.get("text").getAsString());
    

    Puoi anche ottenere informazioni specifiche per le regioni dell'immagine. Per ogni block, paragraph, word e symbol, puoi ottenere il testo riconosciuto nella regione e le coordinate del rettangolo di selezione della regione. Ad esempio:

    Kotlin

    for (page in annotation["pages"].asJsonArray) {
        var pageText = ""
        for (block in page.asJsonObject["blocks"].asJsonArray) {
            var blockText = ""
            for (para in block.asJsonObject["paragraphs"].asJsonArray) {
                var paraText = ""
                for (word in para.asJsonObject["words"].asJsonArray) {
                    var wordText = ""
                    for (symbol in word.asJsonObject["symbols"].asJsonArray) {
                        wordText += symbol.asJsonObject["text"].asString
                        System.out.format(
                            "Symbol text: %s (confidence: %f)%n",
                            symbol.asJsonObject["text"].asString,
                            symbol.asJsonObject["confidence"].asFloat,
                        )
                    }
                    System.out.format(
                        "Word text: %s (confidence: %f)%n%n",
                        wordText,
                        word.asJsonObject["confidence"].asFloat,
                    )
                    System.out.format("Word bounding box: %s%n", word.asJsonObject["boundingBox"])
                    paraText = String.format("%s%s ", paraText, wordText)
                }
                System.out.format("%nParagraph: %n%s%n", paraText)
                System.out.format("Paragraph bounding box: %s%n", para.asJsonObject["boundingBox"])
                System.out.format("Paragraph Confidence: %f%n", para.asJsonObject["confidence"].asFloat)
                blockText += paraText
            }
            pageText += blockText
        }
    }
    

    Java

    for (JsonElement page : annotation.get("pages").getAsJsonArray()) {
        StringBuilder pageText = new StringBuilder();
        for (JsonElement block : page.getAsJsonObject().get("blocks").getAsJsonArray()) {
            StringBuilder blockText = new StringBuilder();
            for (JsonElement para : block.getAsJsonObject().get("paragraphs").getAsJsonArray()) {
                StringBuilder paraText = new StringBuilder();
                for (JsonElement word : para.getAsJsonObject().get("words").getAsJsonArray()) {
                    StringBuilder wordText = new StringBuilder();
                    for (JsonElement symbol : word.getAsJsonObject().get("symbols").getAsJsonArray()) {
                        wordText.append(symbol.getAsJsonObject().get("text").getAsString());
                        System.out.format("Symbol text: %s (confidence: %f)%n", symbol.getAsJsonObject().get("text").getAsString(), symbol.getAsJsonObject().get("confidence").getAsFloat());
                    }
                    System.out.format("Word text: %s (confidence: %f)%n%n", wordText.toString(), word.getAsJsonObject().get("confidence").getAsFloat());
                    System.out.format("Word bounding box: %s%n", word.getAsJsonObject().get("boundingBox"));
                    paraText.append(wordText.toString()).append(" ");
                }
                System.out.format("%nParagraph:%n%s%n", paraText);
                System.out.format("Paragraph bounding box: %s%n", para.getAsJsonObject().get("boundingBox"));
                System.out.format("Paragraph Confidence: %f%n", para.getAsJsonObject().get("confidence").getAsFloat());
                blockText.append(paraText);
            }
            pageText.append(blockText);
        }
    }