จดจำข้อความในรูปภาพด้วย ML Kit บน Android

คุณสามารถใช้ ML Kit เพื่อจดจำข้อความในรูปภาพได้ ML Kit มีทั้ง API อเนกประสงค์ที่เหมาะสำหรับการจดจำข้อความในรูปภาพ เช่น ข้อความบนป้ายถนน และ API ที่เพิ่มประสิทธิภาพสำหรับการจดจำข้อความในเอกสาร API อเนกประสงค์มีทั้งโมเดลในอุปกรณ์และในระบบคลาวด์ การจดจำข้อความในเอกสารจะใช้ได้เฉพาะในรูปแบบที่ทำงานบนระบบคลาวด์เท่านั้น ดูการเปรียบเทียบรูปแบบในระบบคลาวด์และในอุปกรณ์ได้ที่ภาพรวม

ก่อนเริ่มต้น

  1. เพิ่ม Firebase ลงในโปรเจ็กต์ Android หากยังไม่ได้ดำเนินการ
  2. เพิ่มทรัพยากร Dependency สำหรับคลัง ML Kit สำหรับ Android ลงในไฟล์ Gradle ของโมดูล (ระดับแอป) (โดยปกติจะเป็น app/build.gradle)
    apply plugin: 'com.android.application'
    apply plugin: 'com.google.gms.google-services'
    
    dependencies {
      // ...
    
      implementation 'com.google.firebase:firebase-ml-vision:24.0.3'
    }
  3. ไม่บังคับแต่แนะนํา: หากคุณใช้ API ในอุปกรณ์ ให้กําหนดค่าแอปให้ดาวน์โหลดโมเดล ML ลงในอุปกรณ์โดยอัตโนมัติหลังจากติดตั้งแอปจาก Play Store

    โดยเพิ่มการประกาศต่อไปนี้ลงในไฟล์ AndroidManifest.xml ของแอป

    <application ...>
      ...
      <meta-data
          android:name="com.google.firebase.ml.vision.DEPENDENCIES"
          android:value="ocr" />
      <!-- To use multiple models: android:value="ocr,model2,model3" -->
    </application>
    หากคุณไม่ได้เปิดใช้การดาวน์โหลดโมเดลขณะติดตั้ง ระบบจะดาวน์โหลดโมเดลเมื่อคุณเรียกใช้เครื่องตรวจจับในอุปกรณ์เป็นครั้งแรก คำขอที่คุณส่งก่อนการดาวน์โหลดเสร็จสมบูรณ์จะไม่มีผล
  4. หากต้องการใช้โมเดลที่อยู่ในระบบคลาวด์และยังไม่ได้เปิดใช้ API ในระบบคลาวด์สําหรับโปรเจ็กต์ ให้ทําดังนี้

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

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

    3. หากยังไม่ได้เปิดใช้ API ที่อยู่ในระบบคลาวด์ ให้คลิกเปิดใช้ API ที่อยู่ในระบบคลาวด์

    หากต้องการใช้เฉพาะโมเดลในอุปกรณ์ ให้ข้ามขั้นตอนนี้

ตอนนี้คุณก็พร้อมที่จะเริ่มจดจำข้อความในรูปภาพแล้ว

หลักเกณฑ์เกี่ยวกับรูปภาพอินพุต

  • รูปภาพอินพุตต้องมีข้อความที่แสดงด้วยข้อมูลพิกเซลที่เพียงพอเพื่อให้ ML Kit จดจำข้อความได้อย่างแม่นยำ สำหรับข้อความภาษาละติน อักขระแต่ละตัวควรมีขนาดอย่างน้อย 16x16 พิกเซล สำหรับข้อความภาษาจีน ญี่ปุ่น และเกาหลี (API แบบระบบคลาวด์เท่านั้นที่รองรับ) อักขระแต่ละตัวควรมีขนาด 24x24 พิกเซล โดยทั่วไปแล้ว ตัวอักษรที่มีขนาดใหญ่กว่า 24x24 พิกเซลจะไม่มีประโยชน์ด้านความแม่นยำสำหรับทุกภาษา

    ตัวอย่างเช่น รูปภาพขนาด 640x480 อาจเหมาะกับการสแกนนามบัตรที่กินพื้นที่เต็มความกว้างของรูปภาพ หากต้องการสแกนเอกสารที่พิมพ์บนกระดาษขนาดจดหมาย คุณอาจต้องใช้รูปภาพขนาด 720x1280 พิกเซล

  • รูปภาพที่โฟกัสไม่ดีอาจทำให้การจดจำข้อความไม่ถูกต้อง หากไม่ได้ผลลัพธ์ที่ยอมรับได้ ให้ลองขอให้ผู้ใช้ถ่ายภาพอีกครั้ง

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


การรู้จำข้อความในรูปภาพ

หากต้องการจดจำข้อความในรูปภาพโดยใช้โมเดลในอุปกรณ์หรือในระบบคลาวด์ ให้เรียกใช้โปรแกรมจดจำข้อความตามที่อธิบายไว้ด้านล่าง

1. เรียกใช้โปรแกรมจดจำข้อความ

หากต้องการจดจำข้อความในรูปภาพ ให้สร้างออบเจ็กต์ FirebaseVisionImage จาก Bitmap, media.Image, ByteBuffer, อาร์เรย์ไบต์ หรือไฟล์ในอุปกรณ์ จากนั้นส่งออบเจ็กต์ FirebaseVisionImage ไปยังเมธอด processImage ของ FirebaseVisionTextRecognizer

  1. สร้างออบเจ็กต์ FirebaseVisionImage จากรูปภาพ

    • หากต้องการสร้างออบเจ็กต์ FirebaseVisionImage จากออบเจ็กต์ media.Image เช่น เมื่อจับภาพจากกล้องของอุปกรณ์ ให้ส่งออบเจ็กต์ media.Image และการหมุนของรูปภาพไปยัง FirebaseVisionImage.fromMediaImage()

      หากคุณใช้ไลบรารี CameraX คลาส OnImageCapturedListener และ ImageAnalysis.Analyzer จะคํานวณค่าการหมุนให้คุณ คุณจึงต้องแปลงการหมุนเป็นค่าคงที่ ROTATION_ ของ ML Kit ก่อนเรียกใช้ FirebaseVisionImage.fromMediaImage()

      Java

      private class YourAnalyzer implements ImageAnalysis.Analyzer {
      
          private int degreesToFirebaseRotation(int degrees) {
              switch (degrees) {
                  case 0:
                      return FirebaseVisionImageMetadata.ROTATION_0;
                  case 90:
                      return FirebaseVisionImageMetadata.ROTATION_90;
                  case 180:
                      return FirebaseVisionImageMetadata.ROTATION_180;
                  case 270:
                      return FirebaseVisionImageMetadata.ROTATION_270;
                  default:
                      throw new IllegalArgumentException(
                              "Rotation must be 0, 90, 180, or 270.");
              }
          }
      
          @Override
          public void analyze(ImageProxy imageProxy, int degrees) {
              if (imageProxy == null || imageProxy.getImage() == null) {
                  return;
              }
              Image mediaImage = imageProxy.getImage();
              int rotation = degreesToFirebaseRotation(degrees);
              FirebaseVisionImage image =
                      FirebaseVisionImage.fromMediaImage(mediaImage, rotation);
              // Pass image to an ML Kit Vision API
              // ...
          }
      }

      Kotlin

      private class YourImageAnalyzer : ImageAnalysis.Analyzer {
          private fun degreesToFirebaseRotation(degrees: Int): Int = when(degrees) {
              0 -> FirebaseVisionImageMetadata.ROTATION_0
              90 -> FirebaseVisionImageMetadata.ROTATION_90
              180 -> FirebaseVisionImageMetadata.ROTATION_180
              270 -> FirebaseVisionImageMetadata.ROTATION_270
              else -> throw Exception("Rotation must be 0, 90, 180, or 270.")
          }
      
          override fun analyze(imageProxy: ImageProxy?, degrees: Int) {
              val mediaImage = imageProxy?.image
              val imageRotation = degreesToFirebaseRotation(degrees)
              if (mediaImage != null) {
                  val image = FirebaseVisionImage.fromMediaImage(mediaImage, imageRotation)
                  // Pass image to an ML Kit Vision API
                  // ...
              }
          }
      }

      หากไม่ได้ใช้คลังกล้องที่ระบุการหมุนของรูปภาพ คุณจะคำนวณการหมุนได้จากการหมุนของอุปกรณ์และการวางแนวของเซ็นเซอร์กล้องในอุปกรณ์ โดยทำดังนี้

      Java

      private static final SparseIntArray ORIENTATIONS = new SparseIntArray();
      static {
          ORIENTATIONS.append(Surface.ROTATION_0, 90);
          ORIENTATIONS.append(Surface.ROTATION_90, 0);
          ORIENTATIONS.append(Surface.ROTATION_180, 270);
          ORIENTATIONS.append(Surface.ROTATION_270, 180);
      }
      
      /**
       * Get the angle by which an image must be rotated given the device's current
       * orientation.
       */
      @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
      private int getRotationCompensation(String cameraId, Activity activity, Context context)
              throws CameraAccessException {
          // Get the device's current rotation relative to its "native" orientation.
          // Then, from the ORIENTATIONS table, look up the angle the image must be
          // rotated to compensate for the device's rotation.
          int deviceRotation = activity.getWindowManager().getDefaultDisplay().getRotation();
          int rotationCompensation = ORIENTATIONS.get(deviceRotation);
      
          // On most devices, the sensor orientation is 90 degrees, but for some
          // devices it is 270 degrees. For devices with a sensor orientation of
          // 270, rotate the image an additional 180 ((270 + 270) % 360) degrees.
          CameraManager cameraManager = (CameraManager) context.getSystemService(CAMERA_SERVICE);
          int sensorOrientation = cameraManager
                  .getCameraCharacteristics(cameraId)
                  .get(CameraCharacteristics.SENSOR_ORIENTATION);
          rotationCompensation = (rotationCompensation + sensorOrientation + 270) % 360;
      
          // Return the corresponding FirebaseVisionImageMetadata rotation value.
          int result;
          switch (rotationCompensation) {
              case 0:
                  result = FirebaseVisionImageMetadata.ROTATION_0;
                  break;
              case 90:
                  result = FirebaseVisionImageMetadata.ROTATION_90;
                  break;
              case 180:
                  result = FirebaseVisionImageMetadata.ROTATION_180;
                  break;
              case 270:
                  result = FirebaseVisionImageMetadata.ROTATION_270;
                  break;
              default:
                  result = FirebaseVisionImageMetadata.ROTATION_0;
                  Log.e(TAG, "Bad rotation value: " + rotationCompensation);
          }
          return result;
      }

      Kotlin

      private val ORIENTATIONS = SparseIntArray()
      
      init {
          ORIENTATIONS.append(Surface.ROTATION_0, 90)
          ORIENTATIONS.append(Surface.ROTATION_90, 0)
          ORIENTATIONS.append(Surface.ROTATION_180, 270)
          ORIENTATIONS.append(Surface.ROTATION_270, 180)
      }
      /**
       * Get the angle by which an image must be rotated given the device's current
       * orientation.
       */
      @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
      @Throws(CameraAccessException::class)
      private fun getRotationCompensation(cameraId: String, activity: Activity, context: Context): Int {
          // Get the device's current rotation relative to its "native" orientation.
          // Then, from the ORIENTATIONS table, look up the angle the image must be
          // rotated to compensate for the device's rotation.
          val deviceRotation = activity.windowManager.defaultDisplay.rotation
          var rotationCompensation = ORIENTATIONS.get(deviceRotation)
      
          // On most devices, the sensor orientation is 90 degrees, but for some
          // devices it is 270 degrees. For devices with a sensor orientation of
          // 270, rotate the image an additional 180 ((270 + 270) % 360) degrees.
          val cameraManager = context.getSystemService(CAMERA_SERVICE) as CameraManager
          val sensorOrientation = cameraManager
                  .getCameraCharacteristics(cameraId)
                  .get(CameraCharacteristics.SENSOR_ORIENTATION)!!
          rotationCompensation = (rotationCompensation + sensorOrientation + 270) % 360
      
          // Return the corresponding FirebaseVisionImageMetadata rotation value.
          val result: Int
          when (rotationCompensation) {
              0 -> result = FirebaseVisionImageMetadata.ROTATION_0
              90 -> result = FirebaseVisionImageMetadata.ROTATION_90
              180 -> result = FirebaseVisionImageMetadata.ROTATION_180
              270 -> result = FirebaseVisionImageMetadata.ROTATION_270
              else -> {
                  result = FirebaseVisionImageMetadata.ROTATION_0
                  Log.e(TAG, "Bad rotation value: $rotationCompensation")
              }
          }
          return result
      }

      จากนั้นส่งออบเจ็กต์ media.Image และค่าการหมุนไปยัง FirebaseVisionImage.fromMediaImage() ดังนี้

      Java

      FirebaseVisionImage image = FirebaseVisionImage.fromMediaImage(mediaImage, rotation);

      Kotlin

      val image = FirebaseVisionImage.fromMediaImage(mediaImage, rotation)
    • หากต้องการสร้างออบเจ็กต์ FirebaseVisionImage จาก URI ของไฟล์ ให้ส่งบริบทแอปและ URI ของไฟล์ไปยัง FirebaseVisionImage.fromFilePath() ซึ่งจะมีประโยชน์เมื่อคุณใช้ Intent ACTION_GET_CONTENT เพื่อแจ้งให้ผู้ใช้เลือกรูปภาพจากแอปแกลเลอรี

      Java

      FirebaseVisionImage image;
      try {
          image = FirebaseVisionImage.fromFilePath(context, uri);
      } catch (IOException e) {
          e.printStackTrace();
      }

      Kotlin

      val image: FirebaseVisionImage
      try {
          image = FirebaseVisionImage.fromFilePath(context, uri)
      } catch (e: IOException) {
          e.printStackTrace()
      }
    • หากต้องการสร้างออบเจ็กต์ FirebaseVisionImage จาก ByteBuffer หรืออาร์เรย์ไบต์ ให้คำนวณการหมุนภาพก่อนตามที่อธิบายไว้ข้างต้นสำหรับอินพุต media.Image

      จากนั้นสร้างออบเจ็กต์ FirebaseVisionImageMetadata ซึ่งมีข้อมูลความสูง กว้าง รูปแบบการเข้ารหัสสี และการหมุนของรูปภาพ

      Java

      FirebaseVisionImageMetadata metadata = new FirebaseVisionImageMetadata.Builder()
              .setWidth(480)   // 480x360 is typically sufficient for
              .setHeight(360)  // image recognition
              .setFormat(FirebaseVisionImageMetadata.IMAGE_FORMAT_NV21)
              .setRotation(rotation)
              .build();

      Kotlin

      val metadata = FirebaseVisionImageMetadata.Builder()
              .setWidth(480) // 480x360 is typically sufficient for
              .setHeight(360) // image recognition
              .setFormat(FirebaseVisionImageMetadata.IMAGE_FORMAT_NV21)
              .setRotation(rotation)
              .build()

      ใช้บัฟเฟอร์หรืออาร์เรย์ รวมถึงออบเจ็กต์ข้อมูลเมตาเพื่อสร้างออบเจ็กต์ FirebaseVisionImage ดังนี้

      Java

      FirebaseVisionImage image = FirebaseVisionImage.fromByteBuffer(buffer, metadata);
      // Or: FirebaseVisionImage image = FirebaseVisionImage.fromByteArray(byteArray, metadata);

      Kotlin

      val image = FirebaseVisionImage.fromByteBuffer(buffer, metadata)
      // Or: val image = FirebaseVisionImage.fromByteArray(byteArray, metadata)
    • วิธีสร้างออบเจ็กต์ FirebaseVisionImage จากออบเจ็กต์ Bitmap

      Java

      FirebaseVisionImage image = FirebaseVisionImage.fromBitmap(bitmap);

      Kotlin

      val image = FirebaseVisionImage.fromBitmap(bitmap)
      รูปภาพที่แสดงโดยออบเจ็กต์ Bitmap ต้องตั้งตรงโดยไม่จำเป็นต้องหมุนเพิ่มเติม

  2. รับอินสแตนซ์ของ FirebaseVisionTextRecognizer

    วิธีใช้รูปแบบในอุปกรณ์

    Java

    FirebaseVisionTextRecognizer detector = FirebaseVision.getInstance()
            .getOnDeviceTextRecognizer();

    Kotlin

    val detector = FirebaseVision.getInstance()
            .onDeviceTextRecognizer

    วิธีใช้รูปแบบที่ทำงานบนระบบคลาวด์

    Java

    FirebaseVisionTextRecognizer detector = FirebaseVision.getInstance()
            .getCloudTextRecognizer();
    // Or, to change the default settings:
    //   FirebaseVisionTextRecognizer detector = FirebaseVision.getInstance()
    //          .getCloudTextRecognizer(options);
    // Or, to provide language hints to assist with language detection:
    // See https://cloud.google.com/vision/docs/languages for supported languages
    FirebaseVisionCloudTextRecognizerOptions options = new FirebaseVisionCloudTextRecognizerOptions.Builder()
            .setLanguageHints(Arrays.asList("en", "hi"))
            .build();

    Kotlin

    val detector = FirebaseVision.getInstance().cloudTextRecognizer
    // Or, to change the default settings:
    // val detector = FirebaseVision.getInstance().getCloudTextRecognizer(options)
    // Or, to provide language hints to assist with language detection:
    // See https://cloud.google.com/vision/docs/languages for supported languages
    val options = FirebaseVisionCloudTextRecognizerOptions.Builder()
            .setLanguageHints(listOf("en", "hi"))
            .build()
  3. สุดท้าย ให้ส่งรูปภาพไปยังเมธอด processImage ดังนี้

    Java

    Task<FirebaseVisionText> result =
            detector.processImage(image)
                    .addOnSuccessListener(new OnSuccessListener<FirebaseVisionText>() {
                        @Override
                        public void onSuccess(FirebaseVisionText firebaseVisionText) {
                            // Task completed successfully
                            // ...
                        }
                    })
                    .addOnFailureListener(
                            new OnFailureListener() {
                                @Override
                                public void onFailure(@NonNull Exception e) {
                                    // Task failed with an exception
                                    // ...
                                }
                            });

    Kotlin

    val result = detector.processImage(image)
            .addOnSuccessListener { firebaseVisionText ->
                // Task completed successfully
                // ...
            }
            .addOnFailureListener { e ->
                // Task failed with an exception
                // ...
            }

2. ดึงข้อความจากบล็อกข้อความที่ระบบจดจำได้

หากการดําเนินการจดจําข้อความสําเร็จ ระบบจะส่งออบเจ็กต์ FirebaseVisionText ไปยังโปรแกรมฟังเหตุการณ์สําเร็จ ออบเจ็กต์ FirebaseVisionText มีข้อความแบบเต็มที่ได้รับการจดจำในรูปภาพและออบเจ็กต์ TextBlock ตั้งแต่ 0 รายการขึ้นไป

TextBlock แต่ละรายการแสดงบล็อกข้อความสี่เหลี่ยมผืนผ้าซึ่งมีออบเจ็กต์ Line อย่างน้อย 1 รายการ ออบเจ็กต์ Line แต่ละรายการมีออบเจ็กต์ Element อย่างน้อย 1 รายการ ซึ่งแสดงถึงคำและเอนทิตีที่คล้ายกับคำ (วันที่ ตัวเลข และอื่นๆ)

สําหรับออบเจ็กต์ TextBlock, Line และ Element แต่ละรายการ คุณสามารถรับข้อความที่ระบบจดจําได้ในภูมิภาคและพิกัดขอบเขตของภูมิภาค

เช่น

Java

String resultText = result.getText();
for (FirebaseVisionText.TextBlock block: result.getTextBlocks()) {
    String blockText = block.getText();
    Float blockConfidence = block.getConfidence();
    List<RecognizedLanguage> blockLanguages = block.getRecognizedLanguages();
    Point[] blockCornerPoints = block.getCornerPoints();
    Rect blockFrame = block.getBoundingBox();
    for (FirebaseVisionText.Line line: block.getLines()) {
        String lineText = line.getText();
        Float lineConfidence = line.getConfidence();
        List<RecognizedLanguage> lineLanguages = line.getRecognizedLanguages();
        Point[] lineCornerPoints = line.getCornerPoints();
        Rect lineFrame = line.getBoundingBox();
        for (FirebaseVisionText.Element element: line.getElements()) {
            String elementText = element.getText();
            Float elementConfidence = element.getConfidence();
            List<RecognizedLanguage> elementLanguages = element.getRecognizedLanguages();
            Point[] elementCornerPoints = element.getCornerPoints();
            Rect elementFrame = element.getBoundingBox();
        }
    }
}

Kotlin

val resultText = result.text
for (block in result.textBlocks) {
    val blockText = block.text
    val blockConfidence = block.confidence
    val blockLanguages = block.recognizedLanguages
    val blockCornerPoints = block.cornerPoints
    val blockFrame = block.boundingBox
    for (line in block.lines) {
        val lineText = line.text
        val lineConfidence = line.confidence
        val lineLanguages = line.recognizedLanguages
        val lineCornerPoints = line.cornerPoints
        val lineFrame = line.boundingBox
        for (element in line.elements) {
            val elementText = element.text
            val elementConfidence = element.confidence
            val elementLanguages = element.recognizedLanguages
            val elementCornerPoints = element.cornerPoints
            val elementFrame = element.boundingBox
        }
    }
}

เคล็ดลับในการปรับปรุงประสิทธิภาพแบบเรียลไทม์

หากต้องการใช้โมเดลในอุปกรณ์เพื่อจดจำข้อความในแอปพลิเคชันแบบเรียลไทม์ ให้ทำตามหลักเกณฑ์ต่อไปนี้เพื่อให้ได้อัตราเฟรมที่ดีที่สุด

  • จำกัดการเรียกใช้โปรแกรมจดจำข้อความ หากเฟรมวิดีโอใหม่พร้อมใช้งานขณะที่โปรแกรมอ่านข้อความทำงานอยู่ ให้วางเฟรมนั้น
  • หากใช้เอาต์พุตของตัวจดจำข้อความเพื่อวางกราฟิกซ้อนทับบนรูปภาพอินพุต ให้รับผลลัพธ์จาก ML Kit ก่อน จากนั้นจึงแสดงผลรูปภาพและวางซ้อนในขั้นตอนเดียว ซึ่งจะทำให้คุณแสดงผลไปยังพื้นผิวการแสดงผลเพียงครั้งเดียวสำหรับเฟรมอินพุตแต่ละเฟรม
  • หากคุณใช้ Camera2 API ให้จับภาพในรูปแบบ ImageFormat.YUV_420_888

    หากคุณใช้ Camera API เวอร์ชันเก่า ให้ถ่ายภาพในรูปแบบ ImageFormat.NV21

  • ลองถ่ายภาพด้วยความละเอียดที่ต่ำลง อย่างไรก็ตาม โปรดคำนึงถึงข้อกำหนดด้านขนาดรูปภาพของ API นี้ด้วย

ขั้นตอนถัดไป


จดจำข้อความในรูปภาพของเอกสาร

หากต้องการจดจำข้อความของเอกสาร ให้กําหนดค่าและเรียกใช้โปรแกรมจดจําข้อความเอกสารที่อยู่ในระบบคลาวด์ตามที่อธิบายไว้ด้านล่าง

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

วิธีใช้ API การจดจำข้อความในเอกสาร

1. เรียกใช้โปรแกรมจดจำข้อความ

หากต้องการจดจำข้อความในรูปภาพ ให้สร้างออบเจ็กต์ FirebaseVisionImage จาก Bitmap, media.Image, ByteBuffer, อาร์เรย์ไบต์ หรือไฟล์ในอุปกรณ์ จากนั้นส่งออบเจ็กต์ FirebaseVisionImage ไปยังเมธอด processImage ของ FirebaseVisionDocumentTextRecognizer

  1. สร้างออบเจ็กต์ FirebaseVisionImage จากรูปภาพ

    • หากต้องการสร้างออบเจ็กต์ FirebaseVisionImage จากออบเจ็กต์ media.Image เช่น เมื่อจับภาพจากกล้องของอุปกรณ์ ให้ส่งออบเจ็กต์ media.Image และการหมุนของรูปภาพไปยัง FirebaseVisionImage.fromMediaImage()

      หากคุณใช้ไลบรารี CameraX คลาส OnImageCapturedListener และ ImageAnalysis.Analyzer จะคํานวณค่าการหมุนให้คุณ คุณจึงต้องแปลงการหมุนเป็นค่าคงที่ ROTATION_ ของ ML Kit ก่อนเรียกใช้ FirebaseVisionImage.fromMediaImage()

      Java

      private class YourAnalyzer implements ImageAnalysis.Analyzer {
      
          private int degreesToFirebaseRotation(int degrees) {
              switch (degrees) {
                  case 0:
                      return FirebaseVisionImageMetadata.ROTATION_0;
                  case 90:
                      return FirebaseVisionImageMetadata.ROTATION_90;
                  case 180:
                      return FirebaseVisionImageMetadata.ROTATION_180;
                  case 270:
                      return FirebaseVisionImageMetadata.ROTATION_270;
                  default:
                      throw new IllegalArgumentException(
                              "Rotation must be 0, 90, 180, or 270.");
              }
          }
      
          @Override
          public void analyze(ImageProxy imageProxy, int degrees) {
              if (imageProxy == null || imageProxy.getImage() == null) {
                  return;
              }
              Image mediaImage = imageProxy.getImage();
              int rotation = degreesToFirebaseRotation(degrees);
              FirebaseVisionImage image =
                      FirebaseVisionImage.fromMediaImage(mediaImage, rotation);
              // Pass image to an ML Kit Vision API
              // ...
          }
      }

      Kotlin

      private class YourImageAnalyzer : ImageAnalysis.Analyzer {
          private fun degreesToFirebaseRotation(degrees: Int): Int = when(degrees) {
              0 -> FirebaseVisionImageMetadata.ROTATION_0
              90 -> FirebaseVisionImageMetadata.ROTATION_90
              180 -> FirebaseVisionImageMetadata.ROTATION_180
              270 -> FirebaseVisionImageMetadata.ROTATION_270
              else -> throw Exception("Rotation must be 0, 90, 180, or 270.")
          }
      
          override fun analyze(imageProxy: ImageProxy?, degrees: Int) {
              val mediaImage = imageProxy?.image
              val imageRotation = degreesToFirebaseRotation(degrees)
              if (mediaImage != null) {
                  val image = FirebaseVisionImage.fromMediaImage(mediaImage, imageRotation)
                  // Pass image to an ML Kit Vision API
                  // ...
              }
          }
      }

      หากไม่ได้ใช้คลังกล้องที่ระบุการหมุนของรูปภาพ คุณจะคำนวณการหมุนได้จากการหมุนของอุปกรณ์และการวางแนวของเซ็นเซอร์กล้องในอุปกรณ์ โดยทำดังนี้

      Java

      private static final SparseIntArray ORIENTATIONS = new SparseIntArray();
      static {
          ORIENTATIONS.append(Surface.ROTATION_0, 90);
          ORIENTATIONS.append(Surface.ROTATION_90, 0);
          ORIENTATIONS.append(Surface.ROTATION_180, 270);
          ORIENTATIONS.append(Surface.ROTATION_270, 180);
      }
      
      /**
       * Get the angle by which an image must be rotated given the device's current
       * orientation.
       */
      @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
      private int getRotationCompensation(String cameraId, Activity activity, Context context)
              throws CameraAccessException {
          // Get the device's current rotation relative to its "native" orientation.
          // Then, from the ORIENTATIONS table, look up the angle the image must be
          // rotated to compensate for the device's rotation.
          int deviceRotation = activity.getWindowManager().getDefaultDisplay().getRotation();
          int rotationCompensation = ORIENTATIONS.get(deviceRotation);
      
          // On most devices, the sensor orientation is 90 degrees, but for some
          // devices it is 270 degrees. For devices with a sensor orientation of
          // 270, rotate the image an additional 180 ((270 + 270) % 360) degrees.
          CameraManager cameraManager = (CameraManager) context.getSystemService(CAMERA_SERVICE);
          int sensorOrientation = cameraManager
                  .getCameraCharacteristics(cameraId)
                  .get(CameraCharacteristics.SENSOR_ORIENTATION);
          rotationCompensation = (rotationCompensation + sensorOrientation + 270) % 360;
      
          // Return the corresponding FirebaseVisionImageMetadata rotation value.
          int result;
          switch (rotationCompensation) {
              case 0:
                  result = FirebaseVisionImageMetadata.ROTATION_0;
                  break;
              case 90:
                  result = FirebaseVisionImageMetadata.ROTATION_90;
                  break;
              case 180:
                  result = FirebaseVisionImageMetadata.ROTATION_180;
                  break;
              case 270:
                  result = FirebaseVisionImageMetadata.ROTATION_270;
                  break;
              default:
                  result = FirebaseVisionImageMetadata.ROTATION_0;
                  Log.e(TAG, "Bad rotation value: " + rotationCompensation);
          }
          return result;
      }

      Kotlin

      private val ORIENTATIONS = SparseIntArray()
      
      init {
          ORIENTATIONS.append(Surface.ROTATION_0, 90)
          ORIENTATIONS.append(Surface.ROTATION_90, 0)
          ORIENTATIONS.append(Surface.ROTATION_180, 270)
          ORIENTATIONS.append(Surface.ROTATION_270, 180)
      }
      /**
       * Get the angle by which an image must be rotated given the device's current
       * orientation.
       */
      @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
      @Throws(CameraAccessException::class)
      private fun getRotationCompensation(cameraId: String, activity: Activity, context: Context): Int {
          // Get the device's current rotation relative to its "native" orientation.
          // Then, from the ORIENTATIONS table, look up the angle the image must be
          // rotated to compensate for the device's rotation.
          val deviceRotation = activity.windowManager.defaultDisplay.rotation
          var rotationCompensation = ORIENTATIONS.get(deviceRotation)
      
          // On most devices, the sensor orientation is 90 degrees, but for some
          // devices it is 270 degrees. For devices with a sensor orientation of
          // 270, rotate the image an additional 180 ((270 + 270) % 360) degrees.
          val cameraManager = context.getSystemService(CAMERA_SERVICE) as CameraManager
          val sensorOrientation = cameraManager
                  .getCameraCharacteristics(cameraId)
                  .get(CameraCharacteristics.SENSOR_ORIENTATION)!!
          rotationCompensation = (rotationCompensation + sensorOrientation + 270) % 360
      
          // Return the corresponding FirebaseVisionImageMetadata rotation value.
          val result: Int
          when (rotationCompensation) {
              0 -> result = FirebaseVisionImageMetadata.ROTATION_0
              90 -> result = FirebaseVisionImageMetadata.ROTATION_90
              180 -> result = FirebaseVisionImageMetadata.ROTATION_180
              270 -> result = FirebaseVisionImageMetadata.ROTATION_270
              else -> {
                  result = FirebaseVisionImageMetadata.ROTATION_0
                  Log.e(TAG, "Bad rotation value: $rotationCompensation")
              }
          }
          return result
      }

      จากนั้นส่งออบเจ็กต์ media.Image และค่าการหมุนไปยัง FirebaseVisionImage.fromMediaImage() ดังนี้

      Java

      FirebaseVisionImage image = FirebaseVisionImage.fromMediaImage(mediaImage, rotation);

      Kotlin

      val image = FirebaseVisionImage.fromMediaImage(mediaImage, rotation)
    • หากต้องการสร้างออบเจ็กต์ FirebaseVisionImage จาก URI ของไฟล์ ให้ส่งบริบทแอปและ URI ของไฟล์ไปยัง FirebaseVisionImage.fromFilePath() ซึ่งจะมีประโยชน์เมื่อคุณใช้ Intent ACTION_GET_CONTENT เพื่อแจ้งให้ผู้ใช้เลือกรูปภาพจากแอปแกลเลอรี

      Java

      FirebaseVisionImage image;
      try {
          image = FirebaseVisionImage.fromFilePath(context, uri);
      } catch (IOException e) {
          e.printStackTrace();
      }

      Kotlin

      val image: FirebaseVisionImage
      try {
          image = FirebaseVisionImage.fromFilePath(context, uri)
      } catch (e: IOException) {
          e.printStackTrace()
      }
    • หากต้องการสร้างออบเจ็กต์ FirebaseVisionImage จาก ByteBuffer หรืออาร์เรย์ไบต์ ให้คำนวณการหมุนภาพก่อนตามที่อธิบายไว้ข้างต้นสำหรับอินพุต media.Image

      จากนั้นสร้างออบเจ็กต์ FirebaseVisionImageMetadata ซึ่งมีข้อมูลความสูง กว้าง รูปแบบการเข้ารหัสสี และการหมุนของรูปภาพ

      Java

      FirebaseVisionImageMetadata metadata = new FirebaseVisionImageMetadata.Builder()
              .setWidth(480)   // 480x360 is typically sufficient for
              .setHeight(360)  // image recognition
              .setFormat(FirebaseVisionImageMetadata.IMAGE_FORMAT_NV21)
              .setRotation(rotation)
              .build();

      Kotlin

      val metadata = FirebaseVisionImageMetadata.Builder()
              .setWidth(480) // 480x360 is typically sufficient for
              .setHeight(360) // image recognition
              .setFormat(FirebaseVisionImageMetadata.IMAGE_FORMAT_NV21)
              .setRotation(rotation)
              .build()

      ใช้บัฟเฟอร์หรืออาร์เรย์ รวมถึงออบเจ็กต์ข้อมูลเมตาเพื่อสร้างออบเจ็กต์ FirebaseVisionImage ดังนี้

      Java

      FirebaseVisionImage image = FirebaseVisionImage.fromByteBuffer(buffer, metadata);
      // Or: FirebaseVisionImage image = FirebaseVisionImage.fromByteArray(byteArray, metadata);

      Kotlin

      val image = FirebaseVisionImage.fromByteBuffer(buffer, metadata)
      // Or: val image = FirebaseVisionImage.fromByteArray(byteArray, metadata)
    • วิธีสร้างออบเจ็กต์ FirebaseVisionImage จากออบเจ็กต์ Bitmap

      Java

      FirebaseVisionImage image = FirebaseVisionImage.fromBitmap(bitmap);

      Kotlin

      val image = FirebaseVisionImage.fromBitmap(bitmap)
      รูปภาพที่แสดงโดยออบเจ็กต์ Bitmap ต้องตั้งตรงโดยไม่จำเป็นต้องหมุนเพิ่มเติม

  2. รับอินสแตนซ์ของ FirebaseVisionDocumentTextRecognizer

    Java

    FirebaseVisionDocumentTextRecognizer detector = FirebaseVision.getInstance()
            .getCloudDocumentTextRecognizer();
    // Or, to provide language hints to assist with language detection:
    // See https://cloud.google.com/vision/docs/languages for supported languages
    FirebaseVisionCloudDocumentRecognizerOptions options =
            new FirebaseVisionCloudDocumentRecognizerOptions.Builder()
                    .setLanguageHints(Arrays.asList("en", "hi"))
                    .build();
    FirebaseVisionDocumentTextRecognizer detector = FirebaseVision.getInstance()
            .getCloudDocumentTextRecognizer(options);

    Kotlin

    val detector = FirebaseVision.getInstance()
            .cloudDocumentTextRecognizer
    // Or, to provide language hints to assist with language detection:
    // See https://cloud.google.com/vision/docs/languages for supported languages
    val options = FirebaseVisionCloudDocumentRecognizerOptions.Builder()
            .setLanguageHints(listOf("en", "hi"))
            .build()
    val detector = FirebaseVision.getInstance()
            .getCloudDocumentTextRecognizer(options)

  3. สุดท้าย ให้ส่งรูปภาพไปยังเมธอด processImage ดังนี้

    Java

    detector.processImage(myImage)
            .addOnSuccessListener(new OnSuccessListener<FirebaseVisionDocumentText>() {
                @Override
                public void onSuccess(FirebaseVisionDocumentText result) {
                    // Task completed successfully
                    // ...
                }
            })
            .addOnFailureListener(new OnFailureListener() {
                @Override
                public void onFailure(@NonNull Exception e) {
                    // Task failed with an exception
                    // ...
                }
            });

    Kotlin

    detector.processImage(myImage)
            .addOnSuccessListener { firebaseVisionDocumentText ->
                // Task completed successfully
                // ...
            }
            .addOnFailureListener { e ->
                // Task failed with an exception
                // ...
            }

2. ดึงข้อความจากบล็อกข้อความที่ระบบจดจำได้

หากการดําเนินการจดจําข้อความสําเร็จ ระบบจะแสดงผลออบเจ็กต์ FirebaseVisionDocumentText ออบเจ็กต์ FirebaseVisionDocumentText มีข้อความแบบเต็มที่ได้รับการจดจำในรูปภาพและลําดับชั้นของออบเจ็กต์ที่แสดงถึงโครงสร้างของเอกสารที่จดจํา

สําหรับออบเจ็กต์ Block, Paragraph, Word และ Symbol แต่ละรายการ คุณจะดูข้อความที่ระบบจดจําได้ในภูมิภาคและพิกัดขอบเขตของภูมิภาค

เช่น

Java

String resultText = result.getText();
for (FirebaseVisionDocumentText.Block block: result.getBlocks()) {
    String blockText = block.getText();
    Float blockConfidence = block.getConfidence();
    List<RecognizedLanguage> blockRecognizedLanguages = block.getRecognizedLanguages();
    Rect blockFrame = block.getBoundingBox();
    for (FirebaseVisionDocumentText.Paragraph paragraph: block.getParagraphs()) {
        String paragraphText = paragraph.getText();
        Float paragraphConfidence = paragraph.getConfidence();
        List<RecognizedLanguage> paragraphRecognizedLanguages = paragraph.getRecognizedLanguages();
        Rect paragraphFrame = paragraph.getBoundingBox();
        for (FirebaseVisionDocumentText.Word word: paragraph.getWords()) {
            String wordText = word.getText();
            Float wordConfidence = word.getConfidence();
            List<RecognizedLanguage> wordRecognizedLanguages = word.getRecognizedLanguages();
            Rect wordFrame = word.getBoundingBox();
            for (FirebaseVisionDocumentText.Symbol symbol: word.getSymbols()) {
                String symbolText = symbol.getText();
                Float symbolConfidence = symbol.getConfidence();
                List<RecognizedLanguage> symbolRecognizedLanguages = symbol.getRecognizedLanguages();
                Rect symbolFrame = symbol.getBoundingBox();
            }
        }
    }
}

Kotlin

val resultText = result.text
for (block in result.blocks) {
    val blockText = block.text
    val blockConfidence = block.confidence
    val blockRecognizedLanguages = block.recognizedLanguages
    val blockFrame = block.boundingBox
    for (paragraph in block.paragraphs) {
        val paragraphText = paragraph.text
        val paragraphConfidence = paragraph.confidence
        val paragraphRecognizedLanguages = paragraph.recognizedLanguages
        val paragraphFrame = paragraph.boundingBox
        for (word in paragraph.words) {
            val wordText = word.text
            val wordConfidence = word.confidence
            val wordRecognizedLanguages = word.recognizedLanguages
            val wordFrame = word.boundingBox
            for (symbol in word.symbols) {
                val symbolText = symbol.text
                val symbolConfidence = symbol.confidence
                val symbolRecognizedLanguages = symbol.recognizedLanguages
                val symbolFrame = symbol.boundingBox
            }
        }
    }
}

ขั้นตอนถัดไป