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 bản phác thảo.
Dùng thử
- Thử dùng ứng dụng mẫu để xem ví dụ về cách sử dụng API này.
Trước khi bắt đầu
- Trong tệp
build.gradlecấp dự án, hãy nhớ thêm kho lưu trữ Maven của Google vào cả hai mụcbuildscriptvàallprojects. - Thêm các phần phụ thuộc cho thư viện Android của 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:19.0.0'
}
Bây giờ, 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 đố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 cảm ứng
của bạn
sẽ gọi phương thức addNewTouchEvent()
như minh hoạ trong đ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 bắt đầu nhanh của Bộ công cụ học máy để biết ví dụ đầy đủ 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 thực thể của DigitalInkRecognizer
Để thực hiện nhận dạng, hãy gửi thực thể Ink đến đối tượng
DigitalInkRecognizer. Mã bên dưới cho biết cách tạo 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ý đố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ư 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 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 có khoảng 20 MB dung lượng lưu trữ cho mỗi ngôn ngữ. Việc 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ô 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 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 huấn luyện để xử lý nhiều loại phong cách viết, nhưng kết quả có thể khác nhau giữa người dùng.
Dưới đâ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 các kỹ thuật này không áp dụng cho thuật toán phân loại bản vẽ cho biểu tượng cảm xúc, tính năng tự động vẽ và hình dạng.
Khu vực viết
Nhiều ứng dụng có một khu vực viết được xác định rõ ràng để hoạt động đầu vào của người dùng. Ý nghĩa của một ký hiệu được xác định một phần bởi kích thước của ký hiệu đó so với kích thước của khu vực viết chứa ký hiệu đó. Ví dụ: sự khác biệt giữa chữ "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 khu vực 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 khu vực viết chỉ chứa một dòng văn bản. Nếu khu vực 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 WritingArea có Chiều cao là ướ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 khu vực viết thực tế trên màn hình. Việc thay đổi chiều cao của WritingArea 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 khu vực viết, hãy chỉ định chiều rộng và chiều cao của khu vực đó theo cùng một đơ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ạ độ theo bất kỳ tỷ lệ nào có ý nghĩa đối 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 vẽ 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ề bối cảnh trước.
Ví dụ: các chữ cái viết liền "n" và "u" thường bị nhầm lẫn với nhau. Nếu người dùng đã nhập từ "arg" một phần, họ có thể tiếp tục với các nét vẽ có thể được nhận dạng là "ument" hoặc "nment". Việc chỉ định bối cảnh trước "arg" sẽ giải quyết sự mơ hồ, vì từ "argument" có nhiều khả năng hơn "argnment".
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ừ, khoảng cách giữa các từ. Bạn có thể nhập ký tự dấu cách nhưng không thể vẽ ký tự đó, vậy làm cách nào để trình nhận dạng xác định khi nào 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ừ "world" được viết, thì nếu không có bối 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 bối cảnh trước "hello", mô hình sẽ trả về chuỗi " world" (có dấu cách ở đầu) vì "hello world" có ý nghĩa hơn "helloword".
Bạn nên cung cấp chuỗi bối 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 chỉ sử dụng 20 ký tự cuối cùng.
Mã mẫu bên dưới cho biết cách xác định khu vực viết và sử dụng đối tượng RecognitionContext để chỉ định bối 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 thứ tự nét vẽ
Độ chính xác của tính năng 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 mong đợi các nét vẽ 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. Bất kỳ trường hợp nào khác với 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, đề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. Bản sửa đổi có thể ở giữa câu, nhưng các nét chữ cho bản sửa đổi nằm ở cuối chuỗi nét chữ.
Trong trường hợp này, bạn nên gửi riêng từ mới viết đến API và hợp nhất kết quả với các lần 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 ý nghĩa của hình dạng được cung cấp cho trình nhận dạng là mơ hồ. 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 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ỉ bộ phân loại hình dạng mới cung cấp điểm. Nếu mô hình rất tự tin, thì điểm của kết quả hàng đầu sẽ tốt hơn nhiều so với kết quả tốt thứ hai. Nếu có sự không chắc chắn, thì điểm của 2 kết quả hàng đầu sẽ gần nhau. Ngoài ra, hãy lưu ý rằng bộ phân loại hình dạng 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 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 ứng viên nhận dạng duy nhất không thể đại diện cho 2 hình dạng.