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

Với API Nhận dạng chữ viết tay, bạn có thể nhận dạng văn bản khi nhập bằng tay.

API Nhận dạng chữ viết tay là gì?

Với API Nhận dạng chữ viết tay, bạn có thể chuyển đổi chữ viết tay của người dùng thành văn bản. Một số hệ điều hành đã bao gồm các API như vậy từ lâu và với tính năng mới này, các ứng dụng web của bạn cuối cùng đã có thể sử dụng chức năng này. Quá trình chuyển đổi diễn ra ngay 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 thư viện hoặc dịch vụ của bên thứ ba.

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

Các trường hợp sử dụng được đề xuất cho API Nhận dạng chữ viết tay

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

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

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

API nhận dạng chữ viết tay hiện được cung cấp trên (Chromium 99).

Cách sử dụng API Nhận dạng chữ viết tay

Phát hiện tính năng

Phát hiện tính năng hỗ trợ 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 chính

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

  1. Một điểm thể hiện vị trí của con trỏ tại một thời điểm cụ thể.
  2. Một nét bao gồm một hoặc nhiều điểm. Quá trình ghi lại nét vẽ 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 hoặc ngón tay) và kết thúc khi họ nâng con trỏ lên.
  3. Một bản vẽ bao gồm một hoặc nhiều nét vẽ. Quá trình ghi nhận thực tế diễn ra ở cấp độ này.
  4. Nhận dạng được định cấu hình bằng ngôn ngữ đầu vào dự kiến. Hàm này được dùng để tạo một bản sao của bản vẽ khi áp dụng cấu hình trình nhận 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ẽ trình bày ngay sau đây.

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

Tạo trình nhận dạng

Để nhận dạng văn bản bằng phương thức nhập viết tay, bạn cần có thực thể của HandwritingRecognizer bằng cách gọi navigator.createHandwritingRecognizer() và truyền các điều kiện ràng buộc vào thực thể đó. Các giới hạn xác định mô hình nhận dạng chữ viết tay sẽ được sử dụng. Hiện tại, bạn có thể chỉ định 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 sẽ trả về một lời hứa sẽ 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 kèm theo 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 cho các tính năng nhận dạng cụ thể.

Hỗ trợ trình nhận dạng truy vấn

Bằng cách gọi navigator.queryHandwritingRecognizerSupport(), 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 định sử dụng hay không. 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 cụm từ gợi ý thay thế ít có khả năng xảy ra hơn (nếu có)
  • có quyền 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 chúng
const { languages, alternatives, segmentationResults } =
  await navigator.queryHandwritingRecognizerSupport({
    languages: ['en'],
    alternatives: true,
    segmentationResult: true,
  });

console.log(languages); // true or false
console.log(alternatives); // true or false
console.log(segmentationResult); // true or false

Phương thức này trả về một lời hứa và phân giải với đối tượng kết quả. Nếu trình duyệt hỗ trợ tính năng do nhà phát triển chỉ định, giá trị của trình duyệt sẽ được đặt thành true. Nếu không, giá trị này sẽ được thiết lập 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 nhất định trong ứng dụng, hoặc để điều chỉnh truy vấn và gửi một truy vấn mới.

Bắt đầu vẽ

Trong ứng dụng của mình, bạn nên cung cấp một khu vực nhập dữ liệu mà người dùng sẽ nhập bằng tay. Vì lý do hiệu suất, bạn nên triển khai việc này với 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 một bản vẽ 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 sử dụng một đối tượng chứa nhiều gợi ý để tinh chỉnh thuật toán nhận dạng. Tất cả gợi ý đều không bắt buộc:

  • Loại văn bản đượ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: nhập bằng chuột, thao tác chạm hoặc bút (inputType)
  • Văn bản trước đó (textContext)
  • Số lượng dự đoán thay thế ít có khả năng được trả về (alternatives)
  • Danh sách các ký tự nhận dạng người dùng ("biểu đồ") mà người dùng có nhiều khả năng sẽ nhập nhất (graphemeSet)

API Nhận dạng chữ viết tay hoạt động tốt với Sự kiện con trỏ. Sự kiện này cung cấp một giao diện trừu tượng để sử dụng dữ liệu đầu vào từ bất kỳ thiết bị trỏ nào. 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 kiểu 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 trong lần xuất hiện đầu tiên của sự kiện pointerdown trên khu vực chữ viết tay. Vì pointerType có thể trống hoặc được đặt thành giá trị độc quyền, tôi đã triển khai tính năng 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 dữ liệu đầ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', 'pen'].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 lượt vẽ mới. Để thực hiện việc này, hãy tạo một thực thể 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 trực tiếp thêm điểm đầu tiên vào đó. Vì sẽ thêm nhiều điểm sau này, nên bạn nên triển khai logic tạo điểm trong một phương thức riêng. Trong ví dụ sau, phương thức addPoint() tính thời gian đã trôi qua từ dấu thời gian tham chiếu. Thông tin tạm thời là không bắt buộc nhưng có thể cải thiện chất lượng nhận dạng. Sau đó, công cụ này sẽ đọc toạ độ X và Y từ sự kiện con trỏ và thêm điểm vào nét vẽ 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ỏ được di chuyển trên màn hình. Bạn cũng cần thêm các điểm đó vào nét vẽ. Sự kiện cũng có thể được nâng lên 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 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 đó.

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ại, 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(). 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 vẽ đã hoàn tất.

Tiếp theo, đã đến lúc nhận dạng hoạt động đầ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 nên bạn có thể liên tục chạy các dự đoán nếu cần. Ví dụ sau đây chạy một dự đoán mới sau mỗi lần hoàn tất nét vẽ.

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ó thể phân giải bằng một loạt các dự đoán được sắp xếp theo khả năng tương ứng. Số lượng phần tử phụ thuộc vào giá trị bạn đã chuyển đến gợi ý alternatives. Bạn có thể sử dụng mảng này để cho người dùng thấy các lựa chọn có thể khớp và yêu cầu họ chọn một tuỳ chọn. Ngoài ra, bạn chỉ cần sử dụng cụm từ gợi ý có nhiều khả năng nhất, như tôi đã làm trong ví dụ này.

Đố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 kèm theo 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 phân đoạn chữ viết tay đã nhận dạng được, tổ hợp ký tự nhận dạng người dùng được công nhận (grapheme) cùng với vị trí của văn bản đó trong văn bản được nhận dạng (beginIndex, endIndex), cũng như các nét và điểm đã tạo ra.

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 biểu đồ đã nhận dạng được trên canvas.

Các hộp được vẽ xung quanh mỗi biểu đồ đã xác định

Nhận dạng toàn bộ

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. Bằng cách nhấp vào nút ở góc dưới bên phải của thành phần điều khiển chỉnh sửa, bạn sẽ kích hoạt chế độ vẽ. Khi bạn hoàn tất bản vẽ, thành phần web sẽ tự động bắt đầu nhận dạng và thêm lại văn bản đã nhận dạng vào chế độ điều khiển chỉnh sửa. Nếu API Nhận dạng chữ viết tay 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, nút chỉnh sửa sẽ bị ẩn. Tuy nhiên, chế độ điều khiển chỉnh sửa cơ bản vẫn có thể sử dụng được 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 languagesrecognitiontype. Bạn có thể đặt nội dung của chế độ điều khiển qua thuộc tính value:

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

Để nhận thông báo về mọi thay đổi đối với 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 qua bản minh hoạ này trên Glitch. Ngoài ra, hãy nhớ xem mã nguồn. Để sử dụng chế độ điều khiển này trong ứng dụng, hãy lấy chế độ điều khiển từ npm.

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

Nhóm Chromium đã thiết kế và triển khai API Nhận dạng chữ viết tay theo 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 của nền tảng web mạnh mẽ, bao gồm cả quyền kiểm soát 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 API Nhận dạng chữ viết tay. Lệnh này chỉ dành cho các trang web được phân phối qua HTTPS và chỉ có thể được gọi từ ngữ cảnh duyệt web cấp cao nhất.

Sự minh bạch

Không có chỉ báo nếu tính năng nhận dạng chữ viết tay đang hoạt động. Để ngăn việc 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 hành vi sai trái có thể xảy ra.

Khả năng lưu trữ cố định quyền

API Nhận dạng chữ viết tay hiện không hiển thị bất kỳ 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.

Ý kiến phản hồi

Nhóm Chromium muốn biết ý kiến của bạn về trải nghiệm của bạn khi sử dụng API Nhận dạng chữ viết tay.

Cho chúng tôi biết về thiết kế API

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

Báo cáo sự cố triển khai

Bạn có tìm thấy lỗi khi triển khai Chromium không? Hay cách triển khai có khác với quy cách không? Gửi lỗi tại new.crbug.com. Hãy nhớ bao gồm nhiều chi tiết nhất có thể, hướng dẫn đơn giản để tái tạo và nhập Blink>Handwriting vào hộp Thành phần. Hiệu ứng nhiễu rất phù hợp để chia sẻ bản sao chép nhanh chóng và dễ dàng.

Hiển thị khả năng hỗ trợ API

Bạn có định sử dụng API Nhận dạng chữ viết tay không? Sự hỗ trợ 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 biết 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 dùng tính năng này trên chuỗi bài đăng WICG Discourse. Gửi một bài đăng trên Twitter tới @ChromiumDev bằng hashtag #HandwritingRecognition và cho chúng tôi biết vị trí cũng như cách bạn sử dụng tính năng đó.

Xác nhận

Bài viết này được Joe Medley, Honglin Yu và Jiewei Qian đánh giá. Hình ảnh chính của Samir Bouaked trên Unsplash.