您可以使用手寫辨識 API,在使用者輸入手寫文字時即時辨識文字。
什麼是手寫辨識 API?
您可以使用手寫辨識 API,將使用者的手寫字轉換為文字。部分作業系統早已納入這類 API,有了這項新功能,您的網頁應用程式終於可以使用這項功能。轉換會直接在使用者的裝置上進行,即使在離線模式也能運作,而且不需要新增任何第三方程式庫或服務。
這個 API 實作所謂的「線上」或近乎即時辨識。也就是說,系統會在使用者書寫時,擷取並分析單一筆劃,藉此辨識手寫輸入內容。與「離線」程序 (例如光學字元辨識 (OCR)) 相比,雖然只有最終產品可供參考,但由於線上演算法可利用額外信號 (例如個別墨水筆劃的時間序列和壓力),因此準確度更高。
建議的手寫辨識 API 用途
用法範例包括:
- 筆記應用程式,使用者可在其中記錄手寫筆記,並將其轉換為文字。
- 表單應用程式,使用者可因時間限制使用手寫筆或手指輸入內容。
- 需要填入字母或數字的遊戲,例如填字遊戲、猜謎遊戲或數獨遊戲。
目前狀態
手寫辨識 API 可透過 (Chromium 99) 使用。
如何使用手寫辨識 API
特徵偵測
檢查瀏覽器是否支援 createHandwritingRecognizer()
方法,方法如下:
if ('createHandwritingRecognizer' in navigator) {
// 🎉 The Handwriting Recognition API is supported!
}
核心概念
無論輸入方式為何 (滑鼠、觸控、觸控筆),手寫辨識 API 都會將手寫輸入內容轉換為文字。API 包含四個主要實體:
- 點代表指標在特定時間點的位置。
- 筆劃包含一或多個點。當使用者將指標向下移動 (例如點選主要滑鼠按鈕,或使用觸控筆或手指輕觸螢幕),筆劃會開始記錄,並在使用者將指標向上移回時結束。
- 繪圖包含一或多個筆劃。實際的辨識作業會在這個層級進行。
- 辨識器已設定為使用預期的輸入語言。用於建立繪圖的例項,並套用辨識器設定。
這些概念會以特定介面和字典的形式實作,我稍後會介紹這些概念。
建立辨識器
如要從手寫輸入內容辨識文字,您必須呼叫 navigator.createHandwritingRecognizer()
並將限制條件傳遞給 navigator.createHandwritingRecognizer()
,藉此取得 HandwritingRecognizer
的例項。限制會決定應使用的手寫辨識模型。目前,您可以依偏好順序指定語言清單:
const recognizer = await navigator.createHandwritingRecognizer({
languages: ['en'],
});
當瀏覽器可以執行您的要求時,這個方法會傳回 HandwritingRecognizer
例項解析的承諾。否則,系統會拒絕承諾並傳回錯誤,且無法使用手寫辨識功能。因此,建議您先查詢辨識器是否支援特定辨識功能。
查詢辨識器支援
您可以呼叫 navigator.queryHandwritingRecognizerSupport()
,檢查目標平台是否支援您要使用的手寫辨識功能。在以下範例中,開發人員:
- 想要偵測英文文字
- 取得替代字詞,較不可能的預測 (如有)
- 取得分割結果 (即已辨識的字元),包括組成這些字元的點和筆劃
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
這個方法會傳回使用結果物件解決的承諾。如果瀏覽器支援開發人員指定的功能,其值就會設為 true
。否則會設為 false
。您可以利用這些資訊在應用程式中啟用或停用特定功能,或是調整查詢並傳送新的查詢。
開始繪圖
在應用程式中,您應該提供使用者以手寫輸入內容的輸入區域。基於效能考量,建議您使用畫布物件實作此功能。這部分的確切實作方式不在本文的討論範圍內,但您可以參考示範,瞭解如何進行。
如要開始新的繪圖,請在辨識器上呼叫 startDrawing()
方法。這個方法會使用包含不同提示的物件,精細調整辨識演算法。所有提示皆為選填:
- 輸入的文字類型:文字、電子郵件地址、數字或個別字元 (
recognitionType
) - 輸入裝置類型:滑鼠、觸控或觸控筆輸入 (
inputType
) - 上方文字 (
textContext
) - 應傳回的替代預測次數 (
alternatives
) - 使用者最有可能輸入的可識別字元 (「圖形」) 清單 (
graphemeSet
)
手寫辨識 API 很適合搭配指標事件使用,這類事件提供抽象介面,可從任何指標裝置取用輸入內容。指標事件引數包含所用指標的類型。也就是說,您可以使用指標事件自動判斷輸入類型。在下列範例中,系統會在手寫區域首次發生 pointerdown
事件時,自動建立手寫辨識繪圖。由於 pointerType
可能為空白或設為專屬值,因此我引入了一致性檢查,確保繪圖輸入類型只設定支援的值。
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);
});
新增筆劃
pointerdown
事件也是開始新的筆觸的正確位置。如要這麼做,請建立新的 HandwritingStroke
例項。此外,您應將目前的時間儲存為參考點,以便後續加入的點:
function startStroke(event) {
activeStroke = {
stroke: new HandwritingStroke(),
startTime: Date.now(),
};
addPoint(event);
}
新增地點
建立筆劃後,請直接為其新增第一個點。由於您之後會新增更多點數,因此在個別方法中實作點數建立邏輯會比較合理。在以下範例中,addPoint()
方法會根據參考時間戳記計算經過的時間。時間資訊為選用項目,但可改善辨識品質。接著,它會從指標事件讀取 X 和 Y 座標,並將該點新增至目前的筆劃。
function addPoint(event) {
const timeElapsed = Date.now() - activeStroke.startTime;
activeStroke.stroke.addPoint({
x: event.offsetX,
y: event.offsetY,
t: timeElapsed,
});
}
當指標在螢幕上移動時,系統會呼叫 pointermove
事件處理常式。這些點也必須新增至筆劃。如果游標未處於「向下」狀態,也可以觸發事件,例如在未按下滑鼠按鈕的情況下,在螢幕上移動游標。以下範例中的事件處理常式會檢查是否有有效的筆劃,並為其新增新點。
canvas.addEventListener('pointermove', (event) => {
if (activeStroke) {
addPoint(event);
}
});
辨識文字
當使用者再次放開指標時,您可以呼叫其 addStroke()
方法,在繪圖中加入筆劃。以下範例也會重設 activeStroke
,因此 pointermove
處理常式不會為完成的筆劃新增點。
接著,請在繪圖上呼叫 getPrediction()
方法,以便識別使用者的輸入內容。辨識作業通常會在幾百毫秒內完成,因此您可以視需要重複執行預測作業。以下範例會在每個完成的筆劃後執行新的預測作業。
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));
});
這個方法會傳回承諾,並以預測結果陣列解析,並依預測結果的可能性排序。元素數量取決於您傳遞至 alternatives
提示的值。您可以使用這個陣列,向使用者顯示可能的符合項目,讓他們選取所需選項。或者,您也可以直接採用最可能的預測結果,這也是我在範例中採用的方式。
預測物件包含辨識的文字和選用的區隔結果,我會在下一個章節討論這項結果。
區隔結果的詳細洞察
如果目標平台支援,預測結果物件也可能會包含區隔結果。這是一個陣列,其中包含所有已辨識的手寫區段,以及已辨識的使用者可辨識字元 (grapheme
) 與其在已辨識文字中的位址 (beginIndex
、endIndex
),以及所產生的筆劃和點。
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);
});
},
);
}
您可以利用這項資訊,再次追蹤畫布上已辨識的構字元。
完成辨識
辨識完成後,您可以對 HandwritingDrawing
呼叫 clear()
方法,並在 HandwritingRecognizer
上呼叫 finish()
方法以釋出資源:
drawing.clear();
recognizer.finish();
示範
網路元件 <handwriting-textarea>
實作漸進式強化的編輯控制項,可辨識手寫字跡。只要按一下編輯控制項右下角的按鈕,即可啟用繪圖模式。完成繪圖後,網頁元件會自動啟動辨識功能,並將辨識到的文字新增至編輯控制項。如果系統完全不支援手寫辨識 API,或是平台不支援所要求的功能,編輯按鈕就會隱藏。但基本編輯控制項仍可做為 <textarea>
使用。
網路元件提供屬性和屬性,用於從外部定義辨識行為,包括 languages
和 recognitiontype
。您可以透過 value
屬性設定控制項的內容:
<handwriting-textarea languages="en" recognitiontype="text" value="Hello"></handwriting-textarea>
如要瞭解值的任何變更,您可以監聽 input
事件。
您可以使用這個 Glitch 上的示範試用元件。此外,請務必查看原始碼。如要在應用程式中使用控制項,請從 npm 取得。
安全性和權限
Chromium 團隊根據「控制強大網路平台功能的存取權」一文中定義的核心原則,設計並實作手寫辨識 API,包括使用者控制、透明度和人體工學。
使用者控制項
使用者無法關閉手寫辨識 API。這項功能僅適用於透過 HTTPS 提交的網站,且只能從頂層瀏覽內容中呼叫。
透明度
系統不會顯示是否啟用手寫辨識功能。為避免數位指紋採集,瀏覽器會實作對應措施,例如在偵測到可能的濫用行為時,向使用者顯示權限提示。
權限持續性
手寫辨識 API 目前不會顯示任何權限提示。因此,權限不需要以任何方式保留。
意見回饋
Chromium 團隊希望瞭解您使用手寫辨識 API 的體驗。
請與我們分享 API 設計
API 是否有任何部分無法正常運作?還是你還需要實現想法或屬性呢?對於安全性模型有疑問或意見嗎?在對應的 GitHub 存放區中提出規格問題,或在現有問題中加入您的想法。
回報導入問題
您發現 Chromium 實作錯誤嗎?或者實作方式與規格不同?請前往 new.crbug.com 回報錯誤。請務必盡可能提供詳細資訊,並在「Components」方塊中輸入 Blink>Handwriting
,以便重現問題。Glitch 可讓您輕鬆快速地分享重現內容。
顯示 API 支援
您是否打算使用手寫辨識 API?您的公開支援服務有助於 Chromium 團隊優先開發特定功能,並向其他瀏覽器廠商說明支援這些功能的重要性。
請在 WICG Discourse 討論串中分享您打算如何使用這項功能。請使用主題標記 #HandwritingRecognition
向 @ChromiumDev 發送推文,告訴我們你在何處使用這項功能,以及使用方式。
實用連結
特別銘謝
本文件由 Joe Medley、Honglin Yu 和 Jiewei Qian 審查。