Nhận dạng mực kỹ thuật số bằng Bộ công cụ học máy trên Android

Với tính năng nhận dạng mực kỹ thuật số của Bộ công cụ học máy, bạn có thể nhận dạng văn bản viết tay trên một bề mặt kỹ thuật số bằng hàng trăm ngôn ngữ, cũng như phân loại các bản phác thảo.

Dùng thử

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 ML Kit Android vào tệp Gradle cấp ứng dụng của mô-đun, thường là app/build.gradle:
dependencies {
  // ...
  implementation 'com.google.mlkit:digital-ink-recognition:19.0.0'
}

Giờ đây, bạn đã sẵn sàng bắt đầu nhận dạng văn bản trong các đối tượng Ink.

Tạo một đối tượng Ink

Cách chính để tạo đối tượng Ink là vẽ đối tượng đó trên màn hình cảm ứng. Trên Android, bạn có thể sử dụng Canvas cho mục đích này. Trình xử lý sự kiện chạm của bạn phải gọi phương thức addNewTouchEvent() như minh hoạ trong đoạn mã sau để lưu trữ các điểm trong nét mà người dùng vẽ vào đối tượng Ink.

Mẫu chung này được minh hoạ trong đoạn mã sau. Hãy xem mẫu bắt đầu nhanh của ML Kit để tham khảo một ví dụ hoàn chỉnh hơn.

Kotlin

var inkBuilder = Ink.builder()
lateinit var strokeBuilder: Ink.Stroke.Builder

// Call this each time there is a new event.
fun addNewTouchEvent(event: MotionEvent) {
  val action = event.actionMasked
  val x = event.x
  val y = event.y
  var t = System.currentTimeMillis()

  // If your setup does not provide timing information, you can omit the
  // third paramater (t) in the calls to Ink.Point.create
  when (action) {
    MotionEvent.ACTION_DOWN -> {
      strokeBuilder = Ink.Stroke.builder()
      strokeBuilder.addPoint(Ink.Point.create(x, y, t))
    }
    MotionEvent.ACTION_MOVE -> strokeBuilder!!.addPoint(Ink.Point.create(x, y, t))
    MotionEvent.ACTION_UP -> {
      strokeBuilder.addPoint(Ink.Point.create(x, y, t))
      inkBuilder.addStroke(strokeBuilder.build())
    }
    else -> {
      // Action not relevant for ink construction
    }
  }
}

...

// This is what to send to the recognizer.
val ink = inkBuilder.build()

Java

Ink.Builder inkBuilder = Ink.builder();
Ink.Stroke.Builder strokeBuilder;

// Call this each time there is a new event.
public void addNewTouchEvent(MotionEvent event) {
  float x = event.getX();
  float y = event.getY();
  long t = System.currentTimeMillis();

  // If your setup does not provide timing information, you can omit the
  // third paramater (t) in the calls to Ink.Point.create
  int action = event.getActionMasked();
  switch (action) {
    case MotionEvent.ACTION_DOWN:
      strokeBuilder = Ink.Stroke.builder();
      strokeBuilder.addPoint(Ink.Point.create(x, y, t));
      break;
    case MotionEvent.ACTION_MOVE:
      strokeBuilder.addPoint(Ink.Point.create(x, y, t));
      break;
    case MotionEvent.ACTION_UP:
      strokeBuilder.addPoint(Ink.Point.create(x, y, t));
      inkBuilder.addStroke(strokeBuilder.build());
      strokeBuilder = null;
      break;
  }
}

...

// This is what to send to the recognizer.
Ink ink = inkBuilder.build();

Tạo một thực thể DigitalInkRecognizer

Để thực hiện hoạt động nhận dạng, hãy gửi phiên bản Ink đến một đối tượng DigitalInkRecognizer. Đoạn mã bên dưới cho thấy cách tạo một thực thể của trình nhận dạng như vậy từ thẻ BCP-47.

Kotlin

// Specify the recognition model for a language
var modelIdentifier: DigitalInkRecognitionModelIdentifier
try {
  modelIdentifier = DigitalInkRecognitionModelIdentifier.fromLanguageTag("en-US")
} catch (e: MlKitException) {
  // language tag failed to parse, handle error.
}
if (modelIdentifier == null) {
  // no model was found, handle error.
}
var model: DigitalInkRecognitionModel =
    DigitalInkRecognitionModel.builder(modelIdentifier).build()


// Get a recognizer for the language
var recognizer: DigitalInkRecognizer =
    DigitalInkRecognition.getClient(
        DigitalInkRecognizerOptions.builder(model).build())

Java

// Specify the recognition model for a language
DigitalInkRecognitionModelIdentifier modelIdentifier;
try {
  modelIdentifier =
    DigitalInkRecognitionModelIdentifier.fromLanguageTag("en-US");
} catch (MlKitException e) {
  // language tag failed to parse, handle error.
}
if (modelIdentifier == null) {
  // no model was found, handle error.
}

DigitalInkRecognitionModel model =
    DigitalInkRecognitionModel.builder(modelIdentifier).build();

// Get a recognizer for the language
DigitalInkRecognizer recognizer =
    DigitalInkRecognition.getClient(
        DigitalInkRecognizerOptions.builder(model).build());

Xử lý một đối tượng Ink

Kotlin

recognizer.recognize(ink)
    .addOnSuccessListener { result: RecognitionResult ->
      // `result` contains the recognizer's answers as a RecognitionResult.
      // Logs the text from the top candidate.
      Log.i(TAG, result.candidates[0].text)
    }
    .addOnFailureListener { e: Exception ->
      Log.e(TAG, "Error during recognition: $e")
    }

Java

recognizer.recognize(ink)
    .addOnSuccessListener(
        // `result` contains the recognizer's answers as a RecognitionResult.
        // Logs the text from the top candidate.
        result -> Log.i(TAG, result.getCandidates().get(0).getText()))
    .addOnFailureListener(
        e -> Log.e(TAG, "Error during recognition: " + e));

Đoạn mã mẫu ở trên giả định rằng mô hình nhận dạng đã được tải xuống, như mô tả trong phần tiếp theo.

Quản lý việc tải mô hình xuống

Mặc dù API nhận dạng chữ viết tay dạng số hỗ trợ hàng trăm ngôn ngữ, nhưng mỗi ngôn ngữ đều yêu cầu tải một số dữ liệu xuống trước khi nhận dạng. Mỗi ngôn ngữ cần khoảng 20 MB dung lượng lưu trữ. Việc này do đối tượng RemoteModelManager xử lý.

Tải một mô hình mới xuống

Kotlin

import com.google.mlkit.common.model.DownloadConditions
import com.google.mlkit.common.model.RemoteModelManager

var model: DigitalInkRecognitionModel =  ...
val remoteModelManager = RemoteModelManager.getInstance()

remoteModelManager.download(model, DownloadConditions.Builder().build())
    .addOnSuccessListener {
      Log.i(TAG, "Model downloaded")
    }
    .addOnFailureListener { e: Exception ->
      Log.e(TAG, "Error while downloading a model: $e")
    }

Java

import com.google.mlkit.common.model.DownloadConditions;
import com.google.mlkit.common.model.RemoteModelManager;

DigitalInkRecognitionModel model = ...;
RemoteModelManager remoteModelManager = RemoteModelManager.getInstance();

remoteModelManager
    .download(model, new DownloadConditions.Builder().build())
    .addOnSuccessListener(aVoid -> Log.i(TAG, "Model downloaded"))
    .addOnFailureListener(
        e -> Log.e(TAG, "Error while downloading a model: " + e));

Kiểm tra xem một mô hình đã được tải xuống hay chưa

Kotlin

var model: DigitalInkRecognitionModel =  ...
remoteModelManager.isModelDownloaded(model)

Java

DigitalInkRecognitionModel model = ...;
remoteModelManager.isModelDownloaded(model);

Xoá mô hình đã tải xuống

Việc xoá một mô hình khỏi bộ nhớ của thiết bị sẽ giải phóng dung lượng.

Kotlin

var model: DigitalInkRecognitionModel =  ...
remoteModelManager.deleteDownloadedModel(model)
    .addOnSuccessListener {
      Log.i(TAG, "Model successfully deleted")
    }
    .addOnFailureListener { e: Exception ->
      Log.e(TAG, "Error while deleting a model: $e")
    }

Java

DigitalInkRecognitionModel model = ...;
remoteModelManager.deleteDownloadedModel(model)
                  .addOnSuccessListener(
                      aVoid -> Log.i(TAG, "Model successfully deleted"))
                  .addOnFailureListener(
                      e -> Log.e(TAG, "Error while deleting a model: " + e));

Mẹo cải thiện độ chính xác của tính năng nhận dạng văn bản

Độ chính xác của tính năng nhận dạng văn bản có thể khác nhau tuỳ theo ngôn ngữ. Độ chính xác cũng phụ thuộc vào phong cách viết. Mặc dù tính năng Nhận dạng chữ viết tay được huấn luyện để xử lý nhiều kiểu chữ viết, nhưng kết quả có thể khác nhau tuỳ theo người dùng.

Sau đây là một số cách cải thiện độ chính xác của trình nhận dạng văn bản. Xin lưu ý rằng những kỹ thuật này không áp dụng cho các trình phân loại bản vẽ đối với biểu tượng cảm xúc, tính năng vẽ tự động và hình dạng.

Khu vực viết

Nhiều ứng dụng có một vùng viết được xác định rõ để người dùng nhập dữ liệu. Ý nghĩa của một biểu tượng được xác định một phần theo kích thước của biểu tượng đó so với kích thước của vùng viết chứa biểu tượng đó. Ví dụ: sự khác biệt giữa chữ cái "o" hoặc "c" viết thường hoặc viết hoa, và dấu phẩy so với dấu gạch chéo.

Việc cho trình nhận dạng biết chiều rộng và chiều cao của vùng viết có thể cải thiện độ chính xác. Tuy nhiên, trình nhận dạng giả định rằng vùng viết chỉ chứa một dòng văn bản. Nếu vùng viết thực tế đủ lớn để cho phép người dùng viết từ 2 dòng trở lên, bạn có thể nhận được kết quả tốt hơn bằng cách truyền vào một WritingArea có chiều cao là mức ước tính tốt nhất của bạn về chiều cao của một dòng văn bản. Đối tượng WritingArea mà bạn truyền đến trình nhận dạng không nhất thiết phải tương ứng chính xác với vùng viết thực trên màn hình. Việc thay đổi chiều cao WritingArea theo cách này sẽ hiệu quả hơn ở một số ngôn ngữ so với những ngôn ngữ khác.

Khi chỉ định vùng viết, hãy chỉ định chiều rộng và chiều cao của vùng đó bằng cùng đơn vị với toạ độ nét vẽ. Các đối số toạ độ x, y không có yêu cầu về đơn vị – API chuẩn hoá tất cả các đơn vị, vì vậy, điều duy nhất quan trọng là kích thước và vị trí tương đối của nét vẽ. Bạn có thể truyền toạ độ ở bất kỳ tỷ lệ nào phù hợp với hệ thống của mình.

Bối cảnh trước

Bối cảnh trước là văn bản xuất hiện ngay trước các nét trong Ink mà bạn đang cố gắng nhận dạng. Bạn có thể giúp trình nhận dạng bằng cách cho trình nhận dạng biết về ngữ cảnh trước.

Ví dụ: chữ "n" và "u" viết thảo thường bị nhầm lẫn với nhau. Nếu người dùng đã nhập một phần của từ "arg", họ có thể tiếp tục nhập các nét có thể được nhận dạng là "ument" hoặc "nment". Việc chỉ định "arg" trong ngữ cảnh trước sẽ giải quyết sự mơ hồ, vì từ "argument" (đối số) có khả năng xuất hiện hơn "argnment".

Ngữ cảnh trước cũng có thể giúp trình nhận dạng xác định dấu ngắt từ, tức là khoảng trống giữa các từ. Bạn có thể nhập một ký tự khoảng trắng nhưng không thể vẽ ký tự đó. Vậy làm cách nào để một trình nhận dạng xác định thời điểm một từ kết thúc và từ tiếp theo bắt đầu? Nếu người dùng đã viết "hello" và tiếp tục viết từ "world", thì không có ngữ cảnh trước, trình nhận dạng sẽ trả về chuỗi "world". Tuy nhiên, nếu bạn chỉ định ngữ cảnh trước là "hello", thì mô hình sẽ trả về chuỗi " world" (có khoảng trắng ở đầu), vì "hello world" có nghĩa hơn "helloword".

Bạn nên cung cấp chuỗi ngữ cảnh trước dài nhất có thể, tối đa 20 ký tự, bao gồm cả dấu cách. Nếu chuỗi dài hơn, trình nhận dạng sẽ chỉ sử dụng 20 ký tự cuối cùng.

Mã mẫu bên dưới cho thấy cách xác định một vùng viết và sử dụng đối tượng RecognitionContext để chỉ định ngữ cảnh trước.

Kotlin

var preContext : String = ...;
var width : Float = ...;
var height : Float = ...;
val recognitionContext : RecognitionContext =
    RecognitionContext.builder()
        .setPreContext(preContext)
        .setWritingArea(WritingArea(width, height))
        .build()

recognizer.recognize(ink, recognitionContext)

Java

String preContext = ...;
float width = ...;
float height = ...;
RecognitionContext recognitionContext =
    RecognitionContext.builder()
                      .setPreContext(preContext)
                      .setWritingArea(new WritingArea(width, height))
                      .build();

recognizer.recognize(ink, recognitionContext);

Thứ tự nét

Độ chính xác của tính năng nhận dạng phụ thuộc vào thứ tự của các nét. Trình nhận dạng mong đợi các nét xuất hiện theo thứ tự mà mọi người thường viết; ví dụ: từ trái sang phải đối với tiếng Anh. Mọi trường hợp đi ngược lại quy tắc này, chẳng hạn như viết một câu tiếng Anh bắt đầu bằng từ cuối cùng, đều cho kết quả kém chính xác hơn.

Một ví dụ khác là khi một từ ở giữa Ink bị xoá và thay thế bằng một từ khác. Nội dung sửa đổi có thể nằm ở giữa câu, nhưng các nét vẽ cho nội dung sửa đổi lại nằm ở cuối chuỗi nét vẽ. Trong trường hợp này, bạn nên gửi riêng từ mới viết cho API và hợp nhất kết quả với các kết quả nhận dạng trước đó bằng logic của riêng bạn.

Xử lý các hình dạng mơ hồ

Có những trường hợp mà ý nghĩa của hình dạng được cung cấp cho trình nhận dạng là không rõ ràng. Ví dụ: một hình chữ nhật có các cạnh bo tròn có thể được xem là hình chữ nhật hoặc hình elip.

Bạn có thể xử lý những trường hợp không rõ ràng này bằng cách sử dụng điểm nhận dạng khi có. Chỉ các trình phân loại hình dạng mới cung cấp điểm số. Nếu mô hình rất tự tin, thì điểm số của kết quả hàng đầu sẽ cao hơn nhiều so với kết quả tốt thứ hai. Nếu có sự không chắc chắn, điểm số của 2 kết quả hàng đầu sẽ gần nhau. Ngoài ra, hãy lưu ý rằng các trình phân loại hình dạng sẽ diễn giải toàn bộ Ink dưới dạng một hình dạng duy nhất. Ví dụ: nếu Ink chứa một hình chữ nhật và một hình elip bên cạnh nhau, thì trình nhận dạng có thể trả về hình này hoặc hình kia (hoặc một hình hoàn toàn khác) làm kết quả, vì một đề xuất nhận dạng duy nhất không thể biểu thị hai hình dạng.