Nhận dạng mực kỹ thuật số bằng Bộ công cụ máy học 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ụ máy học, bạn có thể nhận dạng chữ viết tay trên bề mặt kỹ thuật số bằng hàng trăm ngôn ngữ và phân loại các bản phác thảo.

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 mô-đun, thường là app/build.gradle:
dependencies {
  // ...
  implementation 'com.google.mlkit:digital-ink-recognition:18.0.0'
}

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

Xây dựng đố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() và hiển thị đoạn mã sau đây để lưu trữ các điểm trong nét vẽ 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 nhanh về Bộ công cụ máy học để có 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();

Nhận thực thể của DigitalInkRecognizer

Để thực hiện việc nhận dạng, hãy gửi thực thể Ink đến đối tượng DigitalInkRecognizer. Đoạn mã dưới đây cho biết cách tạo bản sao 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ý đố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));

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

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

Mặc dù API nhận dạng mực kỹ thuật 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. Cần khoảng 20 MB dung lượng lưu trữ cho mỗi ngôn ngữ. Điều này do đối tượng RemoteModelManager xử lý.

Tải 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ô 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 nhận dạng văn bản

Tính chính xác của tính năng nhận dạng văn bản có thể khác nhau giữa các 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 mực kỹ thuật số được đào tạo để xử lý nhiều kiểu viết, nhưng kết quả có thể khác nhau giữa các 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. Lưu ý rằng các kỹ thuật này không áp dụng cho các thuật toán phân loại bản vẽ biểu tượng cảm xúc, tự động vẽ và hình dạng.

Lĩnh vực viết

Nhiều ứng dụng có khu vực viết rõ ràng để 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 dựa trên 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ữ hoa và chữ thường "o" hoặc "c" và dấu phẩy và dấu gạch chéo lên.

Việc cho người 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 hai 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 WriteArea với chiều cao ước tính chính xác nhất về chiều cao của một dòng văn bản. Đối tượng WriteArea bạn chuyển đến trình nhận dạng không cần phải tương ứng chính xác với khu vực viết thực trên màn hình. Việc thay đổi chiều cao WriteArea theo cách này sẽ hiệu quả hơn ở một số ngôn ngữ so với các ngôn ngữ khác.

Khi bạn chỉ định vùng viết, hãy chỉ định chiều rộng và chiều cao của đơn vị đó trong cùng một đơn vị như tọa độ. Các đối số tọa độ x, y không yêu cầu đơ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. Bạn có thể truyền toạ độ theo bất kỳ cách nào phù hợp với hệ thống của mình.

Ngữ cảnh trước

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

Ví dụ: các chữ cái nguyền rủa "n" và "u" thường bị nhầm lẫn với nhau. Nếu người dùng đã nhập một phần từ "arg", họ có thể tiếp tục sử dụng các nét vẽ có thể được nhận dạng là "ument" hoặc "nment" Việc chỉ định ngữ cảnh trước "arg" giải quyết tình trạng không rõ ràng, vì từ "argument" có nhiều khả năng hơn "argnment"

Bối cảnh trước cũng có thể giúp người nhận dạng xác định dấu ngắt từ, khoảng trắng giữa các từ. Bạn có thể nhập một ký tự dấu cách nhưng không thể vẽ một ký tự, vậy làm thế nào để 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 với từ viết "world", nếu 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 "hello" thì mô hình sẽ trả về chuỗi " thế giới", với dấu cách ở đầu, vì "xin chao moi nguoi" có ý nghĩa hơn "helloword"

Bạn nên cung cấp chuỗi trước ngữ cảnh 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 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 khu vực 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);

Sắp xếp theo thứ tự nét chữ

Độ chính xác của nhận dạng nhạy cảm với thứ tự của các nét vẽ. Trình nhận dạng dự đoán rằng các nét văn sẽ xảy ra theo thứ tự mà mọi người sẽ tự nhiên viết; ví dụ từ trái sang phải cho tiếng Anh. Mọi trường hợp bắt đầu từ mẫu 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, sẽ mang lại kết quả ít 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. Bản sửa đổi có thể nằm ở giữa một câu, nhưng các nét vẽ dành cho bản sửa đổi này nằm ở cuối trình tự nét. Trong trường hợp này, bạn nên gửi riêng từ mới được viết tới API và hợp nhất kết quả với tính năng nhận dạng trước đó bằng logic của riêng bạn.

Xử lý các hình dạng không rõ ràng

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

Chúng tôi 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 số nhận dạng khi có sẵn. Chỉ các thuật toán phân loại hình dạng mới cung cấp điểm số. Nếu mô hình rất chắc chắn, điểm số của kết quả hàng đầu sẽ tốt hơn nhiều so với kết quả có thứ hạng cao thứ hai. Nếu không chắc chắn thì điểm số của hai kết quả hàng đầu sẽ gần đúng. Ngoài ra, hãy lưu ý rằng các thuật toán 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 dấu ba chấm bên cạnh nhau, thì trình nhận dạng có thể trả về kết quả này hoặc kết quả khác (hoặc một nội dung hoàn toàn khác) vì ứng dụng nhận dạng duy nhất đó không thể biểu thị hai hình dạng.