Google berkomitmen untuk mendorong terwujudnya keadilan ras bagi komunitas Kulit Hitam. Lihat caranya.

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 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 tanpa server dan terkelola ke Google Cloud API yang menangani autentikasi dan dapat dipanggil dari aplikasi seluler Anda dengan SDK yang telah dibuat 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 yang ditagih Cloud Vision 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 Anda, lakukan sekarang:

    1. Buka halaman Firebase ML API 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 tidak menggunakan paket Blaze.)

      Hanya project tingkat Blaze yang dapat menggunakan API berbasis Cloud.

    3. Jika API berbasis Cloud belum diaktifkan, klik Aktifkan API berbasis Cloud.
  3. Konfigurasikan kunci API Firebase yang ada untuk melarang akses ke Cloud Vision API:
    1. Buka halaman Kredensial pada Cloud Console.
    2. Untuk setiap kunci API dalam daftar, buka tampilan edit, dan 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 vision-annotate-image:
    git clone https://github.com/firebase/functions-samples
    cd 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. Jika diminta, pilih project Anda dalam daftar.
    firebase init
  5. Deploy fungsinya:
    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 Firebase Functions dan Android gson ke file Gradle modul (level aplikasi) (biasanya app/build.gradle):
    implementation 'com.google.firebase:firebase-functions:20.0.0'
    implementation 'com.google.code.gson:gson:2.8.6'
    
  • Kini Anda siap untuk mulai mengenali teks dalam gambar.

    1. Menyiapkan 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:

      Java

      Bitmap bitmap = MediaStore.Images.Media.getBitmap(getContentResolver(), uri);

      Kotlin+KTX

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

      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

      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

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

      Kotlin+KTX

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

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

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

    2. Memanggil fungsi callable untuk mengenali teks

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

    1. Pertama, lakukan inisialisasi instance Cloud Functions:

      Java

      private FirebaseFunctions mFunctions;
      // ...
      mFunctions = FirebaseFunctions.getInstance();
      

      Kotlin+KTX

      private lateinit var functions: FirebaseFunctions
      // ...
      functions = Firebase.functions
      
    2. Tentukan metode untuk memanggil fungsi:

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

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

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

      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)
      

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

      Java

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

      Kotlin+KTX

      val imageContext = JsonObject()
      val languageHints = JsonArray()
      languageHints.add("en")
      imageContext.add("languageHints", languageHints)
      request.add("imageContext", imageContext)
      
    4. Terakhir, panggil fungsi tersebut:

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

      Kotlin+KTX

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

    3. Mengekstrak 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:

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

    Kotlin+KTX

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

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

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

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