Catch up on everthing we announced at this year's Firebase Summit. Learn more

จดจำข้อความในรูปภาพได้อย่างปลอดภัยด้วย Cloud Vision โดยใช้ Firebase Auth และฟังก์ชั่นบน Android

ในการเรียก Google Cloud API จากแอปของคุณ คุณต้องสร้าง REST API ระดับกลางที่จัดการการให้สิทธิ์และปกป้องค่าลับ เช่น คีย์ API จากนั้นคุณจะต้องเขียนโค้ดในแอพมือถือของคุณเพื่อตรวจสอบสิทธิ์และสื่อสารกับบริการระดับกลางนี้

วิธีหนึ่งในการสร้าง REST API นี้คือการใช้ Firebase Authentication and Functions ซึ่งให้เกตเวย์แบบไร้เซิร์ฟเวอร์ที่มีการจัดการไปยัง Google Cloud API ที่จัดการการตรวจสอบสิทธิ์ และสามารถเรียกได้จากแอปบนอุปกรณ์เคลื่อนที่ด้วย SDK ที่สร้างไว้ล่วงหน้า

คู่มือนี้สาธิตวิธีใช้เทคนิคนี้เพื่อเรียก Cloud Vision API จากแอปของคุณ วิธีนี้จะอนุญาตให้ผู้ใช้ที่ผ่านการตรวจสอบสิทธิ์ทั้งหมดเข้าถึงบริการที่เรียกเก็บเงิน Cloud Vision ผ่านโปรเจ็กต์ Cloud ของคุณ ดังนั้นให้พิจารณาว่ากลไกการตรวจสอบสิทธิ์นี้เพียงพอสำหรับกรณีการใช้งานของคุณหรือไม่ก่อนดำเนินการต่อ

ก่อนจะเริ่ม

กำหนดค่าโครงการของคุณ

  1. หากคุณยังไม่ได้ เพิ่ม Firebase กับโครงการ Android ของคุณ
  2. หากคุณยังไม่ได้เปิดใช้งาน API แบบ Cloud-based สำหรับโปรเจ็กต์ของคุณ ให้ดำเนินการดังนี้:

    1. เปิด หน้า Firebase ML APIs ของคอนโซล Firebase
    2. หากคุณไม่ได้อัปเกรดแล้วโครงการของคุณแผนกำหนดราคา Blaze คลิกอัพเกรดจะทำเช่นนั้น (คุณจะได้รับแจ้งให้อัปเกรดเฉพาะเมื่อโปรเจ็กต์ของคุณไม่อยู่ในแผน Blaze)

      เฉพาะโปรเจ็กต์ระดับ Blaze เท่านั้นที่สามารถใช้ API แบบคลาวด์ได้

    3. หาก APIs cloud-based ยังไม่ได้เปิดใช้งานแล้วคลิกเปิดใช้งาน API ของ cloud-based
  3. กำหนดค่าคีย์ Firebase API ที่มีอยู่ของคุณเพื่อไม่อนุญาตให้เข้าถึง Cloud Vision API:
    1. เปิด ข้อมูลประจำตัว หน้าของคอนโซลเมฆ
    2. สำหรับแต่ละคีย์ API ในรายการเปิดมุมมองการแก้ไขและในส่วนข้อ จำกัด ที่สำคัญเพิ่มทั้งหมดของ API ที่มีอยู่ยกเว้น API เมฆวิสัยทัศน์ในรายการ

ปรับใช้ฟังก์ชันที่เรียกได้

ขั้นต่อไป ปรับใช้ Cloud Function ที่คุณจะใช้เพื่อเชื่อมโยงแอปของคุณและ Cloud Vision API functions-samples ที่เก็บมีตัวอย่างเช่นคุณสามารถใช้

ตามค่าเริ่มต้น การเข้าถึง Cloud Vision API ผ่านฟังก์ชันนี้จะอนุญาตให้เฉพาะผู้ใช้แอปที่ตรวจสอบสิทธิ์เข้าถึง Cloud Vision API เท่านั้น คุณสามารถปรับเปลี่ยนฟังก์ชันสำหรับข้อกำหนดต่างๆ ได้

ในการปรับใช้ฟังก์ชัน:

  1. โคลนหรือ Download ฟังก์ชั่นตัวอย่าง repo และการเปลี่ยนแปลงไปสู่ vision-annotate-image ไดเรกทอรี:
    git clone https://github.com/firebase/functions-samples
    cd vision-annotate-image
    
  2. ติดตั้งการอ้างอิง:
    cd functions
    npm install
    cd ..
    
  3. หากคุณไม่ได้มี Firebase CLI, ติดตั้ง
  4. เริ่มต้นโครงการ Firebase ใน vision-annotate-image ไดเรกทอรี เมื่อได้รับแจ้ง ให้เลือกโครงการของคุณในรายการ
    firebase init
  5. การจัดวางฟังก์ชั่น:
    firebase deploy --only functions:annotateImage

เพิ่ม Firebase Auth ในแอปของคุณ

ฟังก์ชัน callable ที่ปรับใช้ด้านบนจะปฏิเสธคำขอใดๆ จากผู้ใช้ที่ไม่ผ่านการตรวจสอบสิทธิ์ของแอปของคุณ หากคุณยังไม่ได้ทำเช่นนั้นคุณจะต้อง เพิ่ม Firebase Auth แอปของคุณ

เพิ่มการพึ่งพาที่จำเป็นให้กับแอปของคุณ

  • เพิ่มการอ้างอิงสำหรับฟังก์ชั่นและ Firebase gson Android ห้องสมุดโมดูลของคุณ (app ระดับ) ไฟล์ Gradle (ปกติ app / build.gradle):
    implementation 'com.google.firebase:firebase-functions:20.0.1'
    implementation 'com.google.code.gson:gson:2.8.6'
    
  • ตอนนี้คุณพร้อมที่จะเริ่มจดจำข้อความในภาพแล้ว

    1. เตรียมภาพอินพุต

    ในการเรียก Cloud Vision รูปภาพต้องได้รับการจัดรูปแบบเป็นสตริงที่เข้ารหัส base64 ในการประมวลผลรูปภาพจากไฟล์ URI ที่บันทึกไว้:
    1. รับภาพเป็น Bitmap วัตถุ:

      Java

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

      คอตลิน+KTX

      var bitmap: Bitmap = MediaStore.Images.Media.getBitmap(contentResolver, uri)
    2. คุณสามารถเลือกลดขนาดรูปภาพเพื่อประหยัดแบนด์วิดท์ ดู เมฆวิสัยทัศน์ขนาดภาพแนะนำ

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

      คอตลิน+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);

      คอตลิน+KTX

      // Scale down bitmap size
      bitmap = scaleBitmapDown(bitmap, 640)
    3. แปลงวัตถุบิตแมปเป็นสตริงที่เข้ารหัส 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);

      คอตลิน+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. ภาพที่แสดงโดย Bitmap วัตถุต้องตรงกับที่ไม่มีการหมุนเพิ่มเติมที่จำเป็น

    2. เรียกใช้ฟังก์ชันที่เรียกได้เพื่อจดจำข้อความ

    ที่จะรับรู้ข้อความในภาพเรียกฟังก์ชั่น callable, ผ่าน คำขอ JSON เมฆวิสัยทัศน์

    1. ขั้นแรก เริ่มต้นอินสแตนซ์ของ Cloud Functions:

      Java

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

      คอตลิน+KTX

      private lateinit var functions: FirebaseFunctions
      // ...
      functions = Firebase.functions
      
    2. กำหนดวิธีการเรียกใช้ฟังก์ชัน:

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

      คอตลิน+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. สร้างคำขอ JSON เมฆวิสัยทัศน์ API สนับสนุนทั้งสอง ประเภท ของการตรวจสอบข้อความ: TEXT_DETECTION และ DOCUMENT_TEXT_DETECTION ดู เมฆวิสัยทัศน์ OCR เอกสาร สำหรับความแตกต่างระหว่างทั้งสองกรณีการใช้งาน

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

      คอตลิน+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

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

      คอตลิน+KTX

      val imageContext = JsonObject()
      val languageHints = JsonArray()
      languageHints.add("en")
      imageContext.add("languageHints", languageHints)
      request.add("imageContext", imageContext)
      
    4. ในที่สุด เรียกใช้ฟังก์ชัน:

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

      คอตลิน+KTX

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

    3. แยกข้อความจากบล็อกของข้อความที่รู้จัก

    หากการดำเนินการรับรู้ข้อความที่ประสบความสำเร็จในการตอบสนองของ JSON BatchAnnotateImagesResponse จะกลับมาในผลงานของ คำอธิบายประกอบข้อความที่สามารถพบได้ใน fullTextAnnotation วัตถุ

    คุณจะได้รับข้อความที่ได้รับการยอมรับเป็นสตริงในส่วน text ฟิลด์ ตัวอย่างเช่น:

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

    คอตลิน+KTX

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

    คุณยังสามารถรับข้อมูลเฉพาะสำหรับภูมิภาคของรูปภาพได้อีกด้วย สำหรับแต่ละ block , paragraph , word และ symbol คุณจะได้รับข้อความที่ได้รับการยอมรับในภูมิภาคและพิกัดขอบเขตของภูมิภาค ตัวอย่างเช่น:

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

    คอตลิน+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
        }
    }