Handwrite Recognition API を使用すると、手書き入力のテキストをリアルタイムで認識できます。
Handwrite Recognition API とは何ですか?
手書き入力 Recognition API を使用すると、ユーザーの手書き入力(インク)をテキストに変換できます。このような API は、一部のオペレーティング システムに以前から存在しています。この新しい機能により、ウェブアプリで最終的にこの機能を使用できるようになります。変換はユーザーのデバイス上で直接行われ、オフライン モードでも機能します。サードパーティのライブラリやサービスを追加する必要はありません。
この API は、いわゆる「オンライン」、つまりほぼリアルタイムの認識を実装します。つまり、1 つのストロークをキャプチャして分析することで、ユーザーが手書き入力を描画している間でも認識できます。最終製品のみがわかっている光学式文字認識(OCR)のような「オフライン」手順とは対照的に、オンライン アルゴリズムは、個々のインクストロークの時間シーケンスや圧力などの追加の信号によって、より高い精度を提供できます。
Handwrite Recognition API の推奨されるユースケース
次のような用途があります。
- ユーザーが手書きのメモを記録してテキストに翻訳したいメモ作成アプリ。
- 時間的な制約により、ユーザーがペンや指で入力できるフォーム アプリケーション。
- クロスワード、ハンマン、数独など、文字や数字の入力が必要なゲーム。
現在のステータス
Handwrite Recognition API は(Chromium 99)から利用可能です。
Handwrite Recognition API の使用方法
特徴検出
ナビゲータ オブジェクトに createHandwritingRecognizer()
メソッドの存在を確認して、ブラウザ サポートを検出します。
if ('createHandwritingRecognizer' in navigator) {
// 🎉 The Handwriting Recognition API is supported!
}
基本コンセプト
Handwrite Recognition API は、入力方法(マウス、タップ、ペン)に関係なく、手書きでの入力をテキストに変換します。API には次の 4 つの主要エンティティがあります。
- ポイントは、特定の時点におけるポインタの位置を表します。
- ストロークは、1 つ以上のポイントで構成されます。ストロークの記録は、ユーザーがポインタを下に置く(メインのマウスボタンをクリックする、ペンや指で画面をタップする)と開始し、ポインタを上に戻すと終了します。
- 描画は 1 つ以上のストロークで構成されます。実際の認識はこのレベルで行われます。
- 認識ツールが、想定される入力言語で構成されています。これは、認識機能の構成が適用された描画のインスタンスを作成するために使用されます。
これらの概念は、特定のインターフェースと辞書として実装されます。これについては後ほど説明します。
認識機能の作成
手書き入力のテキストを認識するには、navigator.createHandwritingRecognizer()
を呼び出して制約を渡すことで、HandwritingRecognizer
のインスタンスを取得する必要があります。制約によって、使用する手書き入力認識モデルが決まります。現時点では、言語のリストは優先順位の高い順序で指定できます。
const recognizer = await navigator.createHandwritingRecognizer({
languages: ['en'],
});
ブラウザがリクエストを処理できる場合、このメソッドは HandwritingRecognizer
のインスタンスで解決される Promise を返します。そうしないと、Promise がエラーで拒否され、手書き入力認識が無効になります。このため、まず特定の認識機能に対する認識器のサポートをクエリすることをおすすめします。
認識ツールのサポートのクエリ
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
このメソッドは、結果オブジェクトで解決される Promise を返します。デベロッパーが指定した機能をブラウザがサポートしている場合、その値は true
に設定されます。それ以外の場合は、false
に設定されます。この情報を使用して、アプリ内の特定の機能を有効または無効にできます。また、クエリを調整して新しいクエリを送信することもできます。
図形描画を開始
アプリケーション内に、ユーザーが手書き入力を行う入力領域を用意する必要があります。パフォーマンス上の理由から、キャンバス オブジェクトを使用してこれを実装することをおすすめします。この部分の正確な実装はこの記事では説明しませんが、実装方法についてはデモを参照してください。
新しい描画を開始するには、認識ツールで startDrawing()
メソッドを呼び出します。このメソッドは、認識アルゴリズムを微調整するためのさまざまなヒントを含むオブジェクトを受け取ります。すべてのヒントは省略可能です。
- 入力するテキストの種類: テキスト、メールアドレス、数字、1 文字(
recognitionType
) - 入力デバイスの種類: マウス、タップ、ペンでの入力(
inputType
) - 前のテキスト(
textContext
) - 返される、可能性の低い代替予測の数(
alternatives
) - ユーザーが入力する可能性が高いユーザー識別文字(「グラフィーム」)のリスト(
graphemeSet
)
Handwrite Recognition 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);
}
Wifi 拡張ポイントを追加する
ストロークを作成したら、最初のポイントをストロークに直接追加する必要があります。後でポイントを追加する場合、ポイント作成ロジックは別のメソッドで実装することをおすすめします。次の例では、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));
});
このメソッドは、候補を可能性が高い順に並べた配列で解決する Promise を返します。要素の数は、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 を設計、実装しました。
ユーザー コントロール
ユーザーが Handwriting Recognition API をオフにすることはできません。HTTPS 経由で配信されるウェブサイトでのみ使用できます。また、最上位のブラウジング コンテキストからのみ呼び出すことができます。
透明性
手書き入力認識が有効かどうかは表示されません。フィンガープリントを防ぐため、ブラウザには不正行為の可能性が検出されたときに権限プロンプトを表示するなど、対策が実装されています。
権限の永続性
現在、手書き入力認識 API では権限プロンプトが表示されません。したがって、権限を保持する必要はありません。
フィードバック
Chromium チームは、H 手書き認識 API の使用経験、
API 設計について教えてください
API について、想定どおりに動作しない点はありますか?あるいは、アイデアを実装するために必要なメソッドやプロパティが欠落していないか?セキュリティモデルについて質問や コメントがある場合は対応する GitHub リポジトリで仕様の問題を提出するか、既存の問題に意見を追加します。
実装に関する問題を報告する
Chromium の実装でバグが見つかりましたか?それとも、実装が仕様と異なるのでしょうか?
new.crbug.com でバグを報告します。できるだけ詳しい情報と簡単な再現手順を記載し、[Components] ボックスに「Blink>Handwriting
」と入力します。Glitch は、再現をすばやく簡単に行うのに最適です。
API のサポートを示す
Handwriting Recognition API を使用する予定はありますか?皆さまのご協力のおかげで、Chromium チームは機能の優先順位付けに役立ち、サポートがいかに重要であるかを他のブラウザ ベンダーに示すことができます。
WICG Discourse スレッドでその使用目的を共有します。ハッシュタグ #HandwritingRecognition
を使って @ChromiumDev 宛てにツイートを送信し、どこでどのように使用されているかを教えてください。
関連リンク
謝辞
この記事は Joe Medley、Honglin Yu、Jiewei Qian によってレビューされました。Samir Bouaked 氏による Unsplash のヒーロー画像