InkML(Ink Markup Language)は、電子ペンやスタイラスで入力した筆跡を記述するためのXMLフォーマットです。座標だけでなく筆圧・傾き・時刻を並べて保存できるため、手書きデータをアプリケーションやベンダーをまたいで受け渡す用途に向きます。一方で、実装時は、ルート要素・識別子属性・予約チャネル名を仕様と照合する必要があります。この記事ではW3C勧告の本文にあたり、正しい書き方と、公開実装の保守状況を踏まえた使いどころを整理します。
まとめ
- InkMLは2011年9月20日付のW3C勧告です。名前空間は
http://www.w3.org/2003/InkML、ルート要素は<ink>で、<inkml>という要素は仕様に存在しません。 - 筆跡は
<trace>に数値の並びとして書きます。識別子はidではなくxml:idです。 - チャネル名は大文字小文字を区別する予約語です。ペンの方位角は
azimuthではなくOA、傾きはOTx/OTyを使います。 - 値には差分エンコーディングがあり、接頭辞
'で1階差分、"で2階差分を表します。これがInkMLのファイルサイズを小さくしている仕組みです。 - 仕様を策定したW3C Multimodal Interaction Working Groupは2017年2月に閉鎖しており、現在この仕様を保守する作業部会はありません。勧告そのものは2011年版のまま有効です。
- 公開実装は限られます。Microsoftが公開していたInkMLjsはアーカイブ済みで、確認できる利用例は研究用データセットの配布形式と、ベンダー形式への変換に集まっています。
- 製品に手書き認識を載せるなら、認識そのものは実行時APIに任せ、InkMLは保存・交換の層に置く構成が現実的です。
InkMLとは何か:W3C勧告のデジタルインク交換フォーマット
InkMLは、W3Cが2011年9月20日にW3C勧告(W3C Recommendation)として公開した仕様です。勧告本文のAbstractは、InkMLを「electronic penやstylusで入力されたinkを表現するためのデータフォーマット」と位置づけ、対象として手書き文字(handwriting)、ジェスチャ(gestures)、スケッチ(sketches)、楽譜(music)、その他の記譜法を挙げています。編集者はStephen M. Watt氏(University of Western Ontario/Maplesoft)とTom Underhill氏(Microsoft)です。
ここで言う「デジタルインク」は、ペン先が動いた軌跡をサンプル点の列として持つデータを指します。紙をスキャンした画像から文字を読み取るOCRが「できあがった形」を扱うのに対し、InkMLが扱うのは「どの順序で、どのくらいの速さと筆圧で書かれたか」という書き順つきの情報です。この違いは後述する使い分けの根拠になるので、画像からの読み取りを検討している場合はOCRとは?光学文字認識の仕組み・種類・精度と実装での組み込み方を解説もあわせて確認してください。
なお日本語で「デジタルインク」と検索すると、ペンタブレットのペン技術、電子ペーパー端末、印刷用のインクが混ざって出てきます。本記事で扱うのはW3C仕様としてのInkMLに限定します。
ファイルの識別方法も勧告の付録に定義があります。MIMEメディアタイプはapplication/inkml+xmlとして登録されており、拡張子は.inkまたは.inkmlが使われると記載されています。先頭の固定バイト列(マジックナンバー)は定義されていません。
InkMLの基本構造:ルート要素は<ink>、筆跡は<trace>
最小のInkML文書は、ルート要素<ink>の中に<trace>を並べるだけで成立します。勧告本文が最も単純な形として示している例は次のとおりです。
<ink xmlns="http://www.w3.org/2003/InkML">
<trace>
10 0, 9 14, 8 28, 7 42, 6 56, 6 70, 8 84, 8 98, 8 112, 9 126, 10 140
</trace>
</ink>
1つの<trace>が連続した点列を表します。通常は一筆に対応しますが、離筆中の軌跡や接触状態が変わる点列も記録できます。点はカンマで区切り、点の中の値は空白で区切ります。既定では値の並びはX座標・Y座標の2つとして解釈されます。
<ink>要素が取る属性はdocumentIDだけで、必須ではありません。子要素として<definitions>、<context>、<trace>、<traceGroup>、<traceView>、<annotation>、<annotationXML>を任意の順序・任意の回数で置けます。
<trace>の属性は次のように定められています。識別子がxml:idである点に注意してください。
| 属性 | 値 | 既定 | 意味 |
|---|---|---|---|
xml:id |
xsd:ID | なし | このtraceの識別子 |
type |
penDown / penUp / indeterminate | penDown | ペンの接触状態 |
continuation |
begin / middle / end | なし | 分割されたtraceの位置 |
priorRef |
xsd:anyURI | なし | 直前のtraceのURI |
contextRef |
xsd:anyURI | 既定コンテキスト | 適用するcontext |
brushRef |
xsd:anyURI | contextから継承 | 適用するbrush |
duration |
xsd:decimal | なし | このtraceの所要時間(ms) |
timeOffset |
xsd:decimal | なし | 開始時刻のオフセット(ms) |
continuationがmiddleまたはendのときはpriorRefが必須です。beginとmiddleのtraceはちょうど1本のtraceの前ステップになれますが、endのtraceを他のtraceの前ステップにすることはできません。ストリーミングで筆跡を分割送信するときに、この制約が順序の一意性を担保します。
複数のストロークをまとめるときは<traceGroup>を使います。同じ<traceFormat>やブラシを共有する筆跡を束ねる入れ物で、入れ子にもできます。すでに書いたtraceの一部だけを参照したいときは<traceView>を使い、traceDataRef(必須)で参照先を、fromとtoで範囲を指定します。文字認識の結果を「この筆跡の3点目から7点目がこの文字」という形で後から重ねられるのは、この参照の仕組みがあるためです。
古い解説記事では<inkml>をルート要素として紹介したり、<trace id="t1">のように書いたりする例が見られます。どちらも勧告の定義には無い書き方で、スキーマ検証を通りません。
traceFormatとchannel:予約チャネル名と属性の定義
X・Y以外の情報を持たせるときは<traceFormat>で値の並びを宣言します。<channel>のname属性は必須で、仕様が次の名前を予約しています。大文字小文字は区別されます。
| チャネル名 | 次元 | 既定単位 | 意味 |
|---|---|---|---|
| X / Y | length | mm | 筆記面上の水平・垂直位置 |
| Z | length | mm | 筆記面からのペンの高さ |
| F | force | % | ペン先の筆圧 |
| S | - | - | ペン先スイッチの状態 |
| B1…Bn | - | - | サイドボタンの状態 |
| OTx / OTy | angle | deg | x軸・y軸方向の傾き |
| OA | angle | deg | 方位角(ヨー) |
| OE | angle | deg | 仰角(ピッチ) |
| OR | angle | deg | ペン軸まわりの回転 |
| C | color | - | RGB3オクテット値 |
| CR / CG / CB | color | - | 赤・緑・青の各値 |
| CC / CM / CY / CK | color | - | シアン・マゼンタ・イエロー・黒 |
| A | - | - | 透明度(デバイス依存) |
| W | length | mm | ストローク幅 |
| BW / BH | length | mm | ブラシの幅・高さ |
| T | time | ms | サンプル点の時刻 |
ペンの傾きや向きを保存したいとき、azimuthやtiltといった一般的な語をそのままnameに書いた例を見かけますが、これらは予約名ではありません。方位角はOA、傾きはOTxとOTyです。独自の値を足すこと自体は認められていますが、その場合は予約名と衝突しない名前を選ぶ必要があります。
<channel>にはtype(boolean/decimal/integer/double、既定はdecimal)、default、min、max、orientation(+ve/-ve)、respectTo、unitsの各属性を付けられます。defaultは間欠チャネルに適用され、省略時はinteger・decimalで0、booleanでfalseが既定値になります。
すべての点で値が揃わないチャネルは<intermittentChannels>にまとめます。ボタンの押下状態のように、変化したときだけ書きたい情報に使う区分です。
traceの値エンコーディング:差分表記と復元計算
InkMLがテキスト形式でありながら実用的なサイズに収まるのは、値の書き方に差分表記が用意されているためです。仕様はtraceの中身をEBNFで定義しており、値の前に置ける接頭辞を次の3つに限定しています。
| 接頭辞 | 解釈 | 意味 |
|---|---|---|
! |
explicit | その値をそのまま使う |
' |
single difference | 直前の値からの差分 |
" |
second difference | 差分の差分(加速度に相当) |
| なし | 直前の解釈を継承 | 継承する接頭辞が無ければ明示値 |
順序には制約があります。2階差分の前には1階差分が、1階差分の前には明示値が必要です。そしてすべてのtraceは明示値から始めなければならず、これは継続traceでも同じです。仕様はこの制約の理由を、traceの終わりで位置と速度の状態を捨ててよくなりパーサの設計が単純になるためだと説明しています。
実際にどう縮むのかは、勧告が載せているデコード例を追うのが早道です。次は勧告の例から先頭6点を抜粋したXML断片です。単独の文書として使う場合は、最初の例と同じ名前空間を指定したink要素で全体を囲みます。
<traceFormat>
<channel name="X" type="decimal"/>
<channel name="Y" type="decimal"/>
<intermittentChannels>
<channel name="B1" type="boolean" default="F"/>
<channel name="B2" type="boolean" default="F"/>
</intermittentChannels>
</traceFormat>
<trace xml:id="id4525abc">
1125 18432,'23'43,"7"-8,3-5,7 -3,6 2
</trace>
この並びは次のように展開されます。vx・vyは内部状態として保持される速度で、ファイルには現れません。
| 書かれた点 | X | Y | vx | vy | 解釈 |
|---|---|---|---|---|---|
| 1125 18432 | 1125 | 18432 | - | - | 明示値 |
| ’23’43 | 1148 | 18475 | 23 | 43 | 1階差分 |
| "7"-8 | 1178 | 18510 | 30 | 35 | 2階差分 |
| 3-5 | 1211 | 18540 | 33 | 30 | 接頭辞を継承 |
| 7 -3 | 1251 | 18567 | 40 | 27 | 空白は任意 |
| 6 2 | 1297 | 18596 | 46 | 29 | 空白が必要 |
3行目を追うと仕組みが分かります。速度が23・43の状態で2階差分7・-8が与えられるので、速度は30・35になり、座標はそれぞれ1148+30=1178、18475+35=18510となります。4行目以降は接頭辞が無いため2階差分の解釈を引き継ぎ、1点あたり数文字で済んでいます。
値にはワイルドカードも使えます。*は明示値の場合はその値を維持し、差分の場合は直前の速度や加速度で積分を続けることを表します。?は間欠チャネルでその点の値を与えないことを表し、複数の間欠チャネルが同時に報告されないケースで使います。
InkMLの実装状況:公開リポジトリの保守状況
InkMLを検討するときに最初に押さえておくべきなのは、仕様を保守する体制が現在は無いという点です。InkMLを策定したW3CのMultimodal Interaction Working Groupは、W3Cのグループページに明記されているとおり2017年2月に閉鎖しました。参照すべきは2011年9月20日付の日付付き勧告で、実装にあたってはこの版と、使うライブラリの対応範囲を突き合わせることになります。
ライブラリも限られます。GitHubで公開されている主な実装は次のとおりです。
| リポジトリ | 言語 | ライセンス | 最終push | 状態 |
|---|---|---|---|---|
| microsoft/InkMLjs | JavaScript | Apache-2.0 | 2022-11-28 | アーカイブ済み |
| RobinXL/inkml2img | Python | なし | 2022-04-04 | 公開中 |
| Wacom-Developer/inkml-to-uim | Python | Apache-2.0 | 2024-09-04 | 公開中 |
| justinrubek/inkmlrs | Rust | MIT | 2024-02-12 | 積極的な開発は停止 |
microsoft/InkMLjsはアーカイブ済みで、読み取り専用になっています。採用する場合はフォークして自社で保守する前提が必要です。READMEには「Not Yet Implemented Features」として、1点だけで構成されるtrace(iやjの点)が未対応であること、X・Y以外の名前付きチャネルが未対応で3番目のチャネルは常にForceとみなされること、<mapping>が未実装であることなどが列挙されています。採用前に、自社データがこの範囲に収まるかを確認してください。inkml2imgはInkMLを画像へ変換する用途で広く参照されていますが、リポジトリにライセンスファイルが置かれていません。ライセンス表示の無いコードは既定で再利用の権利が発生しないため、業務利用では使えないと判断するのが無難です。
一方でWacom-Developer/inkml-to-uimは、ワコムのUniversal Ink Modelへ変換するパーサです。READMEは自らをサンプル実装と位置づけ、入力が単一のtraceGroupと単一のtraceで構成されることを前提にすると明記しています。ここにInkMLの現在地が表れています。InkMLそのものを実行時のデータ構造として使うのではなく、ベンダー独自形式との間をつなぐ中間形式として扱う、という位置づけです。
なお、ブラウザのnavigator.ink(Ink API)をInkMLの後継と紹介する記述を見かけますが、両者は別物です。Ink APIのrequestPresenter()が返すDelegatedInkTrailPresenterは、ポインタイベントの合間にOSレベルのコンポジタへ軌跡を描かせて表示の遅延を縮める仕組みで(expectedImprovementはその改善量をミリ秒で返します)、データを保存・交換する形式ではありません。MDNでもExperimentalかつ限定的な提供状況(Limited availability)として扱われており、Baselineには入っていません。
InkMLを選ぶ場面・選ばない場面
採用判断では、交換先が求める形式と、認識エンジンが受け取る入力形式を確認します。
InkMLが向くのは、まず研究用データの配布と再現です。オンライン手書き数式認識のコンペティションであるCROHMEは、学習データ・テストデータをInkML形式で配布すると明記しています。InkML形式の既存データセットを扱う場合は、読み込みまたは変換処理が必要です。自社データの保存形式は、交換先との互換性を基に選びます。
次に、筆圧・傾き・時刻を落とさずに別のシステムへ渡す場面です。画像に変換した時点でこれらの情報は失われるため、後から筆順を解析したり、署名の筆記速度を検証したりする可能性があるなら、ストローク列のまま保存する意味があります。XMLなので既存のスキーマ検証やXSLT変換の道具がそのまま使える点も、業務システムでは扱いやすい要素です。
逆に向かないのは、製品にリアルタイムの手書き認識を載せる場面です。認識エンジンの候補には既存のAPIがあり、たとえばGoogleのML Kit Digital Ink Recognitionは300以上の言語と25以上の書記体系に対応し、必要な認識モデルをダウンロードした後は、ネットワーク接続なしで端末上の認識処理ができます。入力として受け取るのはInkMLではなく、ストロークとタッチポイントで構成される独自のInkオブジェクトです。
したがって現実的な構成は、端末のAPIで取得した座標列を正規化し、保存・交換の層にInkMLを置き、認識は実行時APIに任せる形になります。InkMLを認識エンジンの入力形式にしようとすると、採用する認識エンジンがInkMLを直接受け取れるかを確認し、非対応なら入力形式への変換を実装する必要があります。
なお、紙の書類をデータ化したいだけであれば、ストロークデータは最初から存在しません。その場合はInkMLではなくOCRの領域です。日本語帳票を扱うならYomiTokuとは?使い方とYomiToku Studio(GUI)・商用利用の条件【v0.15.0】のような文書解析エンジンのほうが目的に合います。
よくある質問
InkMLのルート要素は<inkml>ではないのですか?
違います。勧告が定めるルート要素は<ink>です。名前空間http://www.w3.org/2003/InkMLと混同して<inkml>と書いた例が流通していますが、その要素は仕様に存在せず、スキーマ検証で弾かれます。
InkMLは廃止されたのですか?
廃止されていません。2011年9月20日付のW3C勧告として現在も有効です。ただし策定を担当したMultimodal Interaction Working Groupが2017年2月に閉鎖しているため、現在この仕様を保守する作業部会はありません。仕様が動かない点は安定要因ですが、新しいデバイスの機能に合わせた拡張を待つ前提では設計しないほうが安全です。
traceに書く数値の単位は何ですか?
チャネルごとに既定単位が決まっており、X・Y・Zはmm、角度系はdeg、時刻Tはmsです。異なる単位を使う場合は<channel>のunits属性で明示します。デバイスから取得した生の値をそのまま書くと、再生側でスケールが合わなくなります。
InkMLとSVGはどう違いますか?
SVGが記述するのは最終的な図形の形です。対してInkMLが記述するのは、時間順に並んだサンプル点の列で、各点が筆圧や傾きを持ちます。見た目を再現するだけならSVGで足りますが、書き順や筆記速度を後から解析するなら、InkMLなどで点列と時刻情報を保存する必要があります。
ファイルの拡張子とMIMEタイプは何を使いますか?
勧告の付録でMIMEメディアタイプapplication/inkml+xmlが登録されており、拡張子は.inkまたは.inkmlが使われると記載されています。先頭の固定バイト列はありません。内容を確認する場合はXMLとして解析し、ルート要素と名前空間を確認します。