Nhận dạng chữ viết tay của người dùng

API Nhận dạng chữ viết tay cho phép bạn nhận dạng văn bản từ dữ liệu đầu vào viết tay ngay khi dữ liệu đó xuất hiện.

Handwriting Recognition API là gì?

API Nhận dạng chữ viết tay cho phép bạn chuyển đổi chữ viết tay (mực) của người dùng thành văn bản. Một số hệ điều hành đã có sẵn các API như vậy từ lâu và nhờ tính năng mới này, các ứng dụng web của bạn cuối cùng cũng có thể sử dụng chức năng này. Lượt chuyển đổi diễn ra trực tiếp trên thiết bị của người dùng, hoạt động ngay cả ở chế độ ngoại tuyến mà không cần thêm bất kỳ thư viện hoặc dịch vụ nào của bên thứ ba.

API này triển khai cái gọi là tính năng nhận dạng "trực tuyến" hoặc gần như theo thời gian thực. Điều này có nghĩa là dữ liệu đầu vào viết tay được nhận dạng trong khi người dùng vẽ bằng cách ghi lại và phân tích các nét vẽ riêng lẻ. Ngược lại với các quy trình "ngoại tuyến" như Nhận dạng ký tự quang học (OCR), trong đó chỉ có sản phẩm cuối cùng được biết, các thuật toán trực tuyến có thể cung cấp độ chính xác cao hơn do các tín hiệu bổ sung như chuỗi thời gian và áp lực của từng nét mực riêng lẻ.

Các trường hợp sử dụng được đề xuất cho Handwriting Recognition API

Ví dụ về cách sử dụng:

  • Các ứng dụng ghi chú mà người dùng muốn ghi lại ghi chú viết tay và dịch ghi chú đó thành văn bản.
  • Các ứng dụng biểu mẫu mà người dùng có thể sử dụng bút cảm ứng hoặc ngón tay để nhập dữ liệu do hạn chế về thời gian.
  • Những trò chơi yêu cầu điền chữ cái hoặc số, chẳng hạn như trò chơi ô chữ, trò chơi treo cổ hoặc trò chơi sudoku.

Trạng thái hiện tại

Handwriting Recognition API (API Nhận dạng chữ viết tay) có trong (Chromium 99).

Cách sử dụng Handwriting Recognition API

Phát hiện đối tượng

Phát hiện khả năng hỗ trợ của trình duyệt bằng cách kiểm tra sự tồn tại của phương thức createHandwritingRecognizer() trên đối tượng trình điều hướng:

if ('createHandwritingRecognizer' in navigator) {
  // 🎉 The Handwriting Recognition API is supported!
}

Các khái niệm cốt lõi

Handwriting Recognition API (API Nhận dạng chữ viết tay) chuyển đổi dữ liệu đầu vào viết tay thành văn bản, bất kể phương thức nhập (chuột, thao tác chạm, bút cảm ứng). API này có 4 thực thể chính:

  1. Điểm biểu thị vị trí của con trỏ tại một thời điểm cụ thể.
  2. Một nét vẽ bao gồm một hoặc nhiều điểm. Quá trình ghi lại một nét bắt đầu khi người dùng đặt con trỏ xuống (tức là nhấp vào nút chuột chính hoặc chạm vào màn hình bằng bút cảm ứng hoặc ngón tay) và kết thúc khi họ nhấc con trỏ lên.
  3. Một hình vẽ bao gồm một hoặc nhiều nét vẽ. Hoạt động nhận dạng thực tế diễn ra ở cấp độ này.
  4. Trình nhận dạng được định cấu hình bằng ngôn ngữ nhập dự kiến. Phương thức này dùng để tạo một phiên bản bản vẽ có cấu hình trình nhận dạng được áp dụng.

Những khái niệm này được triển khai dưới dạng các giao diện và từ điển cụ thể mà tôi sẽ đề cập ngay sau đây.

Các thực thể cốt lõi của API Nhận dạng chữ viết tay: Một hoặc nhiều điểm tạo thành một nét, một hoặc nhiều nét tạo thành một bản vẽ mà trình nhận dạng tạo ra. Hoạt động nhận dạng thực tế diễn ra ở cấp bản vẽ.

Tạo một trình nhận dạng

Để nhận dạng văn bản từ dữ liệu đầu vào viết tay, bạn cần lấy một thực thể của HandwritingRecognizer bằng cách gọi navigator.createHandwritingRecognizer() và truyền các ràng buộc đến thực thể đó. Các quy tắc ràng buộc xác định mô hình nhận dạng chữ viết tay cần được sử dụng. Hiện tại, bạn có thể chỉ định một danh sách ngôn ngữ theo thứ tự ưu tiên:

const recognizer = await navigator.createHandwritingRecognizer({
  languages: ['en'],
});

Phương thức này trả về một lời hứa phân giải bằng một thực thể của HandwritingRecognizer khi trình duyệt có thể thực hiện yêu cầu của bạn. Nếu không, phương thức này sẽ từ chối lời hứa bằng một lỗi và tính năng nhận dạng chữ viết tay sẽ không hoạt động. Vì lý do này, trước tiên, bạn nên truy vấn khả năng hỗ trợ của trình nhận dạng đối với các tính năng nhận dạng cụ thể.

Truy vấn hỗ trợ của trình nhận dạng

Bằng cách gọi navigator.queryHandwritingRecognizer(), bạn có thể kiểm tra xem nền tảng mục tiêu có hỗ trợ các tính năng nhận dạng chữ viết tay mà bạn dự định sử dụng hay không. Phương thức này lấy cùng một đối tượng ràng buộc như phương thức navigator.createHandwritingRecognizer(), chứa danh sách các ngôn ngữ được yêu cầu. Phương thức này trả về một lời hứa phân giải bằng một đối tượng kết quả nếu tìm thấy một trình nhận dạng tương thích. Nếu không, lời hứa sẽ phân giải thành null. Trong ví dụ sau, nhà phát triển:

  • muốn phát hiện văn bản bằng tiếng Anh
  • nhận các dự đoán thay thế, ít có khả năng xảy ra hơn (nếu có)
  • truy cập vào kết quả phân đoạn, tức là các ký tự được nhận dạng, bao gồm cả các điểm và nét tạo nên các ký tự đó
const result =
  await navigator.queryHandwritingRecognizerSupport({
    languages: ['en']
  });

console.log(result?.textAlternatives); // true if alternatives are supported
console.log(result?.textSegmentation); // true if segmentation is supported

Nếu trình duyệt hỗ trợ tính năng mà nhà phát triển cần, thì giá trị của tính năng đó sẽ được đặt thành true trong đối tượng kết quả. Nếu không, giá trị này sẽ được đặt thành false. Bạn có thể sử dụng thông tin này để bật hoặc tắt một số tính năng trong ứng dụng của mình hoặc gửi một cụm từ tìm kiếm mới cho một nhóm ngôn ngữ khác.

Bắt đầu vẽ

Trong ứng dụng của mình, bạn nên cung cấp một vùng nhập dữ liệu để người dùng nhập dữ liệu viết tay. Vì lý do hiệu suất, bạn nên triển khai việc này bằng sự trợ giúp của đối tượng canvas. Việc triển khai chính xác phần này nằm ngoài phạm vi của bài viết này, nhưng bạn có thể tham khảo bản minh hoạ để xem cách thực hiện.

Để bắt đầu vẽ một hình mới, hãy gọi phương thức startDrawing() trên trình nhận dạng. Phương thức này lấy một đối tượng chứa nhiều gợi ý để tinh chỉnh thuật toán nhận dạng. Bạn không bắt buộc phải cung cấp tất cả các gợi ý:

  • Loại văn bản đang được nhập: văn bản, địa chỉ email, số hoặc một ký tự riêng lẻ (recognitionType)
  • Loại thiết bị đầu vào: chuột, thao tác chạm hoặc bút cảm ứng (inputType)
  • Văn bản trước đó (textContext)
  • Số lượng dự đoán thay thế ít có khả năng xảy ra cần được trả về (alternatives)
  • Danh sách các ký tự mà người dùng có thể nhận dạng ("đơn vị chữ viết") mà người dùng có nhiều khả năng sẽ nhập (graphemeSet)

Handwriting Recognition API hoạt động tốt với Pointer Events (Sự kiện con trỏ). API này cung cấp một giao diện trừu tượng để sử dụng dữ liệu đầu vào từ mọi thiết bị trỏ. Các đối số sự kiện con trỏ chứa loại con trỏ đang được sử dụng. Điều này có nghĩa là bạn có thể sử dụng các sự kiện con trỏ để tự động xác định loại dữ liệu đầu vào. Trong ví dụ sau, bản vẽ cho tính năng nhận dạng chữ viết tay sẽ tự động được tạo khi sự kiện pointerdown xảy ra lần đầu tiên trên vùng chữ viết tay. Vì pointerType có thể trống hoặc được đặt thành một giá trị độc quyền, nên tôi đã giới thiệu một quy trình kiểm tra tính nhất quán để đảm bảo chỉ các giá trị được hỗ trợ mới được đặt cho loại đầu vào của bản vẽ.

let drawing;
let activeStroke;

canvas.addEventListener('pointerdown', (event) => {
  if (!drawing) {
    drawing = recognizer.startDrawing({
      recognitionType: 'text', // email, number, per-character
      inputType: ['mouse', 'touch', 'stylus'].find((type) => type === event.pointerType),
      textContext: 'Hello, ',
      alternatives: 2,
      graphemeSet: ['f', 'i', 'z', 'b', 'u'], // for a fizz buzz entry form
    });
  }
  startStroke(event);
});

Thêm nét vẽ

Sự kiện pointerdown cũng là nơi thích hợp để bắt đầu một nét mới. Để thực hiện việc này, hãy tạo một phiên bản mới của HandwritingStroke. Ngoài ra, bạn nên lưu trữ thời gian hiện tại làm điểm tham chiếu cho các điểm tiếp theo được thêm vào:

function startStroke(event) {
  activeStroke = {
    stroke: new HandwritingStroke(),
    startTime: Date.now(),
  };
  addPoint(event);
}

Thêm một điểm

Sau khi tạo nét vẽ, bạn nên thêm trực tiếp điểm đầu tiên vào nét vẽ đó. Vì sau này bạn sẽ thêm nhiều điểm hơn, nên việc triển khai logic tạo điểm trong một phương thức riêng là hợp lý. Trong ví dụ sau, phương thức addPoint() sẽ tính toán thời gian đã trôi qua kể từ dấu thời gian tham chiếu. Thông tin về thời gian là không bắt buộc, nhưng có thể cải thiện chất lượng nhận dạng. Sau đó, nó đọc toạ độ X và Y từ sự kiện con trỏ rồi thêm điểm vào nét hiện tại.

function addPoint(event) {
  const timeElapsed = Date.now() - activeStroke.startTime;
  activeStroke.stroke.addPoint({
    x: event.offsetX,
    y: event.offsetY,
    t: timeElapsed,
  });
}

Trình xử lý sự kiện pointermove được gọi khi con trỏ di chuyển trên màn hình. Bạn cũng cần thêm những điểm đó vào nét vẽ. Sự kiện này cũng có thể được kích hoạt nếu con trỏ không ở trạng thái "xuống", chẳng hạn như khi di chuyển con trỏ trên màn hình mà không nhấn nút chuột. Trình xử lý sự kiện trong ví dụ sau đây sẽ kiểm tra xem có nét vẽ đang hoạt động hay không và thêm điểm mới vào nét vẽ đó.

canvas.addEventListener('pointermove', (event) => {
  if (activeStroke) {
    addPoint(event);
  }
});

Nhận dạng văn bản

Khi người dùng nhấc con trỏ lên lần nữa, bạn có thể thêm nét vẽ vào bản vẽ bằng cách gọi phương thức addStroke() của nét vẽ đó. Ví dụ sau đây cũng đặt lại activeStroke, vì vậy trình xử lý pointermove sẽ không thêm điểm vào nét đã hoàn thành.

Tiếp theo, bạn cần nhận dạng dữ liệu đầu vào của người dùng bằng cách gọi phương thức getPrediction() trên bản vẽ. Quá trình nhận dạng thường mất chưa đến vài trăm mili giây, vì vậy, bạn có thể chạy các dự đoán nhiều lần nếu cần. Ví dụ sau đây chạy một cụm từ gợi ý mới sau mỗi nét hoàn chỉnh.

canvas.addEventListener('pointerup', async (event) => {
  drawing.addStroke(activeStroke.stroke);
  activeStroke = null;

  const [mostLikelyPrediction, ...lessLikelyAlternatives] = await drawing.getPrediction();
  if (mostLikelyPrediction) {
    console.log(mostLikelyPrediction.text);
  }
  lessLikelyAlternatives?.forEach((alternative) => console.log(alternative.text));
});

Phương thức này trả về một lời hứa được giải quyết bằng một mảng các dự đoán được sắp xếp theo mức độ phù hợp. Số lượng phần tử phụ thuộc vào giá trị mà bạn đã truyền đến gợi ý alternatives. Bạn có thể dùng mảng này để cho người dùng lựa chọn trong số các kết quả trùng khớp có thể có và yêu cầu họ chọn một lựa chọn. Ngoài ra, bạn có thể chỉ cần chọn kết quả dự đoán có khả năng xảy ra nhất, đó là những gì tôi làm trong ví dụ.

Đối tượng dự đoán chứa văn bản được nhận dạng và kết quả phân đoạn không bắt buộc mà tôi sẽ thảo luận trong phần sau.

Thông tin chi tiết với kết quả phân đoạn

Nếu được nền tảng mục tiêu hỗ trợ, đối tượng dự đoán cũng có thể chứa kết quả phân đoạn. Đây là một mảng chứa tất cả các đoạn chữ viết tay được nhận dạng, là sự kết hợp giữa ký tự do người dùng xác định được nhận dạng (grapheme) cùng với vị trí của ký tự đó trong văn bản được nhận dạng (beginIndex, endIndex) và các nét cũng như điểm đã tạo ra ký tự đó.

if (mostLikelyPrediction.segmentationResult) {
  mostLikelyPrediction.segmentationResult.forEach(
    ({ grapheme, beginIndex, endIndex, drawingSegments }) => {
      console.log(grapheme, beginIndex, endIndex);
      drawingSegments.forEach(({ strokeIndex, beginPointIndex, endPointIndex }) => {
        console.log(strokeIndex, beginPointIndex, endPointIndex);
      });
    },
  );
}

Bạn có thể sử dụng thông tin này để theo dõi lại các chữ cái được nhận dạng trên canvas.

Các hộp được vẽ xung quanh mỗi chữ cái được nhận dạng

Hoàn tất quy trình nhận dạng

Sau khi quá trình nhận dạng hoàn tất, bạn có thể giải phóng tài nguyên bằng cách gọi phương thức clear() trên HandwritingDrawing và phương thức finish() trên HandwritingRecognizer:

drawing.clear();
recognizer.finish();

Bản minh hoạ

Thành phần web <handwriting-textarea> triển khai một chế độ kiểm soát chỉnh sửa được cải tiến dần, có khả năng nhận dạng chữ viết tay. Khi nhấp vào nút ở góc dưới cùng bên phải của chế độ chỉnh sửa, bạn sẽ kích hoạt chế độ vẽ. Khi bạn hoàn tất việc vẽ, thành phần web sẽ tự động bắt đầu nhận dạng và thêm văn bản đã nhận dạng trở lại chế độ kiểm soát chỉnh sửa. Nếu Handwriting Recognition API (API Nhận dạng chữ viết tay) hoàn toàn không được hỗ trợ hoặc nền tảng không hỗ trợ các tính năng được yêu cầu, thì nút chỉnh sửa sẽ bị ẩn. Tuy nhiên, bạn vẫn có thể dùng chế độ chỉnh sửa cơ bản dưới dạng <textarea>.

Thành phần web cung cấp các thuộc tính và thuộc tính để xác định hành vi nhận dạng từ bên ngoài, bao gồm cả languagesrecognitiontype. Bạn có thể đặt nội dung của chế độ kiểm soát thông qua thuộc tính value:

<handwriting-textarea languages="en" recognitiontype="text" value="Hello"></handwriting-textarea>

Để biết mọi thay đổi về giá trị, bạn có thể theo dõi sự kiện input.

Bạn có thể dùng thử thành phần này bằng bản minh hoạ này trên GitHub. Ngoài ra, hãy nhớ xem mã nguồn. Để sử dụng chế độ kiểm soát này trong ứng dụng của bạn, hãy lấy chế độ kiểm soát này từ npm.

Tính bảo mật và quyền truy cập

Nhóm Chromium đã thiết kế và triển khai API Nhận dạng chữ viết tay dựa trên các nguyên tắc cốt lõi được xác định trong Kiểm soát quyền truy cập vào các tính năng mạnh mẽ của nền tảng web, bao gồm quyền kiểm soát của người dùng, tính minh bạch và tính công thái học.

Quyền kiểm soát của người dùng

Người dùng không thể tắt Handwriting Recognition API. API này chỉ dùng được cho các trang web được phân phối qua HTTPS và chỉ có thể được gọi từ bối cảnh duyệt web cấp cao nhất.

Sự minh bạch

Không có dấu hiệu nào cho biết tính năng nhận dạng chữ viết tay có đang hoạt động hay không. Để ngăn chặn hành vi tạo vân tay số, trình duyệt sẽ triển khai các biện pháp đối phó, chẳng hạn như hiển thị lời nhắc cấp quyền cho người dùng khi phát hiện thấy hành vi sai trái có thể xảy ra.

Quyền có tác dụng lâu dài

Handwriting Recognition API hiện không hiển thị lời nhắc cấp quyền nào. Do đó, bạn không cần phải duy trì quyền theo bất kỳ cách nào.

Phản hồi

Nhóm Chromium muốn biết ý kiến của bạn về API Nhận dạng chữ viết tay.

Hãy cho chúng tôi biết về thiết kế API

Có vấn đề gì về API mà bạn không mong muốn không? Hoặc có phương thức hay thuộc tính nào bị thiếu mà bạn cần triển khai ý tưởng của mình không? Bạn có câu hỏi hoặc bình luận về mô hình bảo mật? Báo cáo vấn đề về quy cách trên kho lưu trữ GitHub tương ứng hoặc thêm ý kiến của bạn vào một vấn đề hiện có.

Báo cáo vấn đề về việc triển khai

Bạn có phát hiện thấy lỗi trong quá trình triển khai Chromium không? Hoặc việc triển khai có khác với quy cách không? Báo cáo lỗi tại new.crbug.com. Nhớ cung cấp càng nhiều thông tin chi tiết càng tốt, hướng dẫn đơn giản để tái hiện và nhập Blink>Handwriting vào hộp Thành phần.

Thể hiện sự ủng hộ đối với API

Bạn có dự định sử dụng API Nhận dạng chữ viết tay không? Sự ủng hộ công khai của bạn giúp nhóm Chromium ưu tiên các tính năng và cho các nhà cung cấp trình duyệt khác thấy tầm quan trọng của việc hỗ trợ các tính năng đó.

Chia sẻ cách bạn dự định sử dụng tính năng này trên chuỗi thảo luận WICG Discourse. Gửi một tweet đến @ChromiumDev bằng thẻ bắt đầu bằng #HandwritingRecognition và cho chúng tôi biết bạn đang sử dụng tính năng này ở đâu và như thế nào.

Lời cảm ơn

Tài liệu này được Joe Medley, Honglin Yu và Jiewei Qian xem xét.