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 nền tảng 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ớ đưa kho lưu trữ Maven của Google vào cả hai phần buildscriptallprojects.
  2. Thêm các phần phụ thuộc cho thư viện Android Bộ công cụ học máy 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.1.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. Đang bật Trên thiết bị Android, bạn có thể sử dụng Canvas cho cho mục đích này. Thông tin trình xử lý sự kiện chạm nên gọi addNewTouchEvent() hiển thị đoạn mã sau để lưu trữ các điểm trong nét vẽ người dùng vẽ vào đối tượng Ink.

Mẫu chung này được minh hoạ trong đoạn mã sau. Xem Mẫu bắt đầu nhanh cho Bộ công cụ học máy để 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();

Tải một phiên bản của DigitalInkRecognitionr

Để thực hiện nhận dạng, hãy gửi thực thể Ink đến một Đối tượng DigitalInkRecognizer. Đoạn mã dưới đây cho thấy cách tạo thực thể 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 như được mô tả trong phần tiếp theo.

Quản lý lượt tải mô hình 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ữ ngôn ngữ yêu cầu một số dữ liệu phải được tải xuống trước khi bất kỳ nhận dạng nào được nhận dạng. Xung quanh Yêu cầu bộ nhớ 20 MB cho mỗi ngôn ngữ. Việc này được xử lý bởi Đối tượng RemoteModelManager.

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 kiểu máy 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 giữa các ngôn ngữ. Độ chính xác cũng phụ thuộc về phong cách viết. Mặc dù công nghệ Nhận dạng mực kỹ thuật số được huấn luyện để xử lý nhiều loại kiểu viết, 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. Lưu ý rằng những kỹ thuật này không áp dụng cho bộ 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ó vùng viết được xác định rõ ràng cho thao tác nhập của người dùng. Ý nghĩa của ký hiệu là được xác định một phần bởi kích thước tương ứng với kích thước của vùng viết có chứa ký tự đó. Ví dụ: sự khác biệt giữa chữ "o" viết thường hoặc viết hoa hoặc "c" và dấu phẩy so với dấu gạch chéo lên.

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 khách hàng thực tế diện tích viết đủ lớn để cho phép người dùng viết hai hoặc nhiều dòng, bạn có thể cải thiện hơn kết quả bằng cách chuyển vào một WriteArea có chiều cao là số liệu ướ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 mà bạn truyền đến trình nhận dạng không cần phải tương ứng chính xác với vùng viết thực trên màn hình. Thay đổi chiều cao của WriteArea theo cách này hoạt động tốt 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 vùng viết đó bằng cùng đơn vị với nét vẽ toạ độ. Đối số toạ độ x,y không có yêu cầu về đơn vị – API chuẩn hoá tất cả đơn vị, do đó, đ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 tuỳ ý chuyển toạ độ theo bất kỳ tỷ lệ nào phù hợp với hệ thống của bạn.

Bối cảnh trước

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

Ví dụ: chữ cái viết tay "n" và "u" thường bị nhầm lẫn với nhau. Nếu người dùng có đã nhập một phần từ "arg", chúng có thể tiếp tục với các nét chữ có thể được nhận ra là "ument" hoặc "nment". Chỉ định "arg" trước ngữ cảnh giải quyết sự không rõ ràng, vì từ "đối số" có nhiều khả năng hơn là "argnation".

Bối cảnh trước cũng có thể giúp trình nhận dạng xác định dấu ngắt từ, dấu cách giữa các từ. Bạn có thể nhập một ký tự dấu cách nhưng bạn không thể vẽ một ký tự, vậy làm thế nào một trình nhận dạng có thể xác định thời điểm một từ kết thúc và câu hỏi tiếp theo bắt đầu? Nếu người dùng đã viết "xin chào" và tiếp tục với từ được viết "world", không có ngữ cảnh trước trình nhận dạng trả về chuỗi "world". Tuy nhiên, nếu bạn chỉ định "hello", mô hình sẽ trả về chuỗi " thế giới", với một khoảng trống dẫn đầu, vì "xin chào" thế giới" sẽ có ý nghĩa hơn từ "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 không gian. Nếu chuỗi dài hơn thì trình nhận dạng chỉ sử dụng 20 ký tự cuối cùng.

Mã mẫu dưới đây cho biết cách xác định vùng viết và sử dụ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 vẽ

Độ chính xác của tính năng nhận dạng phụ thuộc vào thứ tự nét vẽ. Thiết bị nhận dạng mong muốn nét diễn ra theo thứ tự mà mọi người thường viết; ví dụ từ trái sang phải cho tiếng Anh. Mọi trường hợp giống như 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ẽ cho ra kết quả ít chính xác hơn.

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 câu, nhưng các nét của bản sửa đổi ở 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 tới API và hợp nhất kết quả nhờ những nhận biết trước đó bằng cách sử dụng logic của riêng bạn.

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

Có những trường hợp ý 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. Cho ví dụ: một hình chữ nhật có các cạnh rất 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 số nhận dạng (nếu có). Chỉ thuật toán phân loại hình dạng sẽ đưa ra điểm số. Nếu mô hình rất tự tin, điểm số của kết quả hàng đầu sẽ là tốt hơn nhiều so với ứng dụng tốt thứ hai. Nếu không chắc chắn, điểm số của hai kết quả hàng đầu sẽ ở gần nhau. Ngoài ra, xin 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 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 mỗi khác, trình nhận dạng có thể trả về một trong hai giá trị này (hoặc một giá trị hoàn toàn khác) dưới dạng một do một ứng viên công nhận duy nhất không thể biểu thị hai hình dạng.