Mengenali Teks dalam Gambar secara Aman dengan Cloud Vision menggunakan Firebase Auth dan Functions di Android

Untuk memanggil Google Cloud API dari aplikasi, Anda perlu membuat REST API perantara yang menangani otorisasi dan melindungi nilai rahasia seperti kunci API. Kemudian, Anda perlu menulis kode di aplikasi seluler untuk mengautentikasi ke dan berkomunikasi dengan layanan perantara ini.

Salah satu cara untuk membuat REST API ini adalah dengan menggunakan Firebase Authentication dan Functions, yang memberi Anda gateway serverless dan terkelola ke Google Cloud API yang menangani autentikasi dan dapat dipanggil dari aplikasi seluler Anda dengan SDK yang telah dibangun sebelumnya.

Panduan ini menunjukkan cara menggunakan teknik tersebut untuk memanggil Cloud Vision API dari aplikasi Anda. Metode ini akan mengizinkan semua pengguna terautentikasi untuk mengakses layanan Cloud Vision yang ditagih melalui project Cloud Anda. Jadi, pertimbangkan apakah mekanisme autentikasi ini sudah memadai untuk kasus penggunaan Anda sebelum melanjutkan.

Sebelum memulai

Mengonfigurasi project Anda

  1. Tambahkan Firebase ke project Android jika belum melakukannya.
  2. Jika belum mengaktifkan API berbasis Cloud untuk project, lakukan sekarang:

    1. Buka halaman API di bagian Firebase ML di Firebase console.
    2. Jika Anda belum mengupgrade project ke paket harga Blaze, klik Upgrade untuk melakukannya. (Anda akan diminta untuk mengupgrade hanya jika project tersebut tidak menggunakan paket Blaze.)

      Hanya project tingkat Blaze yang dapat menggunakan API berbasis Cloud.

    3. Jika API berbasis Cloud belum diaktifkan, klik Enable Cloud-based APIs.
  3. Konfigurasikan kunci API Firebase yang ada untuk melarang akses ke Cloud Vision API:
    1. Buka halaman Credentials di Konsol Cloud.
    2. Untuk setiap kunci API dalam daftar, buka tampilan edit. Selanjutnya di bagian Pembatasan Kunci, tambahkan semua API yang tersedia ke dalam daftar, kecuali Cloud Vision API.

Men-deploy fungsi callable

Selanjutnya, deploy Cloud Function yang akan Anda gunakan untuk menghubungkan aplikasi dan Cloud Vision API. Repositori functions-samples berisi contoh yang dapat Anda gunakan.

Secara default, mengakses Cloud Vision API melalui fungsi ini akan memberikan akses ke Cloud Vision API hanya kepada pengguna aplikasi yang telah terautentikasi. Anda dapat mengubah fungsi untuk persyaratan yang berbeda.

Untuk men-deploy fungsi tersebut:

  1. Clone atau download repo functions-samples dan ubah ke direktori Node-1st-gen/vision-annotate-image:
    git clone https://github.com/firebase/functions-samples
    cd Node-1st-gen/vision-annotate-image
    
  2. Instal dependensi:
    cd functions
    npm install
    cd ..
  3. Jika Anda tidak memiliki Firebase CLI, instal sekarang.
  4. Lakukan inisialisasi project Firebase di direktori vision-annotate-image. Saat diminta, pilih project Anda dari daftar.
    firebase init
  5. Deploy fungsi tersebut:
    firebase deploy --only functions:annotateImage

Menambahkan Firebase Auth ke aplikasi Anda

Fungsi callable yang di-deploy di atas akan menolak permintaan apa pun dari pengguna aplikasi yang tidak terautentikasi. Jika belum melakukannya, Anda harus menambahkan Firebase Auth ke aplikasi Anda.

Menambahkan dependensi yang diperlukan ke aplikasi Anda

  • Tambahkan dependensi untuk library Cloud Functions for Firebase (klien) dan Android gson ke file Gradle modul (level aplikasi) (biasanya <project>/<app-module>/build.gradle.kts atau <project>/<app-module>/build.gradle):
    implementation("com.google.firebase:firebase-functions:21.1.0")
    implementation("com.google.code.gson:gson:2.8.6")
  • Kini Anda siap untuk mulai mengenali teks dalam gambar.

    1. Persiapkan gambar input

    Untuk memanggil Cloud Vision, gambar harus diformat sebagai string berenkode base64. Untuk memproses gambar dari URI file tersimpan:
    1. Dapatkan gambar sebagai objek Bitmap:

      Kotlin+KTX

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

      Java

      Bitmap bitmap = MediaStore.Images.Media.getBitmap(getContentResolver(), uri);
    2. Jika ingin, perkecil skala gambar untuk menghemat bandwidth. Lihat ukuran gambar yang direkomendasikan Cloud Vision.

      Kotlin+KTX

      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+KTX

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

      Java

      // Scale down bitmap size
      bitmap = scaleBitmapDown(bitmap, 640);
    3. Konversi objek bitmap ke string berenkode base64:

      Kotlin+KTX

      // 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. Gambar yang diwakili oleh objek Bitmap harus berposisi tegak, tanpa perlu rotasi tambahan.

    2. Panggil fungsi callable untuk mengenali teks

    Untuk mengenali teks dalam gambar, panggil fungsi callable dengan meneruskan permintaan JSON Cloud Vision.

    1. Pertama, lakukan inisialisasi instance Cloud Functions:

      Kotlin+KTX

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

      Java

      private FirebaseFunctions mFunctions;
      // ...
      mFunctions = FirebaseFunctions.getInstance();
      
    2. Tentukan metode untuk memanggil fungsi:

      Kotlin+KTX

      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. Buat permintaan JSON. Cloud Vision API mendukung dua Jenis deteksi teks: TEXT_DETECTION dan DOCUMENT_TEXT_DETECTION. Lihat Dokumentasi Cloud Vision OCR untuk mengetahui perbedaan antara dua kasus penggunaan tersebut.

      Kotlin+KTX

      // 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);
      

      Jika ingin, berikan petunjuk bahasa untuk membantu deteksi bahasa (lihat bahasa yang didukung):

      Kotlin+KTX

      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. Terakhir, panggil fungsi tersebut:

      Kotlin+KTX

      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. Ekstrak teks dari blok teks yang dikenali

    Jika operasi pengenalan teks berhasil, respons JSON BatchAnnotateImagesResponse akan ditampilkan di hasil tugas. Anotasi teks dapat ditemukan dalam objek fullTextAnnotation.

    Anda bisa mendapatkan teks yang dikenali sebagai string di kolom text. Contoh:

    Kotlin+KTX

    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());
    

    Anda juga bisa mendapatkan informasi terkait area tertentu dalam gambar. Untuk setiap block, paragraph, word, dan symbol, Anda bisa mendapatkan teks yang dikenali di area dan koordinat pembatas area tersebut. Contoh:

    Kotlin+KTX

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