Nhận dạng văn bản trong hình ảnh bằng Bộ công cụ máy học trên Android

Bạn có thể sử dụng Bộ công cụ máy học để nhận dạng văn bản trong hình ảnh hoặc video, chẳng hạn như văn bản của biển báo đường phố. Đặc điểm chính của tính năng này là:

API nhận dạng văn bản v2
Nội dung mô tảNhận dạng văn bản trong hình ảnh hoặc video, hỗ trợ các chữ viết tiếng Latinh, tiếng Trung, Devanagari, tiếng Nhật và tiếng Hàn, cũng như nhiều ngôn ngữ.
Tên thư việncom.google.mlkit:text-recognition
com.google.mlkit:text-recognition-chinese
com.google.mlkit:text-recognition-devanagari
com.google.mlkit:text-recognition-japanese
com.google.mlkit:text-recognition-korean
Triển khaiThư viện được liên kết tĩnh với ứng dụng của bạn tại thời điểm xây dựng
Tác động đến kích thước ứng dụngKhoảng 4 MB cho mỗi cấu trúc
Hiệu suấtThư viện tập lệnh Latinh theo thời gian thực trên hầu hết các thiết bị, chậm hơn đối với các thiết bị khác.

Trước khi bắt đầu

  1. Trong tệp build.gradle cấp dự án, hãy nhớ thêm kho lưu trữ Maven của Google vào cả hai mục buildscriptallprojects.
  2. Thêm các phần phụ thuộc cho thư viện Android của Bộ công cụ máy học vào tệp gradle cấp ứng dụng của bạn, thường là app/build.gradle:

    dependencies {
      // To recognize Latin script
      implementation 'com.google.mlkit:text-recognition:16.0.0-beta6'
    
      // To recognize Chinese script
      implementation 'com.google.mlkit:text-recognition-chinese:16.0.0-beta6'
    
      // To recognize Devanagari script
      implementation 'com.google.mlkit:text-recognition-devanagari:16.0.0-beta6'
    
      // To recognize Japanese script
      implementation 'com.google.mlkit:text-recognition-japanese:16.0.0-beta6'
    
      // To recognize Korean script
      implementation 'com.google.mlkit:text-recognition-korean:16.0.0-beta6'
    }
    

1. Tạo một thực thể của TextRecognizer

Tạo một bản sao của TextRecognizer, chuyển các tuỳ chọn liên quan đến thư viện mà bạn đã khai báo phần phụ thuộc ở trên:

Kotlin

// When using Latin script library
val recognizer = TextRecognition.getClient(TextRecognizerOptions.DEFAULT_OPTIONS)

// When using Chinese script library
val recognizer = TextRecognition.getClient(ChineseTextRecognizerOptions.Builder().build())

// When using Devanagari script library
val recognizer = TextRecognition.getClient(DevanagariTextRecognizerOptions.Builder().build())

// When using Japanese script library
val recognizer = TextRecognition.getClient(JapaneseTextRecognizerOptions.Builder().build())

// When using Korean script library
val recognizer = TextRecognition.getClient(KoreanTextRecognizerOptions.Builder().build())

Java

// When using Latin script library
TextRecognizer recognizer =
  TextRecognition.getClient(TextRecognizerOptions.DEFAULT_OPTIONS);

// When using Chinese script library
TextRecognizer recognizer =
  TextRecognition.getClient(new ChineseTextRecognizerOptions.Builder().build());

// When using Devanagari script library
TextRecognizer recognizer =
  TextRecognition.getClient(new DevanagariTextRecognizerOptions.Builder().build());

// When using Japanese script library
TextRecognizer recognizer =
  TextRecognition.getClient(new JapaneseTextRecognizerOptions.Builder().build());

// When using Korean script library
TextRecognizer recognizer =
  TextRecognition.getClient(new KoreanTextRecognizerOptions.Builder().build());

2. Chuẩn bị hình ảnh nhập

Để nhận dạng văn bản trong một hình ảnh, tạo một đối tượng InputImage từ Bitmap, media.Image, ByteBuffer, mảng byte hoặc một tệp trên thiết bị. Sau đó, chuyển đối tượng InputImage sang phương thức TextRecognizer#39;s processImage.

Bạn có thể tạo một đối tượng InputImage từ nhiều nguồn, mỗi nguồn được giải thích ở bên dưới.

Sử dụng media.Image

Để tạo một đối tượng InputImage từ đối tượng media.Image, chẳng hạn như khi chụp ảnh từ máy ảnh của thiết bị, hãy truyền đối tượng media.Image và chế độ xoay của hình ảnh đến InputImage.fromMediaImage().

Nếu bạn sử dụng thư viện CameraX, thì các lớp OnImageCapturedListenerImageAnalysis.Analyzer sẽ tính toán giá trị xoay vòng cho bạn.

Kotlin

private class YourImageAnalyzer : ImageAnalysis.Analyzer {

    override fun analyze(imageProxy: ImageProxy) {
        val mediaImage = imageProxy.image
        if (mediaImage != null) {
            val image = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)
            // Pass image to an ML Kit Vision API
            // ...
        }
    }
}

Java

private class YourAnalyzer implements ImageAnalysis.Analyzer {

    @Override
    public void analyze(ImageProxy imageProxy) {
        Image mediaImage = imageProxy.getImage();
        if (mediaImage != null) {
          InputImage image =
                InputImage.fromMediaImage(mediaImage, imageProxy.getImageInfo().getRotationDegrees());
          // Pass image to an ML Kit Vision API
          // ...
        }
    }
}

Nếu không sử dụng thư viện máy ảnh cung cấp cho bạn mức độ xoay của hình ảnh, bạn có thể tính toán mức độ đó từ độ xoay của thiết bị và hướng của cảm biến máy ảnh trong thiết bị.

Kotlin

private val ORIENTATIONS = SparseIntArray()

init {
    ORIENTATIONS.append(Surface.ROTATION_0, 0)
    ORIENTATIONS.append(Surface.ROTATION_90, 90)
    ORIENTATIONS.append(Surface.ROTATION_180, 180)
    ORIENTATIONS.append(Surface.ROTATION_270, 270)
}

/**
 * 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, isFrontFacing: Boolean): 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)

    // Get the device's sensor orientation.
    val cameraManager = activity.getSystemService(CAMERA_SERVICE) as CameraManager
    val sensorOrientation = cameraManager
            .getCameraCharacteristics(cameraId)
            .get(CameraCharacteristics.SENSOR_ORIENTATION)!!

    if (isFrontFacing) {
        rotationCompensation = (sensorOrientation + rotationCompensation) % 360
    } else { // back-facing
        rotationCompensation = (sensorOrientation - rotationCompensation + 360) % 360
    }
    return rotationCompensation
}

Java

private static final SparseIntArray ORIENTATIONS = new SparseIntArray();
static {
    ORIENTATIONS.append(Surface.ROTATION_0, 0);
    ORIENTATIONS.append(Surface.ROTATION_90, 90);
    ORIENTATIONS.append(Surface.ROTATION_180, 180);
    ORIENTATIONS.append(Surface.ROTATION_270, 270);
}

/**
 * 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, boolean isFrontFacing)
        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);

    // Get the device's sensor orientation.
    CameraManager cameraManager = (CameraManager) activity.getSystemService(CAMERA_SERVICE);
    int sensorOrientation = cameraManager
            .getCameraCharacteristics(cameraId)
            .get(CameraCharacteristics.SENSOR_ORIENTATION);

    if (isFrontFacing) {
        rotationCompensation = (sensorOrientation + rotationCompensation) % 360;
    } else { // back-facing
        rotationCompensation = (sensorOrientation - rotationCompensation + 360) % 360;
    }
    return rotationCompensation;
}

Sau đó, hãy truyền đối tượng media.Image và giá trị độ xoay cho InputImage.fromMediaImage():

Kotlin

val image = InputImage.fromMediaImage(mediaImage, rotation)

Java

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

Sử dụng URI tệp

Để tạo một đối tượng InputImage từ URI tệp, hãy chuyển ngữ cảnh ứng dụng và URI tệp cho InputImage.fromFilePath(). Tính năng này rất hữu ích khi bạn sử dụng ý định ACTION_GET_CONTENT để nhắc người dùng chọn hình ảnh trong ứng dụng thư viện ảnh.

Kotlin

val image: InputImage
try {
    image = InputImage.fromFilePath(context, uri)
} catch (e: IOException) {
    e.printStackTrace()
}

Java

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

Sử dụng ByteBuffer hoặc ByteArray

Để tạo một đối tượng InputImage từ ByteBuffer hoặc ByteArray, trước tiên, hãy tính độ xoay hình ảnh như mô tả trước đó cho đầu vào media.Image. Sau đó, tạo đối tượng InputImage bằng vùng đệm hoặc mảng, cùng với chiều cao, chiều rộng, định dạng mã hóa màu và độ xoay của hình ảnh:

Kotlin

val image = InputImage.fromByteBuffer(
        byteBuffer,
        /* image width */ 480,
        /* image height */ 360,
        rotationDegrees,
        InputImage.IMAGE_FORMAT_NV21 // or IMAGE_FORMAT_YV12
)
// Or:
val image = InputImage.fromByteArray(
        byteArray,
        /* image width */ 480,
        /* image height */ 360,
        rotationDegrees,
        InputImage.IMAGE_FORMAT_NV21 // or IMAGE_FORMAT_YV12
)

Java

InputImage image = InputImage.fromByteBuffer(byteBuffer,
        /* image width */ 480,
        /* image height */ 360,
        rotationDegrees,
        InputImage.IMAGE_FORMAT_NV21 // or IMAGE_FORMAT_YV12
);
// Or:
InputImage image = InputImage.fromByteArray(
        byteArray,
        /* image width */480,
        /* image height */360,
        rotation,
        InputImage.IMAGE_FORMAT_NV21 // or IMAGE_FORMAT_YV12
);

Sử dụng Bitmap

Để tạo một đối tượng InputImage từ đối tượng Bitmap, hãy khai báo sau:

Kotlin

val image = InputImage.fromBitmap(bitmap, 0)

Java

InputImage image = InputImage.fromBitmap(bitmap, rotationDegree);

Hình ảnh này được biểu thị bằng một đối tượng Bitmap cùng với độ xoay.

3. Xử lý hình ảnh

Truyền hình ảnh đến phương thức process:

Kotlin

val result = recognizer.process(image)
        .addOnSuccessListener { visionText ->
            // Task completed successfully
            // ...
        }
        .addOnFailureListener { e ->
            // Task failed with an exception
            // ...
        }

Java

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

4. Trích xuất văn bản từ các khối văn bản được công nhận

Nếu thao tác nhận dạng văn bản thành công, đối tượng Text sẽ được chuyển đến trình nghe thành công. Đối tượng Text chứa toàn bộ văn bản được nhận dạng trong hình ảnh và không có hoặc có đối tượng TextBlock.

Mỗi TextBlock đại diện cho một khối văn bản hình chữ nhật, chứa không đối tượng Line trở lên. Mỗi đối tượng Line biểu thị một dòng văn bản, trong đó có 0 hoặc nhiều đối tượng Element. Mỗi đối tượng Element thể hiện một từ hoặc một thực thể giống từ, trong đó không có hoặc có nhiều đối tượng Symbol. Mỗi đối tượng Symbol thể hiện một ký tự, một chữ số hoặc một thực thể giống từ.

Đối với mỗi đối tượng TextBlock, Line, ElementSymbol, bạn có thể nhận dạng được văn bản trong vùng, tọa độ ranh giới của khu vực và nhiều thuộc tính khác như thông tin về chế độ xoay, điểm tin cậy v.v.

Ví dụ:

Kotlin

val resultText = result.text
for (block in result.textBlocks) {
    val blockText = block.text
    val blockCornerPoints = block.cornerPoints
    val blockFrame = block.boundingBox
    for (line in block.lines) {
        val lineText = line.text
        val lineCornerPoints = line.cornerPoints
        val lineFrame = line.boundingBox
        for (element in line.elements) {
            val elementText = element.text
            val elementCornerPoints = element.cornerPoints
            val elementFrame = element.boundingBox
        }
    }
}

Java

String resultText = result.getText();
for (Text.TextBlock block : result.getTextBlocks()) {
    String blockText = block.getText();
    Point[] blockCornerPoints = block.getCornerPoints();
    Rect blockFrame = block.getBoundingBox();
    for (Text.Line line : block.getLines()) {
        String lineText = line.getText();
        Point[] lineCornerPoints = line.getCornerPoints();
        Rect lineFrame = line.getBoundingBox();
        for (Text.Element element : line.getElements()) {
            String elementText = element.getText();
            Point[] elementCornerPoints = element.getCornerPoints();
            Rect elementFrame = element.getBoundingBox();
            for (Text.Symbol symbol : element.getSymbols()) {
                String symbolText = symbol.getText();
                Point[] symbolCornerPoints = symbol.getCornerPoints();
                Rect symbolFrame = symbol.getBoundingBox();
            }
        }
    }
}

Nhập nguyên tắc về hình ảnh

  • Để Bộ công cụ máy học nhận dạng chính xác văn bản, hình ảnh đầu vào phải chứa văn bản được biểu thị bằng đủ dữ liệu pixel. Tốt nhất là mỗi ký tự phải có kích thước tối thiểu là 16x16 pixel. Việc sử dụng các ký tự lớn hơn 24x24 pixel thường không mang lại lợi ích nào về độ chính xác.

    Ví dụ: hình ảnh 640x480 có thể hoạt động hiệu quả để quét danh thiếp chiếm toàn bộ chiều rộng của hình ảnh. Để quét tài liệu trên giấy khổ nhỏ, bạn có thể phải sử dụng hình ảnh có kích thước 720x1280 pixel.

  • Lấy nét hình ảnh kém có thể ảnh hưởng đến độ chính xác của nhận dạng văn bản. Nếu bạn không nhận được kết quả chấp nhận được, hãy thử yêu cầu người dùng chụp lại hình ảnh.

  • Nếu nhận ra văn bản trong một ứng dụng theo thời gian thực, bạn nên xem xét kích thước tổng thể của hình ảnh nhập vào. Hình ảnh nhỏ hơn có thể được xử lý nhanh hơn. Để giảm độ trễ, hãy đảm bảo rằng văn bản chiếm nhiều nhất có thể đối với hình ảnh và chụp ảnh ở độ phân giải thấp hơn (lưu ý đến các yêu cầu về độ chính xác nêu trên). Để biết thêm thông tin, hãy xem bài viết Mẹo cải thiện hiệu suất.

Mẹo cải thiện hiệu suất

  • Nếu bạn sử dụng API Camera hoặc camera2, hãy hạn chế các lệnh gọi đến trình phát hiện. Nếu có khung video mới trong khi trình phát hiện đang chạy, hãy thả khung hình. Hãy xem lớp VisionProcessorBase trong ứng dụng mẫu khởi động nhanh để biết ví dụ.
  • Nếu bạn sử dụng API CameraX, hãy nhớ đặt chiến lược backpressure thành giá trị mặc định ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST. Việc này đảm bảo mỗi lần chỉ phân phối một hình ảnh để phân tích. Nếu nhiều hình ảnh được tạo hơn khi trình phân tích bận, các hình ảnh đó sẽ tự động bị xóa và không được đưa vào hàng đợi phân phối. Khi hình ảnh đang được phân tích bị đóng bằng cách gọi ImageImage.Closed(), hình ảnh mới nhất sẽ được phân phối.
  • Nếu bạn sử dụng đầu ra của trình phát hiện để che phủ hình ảnh trên hình ảnh nhập vào, trước tiên, hãy lấy kết quả từ Bộ công cụ máy học, sau đó kết xuất hình ảnh và lớp phủ trong một bước duy nhất. Thao tác này sẽ xuất hiện trên bề mặt màn hình chỉ một lần cho mỗi khung đầu vào. Hãy xem các lớp CameraSourcePreview GraphicOverlay trong ứng dụng mẫu khởi động nhanh để xem ví dụ.
  • Nếu bạn sử dụng API Camera2, hãy chụp ảnh ở định dạng ImageFormat.YUV_420_888. Nếu bạn sử dụng API Máy ảnh cũ, hãy chụp ảnh ở định dạng ImageFormat.NV21.
  • Hãy cân nhắc chụp ảnh ở độ phân giải thấp hơn. Tuy nhiên, bạn cần lưu ý rằng các yêu cầu về kích thước hình ảnh của API này.