ユーザー(手書き入力)を認識する

手書き入力認識 API を使用すると、手書き入力のテキストを処理して認識できます。

Handwriting Recognition API とは何ですか?

Handwriting Recognition API を使用すると、ユーザーの手書き(インク)をテキストに変換できます。一部のオペレーティング システムには、このような API が長い間含まれていました。この新しい機能により、ウェブアプリでもこの機能を使用できるようになりました。コンバージョンはユーザーのデバイスで直接行われ、オフライン モードでも機能します。サードパーティのライブラリやサービスを追加する必要はありません。

この API は、いわゆる「オンライン」または準リアルタイムの認識を実装します。つまり、ユーザーが描画している間に、単一のストロークを取り込んで分析することで、手書き入力が認識されます。光学式文字認識(OCR)などの「オフライン」手法では最終的な結果のみが判明しますが、オンライン アルゴリズムでは、個々のインクストロークの時間的な順序や圧力などの追加のシグナルにより、より高い精度を実現できます。

Handwriting Recognition API の推奨ユースケース

使用例には次のようなものがあります。

  • ユーザーが手書きのメモをキャプチャしてテキストに翻訳するメモ作成アプリケーション。
  • 時間的な制約からペン入力や指入力が可能なフォーム アプリケーション。
  • クロスワード、ハングマン、数独など、文字や数字を入力する必要があるゲーム。

現在のステータス

Handwriting Recognition API は(Chromium 99)から利用できます。

Handwriting Recognition API の使用方法

特徴検出

ナビゲーター オブジェクトに createHandwritingRecognizer() メソッドが存在するかどうかをチェックして、ブラウザのサポートを検出します。

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

基本コンセプト

Handwriting Recognition API は、入力方法(マウス、タップ、ペン)に関係なく、手書き入力をテキストに変換します。この API には、次の 4 つの主要なエンティティがあります。

  1. ポイントは、特定の時点でポインタがどこにあったかを表します。
  2. ストロークは 1 つ以上のポイントで構成されます。ストロークの記録は、ユーザーがポインタを下ろした(つまり、メインのマウスボタンをクリックした、またはペンや指で画面をタップした)ときに開始され、ポインタを持ち上げたときに終了します。
  3. 描画は 1 つ以上のストロークで構成されます。実際の認識はこのレベルで行われます。
  4. 認識機能が、想定される入力言語で構成されている。これを使用して、認識機能の構成が適用された図形描画のインスタンスを作成します。

これらのコンセプトは、特定のインターフェースと辞書として実装されます。これについては後で説明します。

Handwriting Recognition API のコア エンティティ: 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() メソッドを呼び出します。このメソッドは、認識アルゴリズムを微調整するためのさまざまなヒントを含むオブジェクトを受け取ります。ヒントはすべて省略可能です。

  • 入力するテキストの種類: テキスト、メールアドレス、数字、個々の文字(recognitionType
  • 入力デバイスのタイプ: マウス、タップ、ペン入力(inputType
  • 直前のテキスト(textContext
  • 返される可能性の低い代替予測の数(alternatives
  • ユーザーが入力する可能性が高いユーザー識別可能な文字(「書記体」)のリスト(graphemeSet

Handwriting 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)と、認識されたテキスト内の位置(beginIndexendIndex)、その文字を作成したストロークとポイントの組み合わせです。

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);
      });
    },
  );
}

この情報を使用して、認識されたグラフをキャンバス上で再び追跡できます。

認識された各書記素の周りにボックスが描画される

認識を完了する

認識が完了したら、HandwritingDrawingclear() メソッドを呼び出し、HandwritingRecognizerfinish() メソッドを呼び出してリソースを解放できます。

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

デモ

ウェブ コンポーネント <handwriting-textarea> は、手書き認識が可能な編集コントロールを段階的に拡張しています。編集コントロールの右下にあるボタンをクリックすると、描画モードが有効になります。描画が完了すると、ウェブ コンポーネントが認識を自動的に開始し、認識されたテキストを編集コントロールに追加します。Handwriting Recognition API がまったくサポートされていない場合や、プラットフォームがリクエストされた機能をサポートしていない場合、編集ボタンは非表示になります。ただし、基本的な編集コントロールは <textarea> として引き続き使用できます。

ウェブ コンポーネントには、languagesrecognitiontype など、外部からの認識動作を定義するプロパティと属性があります。コントロールの内容は value 属性で設定できます。

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

値の変更を通知するには、input イベントをリッスンします。

Glitch でこのデモを使用してコンポーネントをお試しいただけます。ソースコードもご覧ください。アプリでコントロールを使用するには、npm から取得します。

セキュリティと権限

Chromium チームは、ユーザー コントロール、透明性、エルゴノミクスなど、強力なウェブ プラットフォーム機能へのアクセスの制御で定義されている基本原則に基づいて手書き認識 API を設計し、実装しました。

ユーザー コントロール

Handwriting Recognition API はユーザーがオフにすることはできません。これは HTTPS 経由で配信されるウェブサイトでのみ使用でき、最上位のブラウジング コンテキストからのみ呼び出すことができます。

透明性

手書き入力認識が有効になっているかどうかはわかりません。指紋認証を防ぐため、ブラウザは不正使用の可能性を検出したときにユーザーに権限プロンプトを表示するなどの対策を実装しています。

権限の永続性

現在、Handwriting Recognition API では権限プロンプトは表示されません。したがって、権限を永続化する必要はありません。

フィードバック

Chromium チームは、Handwriting Recognition API の使用感について、皆様のご意見をお聞かせいただきたいと考えています。

API 設計について

API が想定どおりに動作しない点はありますか?あるいは、アイデアを実装するために 不足しているメソッドやプロパティがないでしょうか。セキュリティモデルについて ご不明な点やご意見がありましたら対応する GitHub リポジトリで仕様に関する問題を報告するか、既存の問題にコメントを追加します。

実装に関する問題を報告する

Chromium の実装にバグが見つかりましたか?それとも、実装が仕様と異なるのでしょうか?new.crbug.com でバグを報告します。できるだけ詳細な情報を含め、再現手順を簡単に説明してください。[コンポーネント] ボックスに Blink>Handwriting を入力します。Glitch は、簡単な再現手順をすばやく共有するのに適しています。

API のサポートを表示する

手書き入力認識 API を使用する予定はありますか?公開サポートは、Chromium チームが機能を優先付けするうえで役立ち、他のブラウザ ベンダーに、その機能のサポートがどれほど重要であるかを示します。

WICG Discourse スレッドで、どのように使用する予定かをお知らせください。ハッシュタグ #HandwritingRecognition を使用して @ChromiumDev にツイートを送信し、どこでどのように使用しているかをお知らせください。

謝辞

このドキュメントは、Joe Medley、Honglin Yu、Jiewei Qian が確認しました。