5.28. QRコードとAprilTag

ここまでの検出器(ブロブ、線、円、矩形)は幾何学的な特徴を見つけます。後段の処理が解釈する位置や輪郭です。残りの検出器は記号的な特徴を見つけます。ペイロードをエンコードするために視覚的な構造がまさに存在する、印刷されたパターンです。カメラがそれらを特定し、デコーダがビットを読み取り、返ってくるのは位置ではなく、記号を印刷した者が意図的に選んだ文字列(あるいはID)です。

こうした2つのファミリーが小型カメラ用途を支配しています。QRコードは任意のテキスト、URL、連絡先カード、バイナリのペイロードを運びます。ポスター、パッケージ、搭乗券に現れる、消費者向けの2次元コードです。AprilTagは小さな固定セットの中から単一の数値IDを運び、遠距離からでも素早くデコードでき、(レンズの内部パラメータが与えられれば)カメラフレーム内での6自由度の姿勢を報告します。ドローン、キャリブレーションターゲット、フィデューシャルに付けられる、ロボティクス向けの2次元コードです。どちらの検出器も、ブロブ検出器や矩形検出器が使うのと同じバウンディングボックスの語彙を持つ結果オブジェクトを返しますが、ペイロードがあることで、これらはここまで扱ってきたどれとも本質的に異なります。

5.28.1. QRコード

find_qrcodes() はフレームをスキャンしてQRコードを探し、QRCode 結果オブジェクトのリストを返します。

codes = img.find_qrcodes()

for c in codes:
    img.draw_rectangle(c.rect, color=(0, 255, 0))
    for corner in c.corners:
        img.draw_circle((corner[0], corner[1], 4),
                        color=(0, 255, 0))
    print(c.payload)

この検出器は、探索を制限するための単一のオプション引数 roi を取ります。グレースケール入力が必要です。カラーフレームはデコード前に内部で変換されます。

各検出は、バウンディングボックス(xywhrect)、検出された4つの角(corners、QRコードのファインダパターンが描き出す射影四辺形)、そして文字列としてデコードされたペイロードを持ちます。検出に注釈を付けるときに描画すべきなのは角です。斜めから見たQRコードは軸に整列しておらず、バウンディングボックスはおおまかな輪郭しか与えないためです。

デコーダのメタデータは、QRデコーダが処理の過程で学習したすべてをカバーします。version はQRコードのバージョン(1~40)で、これがモジュールグリッドのサイズを決めます(バージョン1のコードは幅21モジュール、バージョン40のコードは177モジュール)。ecc_level は誤り訂正レベル(L / M / Q / H に対応する0~3)です。レベルが高いほど誤り訂正に多くのコードワードを割り当て、より大きな損傷に耐えますが、ペイロードの容量は減ります。mask はデコーダの混乱を最小化するためにエンコーダが選んだマスクパターン(0~7)です。data_type はデコーダが報告したエンコーディング(numeric、alphanumeric、binary、Kanji)で、is_numeric / is_alphanumeric / is_binary / is_kanji フラグが同じ値をより扱いやすいブール値として公開します。

eci は拡張チャネル解釈(Extended Channel Interpretation)の値で、バイト列がどのテキストエンコーディング(UTF-8、ISO-8859-1 など)であるかを識別します。任意の印刷物から読み取ったQRコードがUTF-8である保証はありません。バイト列を正しくデコードする必要があるアプリケーションは、eci を確認し、それに応じてデコードします。特に漢字(Kanji)の場合、MicroPythonは漢字エンコーディングを解析しないため、is_kanji のペイロードはバイト配列として扱い、アプリケーション側でデコードする必要があります。

典型的な用途として、カメラがコンベア上のQRコードを読み取り、デコードしたペイロードをホストに報告するケースがあります。カメラはフレームごとに find_qrcodes() を一度実行し、返されたリストを反復し、data_type がアプリケーションの期待するものに一致するコードを選び、c.payload をUARTまたはUSB経由で転送します。バウンディングボックスや角のデータはIDEのプレビューには有用ですが、ホストが気にかけるものではありません。

5.28.2. AprilTag

find_apriltags() はフレームをスキャンしてAprilTagを探し、AprilTag 結果オブジェクトのリストを返します。

tags = img.find_apriltags(families=image.TAG36H11)

for t in tags:
    img.draw_rectangle(t.rect, color=(0, 255, 0))
    img.draw_cross(t.cx, t.cy, color=(0, 255, 0))
    print(t.id, t.decision_margin)

AprilTagは設計目標の点でQRコードと異なります。QRコードは、ユーザーが近距離で一度読み取る単一の高密度な記号に任意のデータをエンコードするために作られています。AprilTagは、そのファミリーのハミング符号が許す限りの誤り耐性を備え、カメラが遠距離から連続的に読み取る疎な記号に小さなIDをエンコードするために作られています。トレードオフは双方向に現れます。QRコードは数百バイトを運べますが近距離で読む必要があり、AprilTagは数百のユニークなIDしか運べませんが数メートル離れた場所から確実に読み取れます。

families キーワードは、デコードするタグファミリーのビットマスクを取ります。利用可能なファミリーは image.TAG16H5image.TAG25H9image.TAG36H10image.TAG36H11image.TAGCIRCLE21H7image.TAGCIRCLE49H12image.TAGCUSTOM48H12image.TAGSTANDARD41H12、および image.TAGSTANDARD52H13 です。各ファミリーはID数とロバスト性をトレードオフします。名前の中の H の数字は、ファミリー内の任意の2つのコード間の最小ハミング距離(ある有効なコードが別のコードになるまでに何ビット反転する必要があるか)です。TAG16H5 は距離5で30個のID、TAG25H9 は距離9で35個のID、TAG36H11(デフォルトかつ最も一般的)は距離11で587個のIDを持ちます。検出器はファミリーに関わらず最大2ビットの誤りを訂正するため、距離がその訂正のリスクを決めます。ノイズの多いフレーム内のランダムなパターンは、有効なコードから2ビット以内に入るだけで誤検出としてデコードされてしまいますが、距離の大きいファミリーはコードをずっと疎に配置するため、そうした衝突はまれになります。これが TAG36H11 が推奨される理由です。検出時間は有効なファミリーの数に比例してスケールするため、アプリケーションは実際に印刷したものだけを有効にします。1回の呼び出しで複数のファミリーが必要な場合、ビットマスクはファミリー定数のビット単位ORになります。

各検出はバウンディングボックスの語彙(xywhrectarea、整数およびサブピクセルの重心(cxcycxfcyf))と、検出された4つの角(corners)を持ちます。続いて識別フィールドが来ます。id はファミリー内の数値ID(TAG36H11 の場合0~586)、family は数値のファミリー定数、name は文字列としてのファミリー名です。

マッチ品質のフィールドは、アプリケーションが検出をフィルタリングするために使うものです。decision_margin は0.0~1.0の信頼度スコアで、高いほど良く、decision_margin > 0.1 未満の検出を除外すれば、ほとんどの偽の検出をコストなしで取り除けます。hamming はこのタグに対してデコーダが受け入れたビット誤りの数で、低いほど良く、0 は完全なデコードを意味します。goodness は現在のデコーダがもはや計算しない過去の画質指標で、常に0.0であり無視できます。

5.28.3. 内部パラメータからの姿勢

find_apriltags() の革新的な特徴、すなわちAprilTagがロボティクスのフィデューシャルとして選ばれる理由を正当化するものは、このメソッドが検出された角と少数のキャリブレーション内部パラメータから、タグのカメラフレーム内での6自由度の姿勢を直接復元できることです。内部パラメータとは、カメラのXおよびY方向の焦点距離(ピクセル単位、fxfy)と光学中心(ピクセル単位、cxcy)であり、これら4つすべてをアプリケーションはキャリブレーション手順で一度測定し、以降はハードコードします。

内部パラメータが与えられると、返される AprilTag は、カメラに対するタグの位置を x_translationy_translationz_translation フィールドに、タグの向きを x_rotationy_rotationz_rotation(および対称性のための重複した rotation)に格納します。内部パラメータがない場合、これら6つのフィールドはすべて0.0となり、必要な姿勢推定はアプリケーションの責任となります。

並進フィールドはタグの幅を単位として報告されます。デコーダはタグを幅1単位として扱うため、アプリケーションは各並進値に印刷されたタグの物理的な幅を掛けてメートル単位の距離を得ます。100 mm幅で印刷され z_translation = 8.3 を報告するタグは、カメラから830 mm離れています。同じタグを同じ距離で50 mm幅で印刷すると z_translation = 16.6 を報告します。回転フィールドはラジアン単位で、スケーリングは不要です。

姿勢推定は幅広いロボティクス用途の基盤となります。タグでマークされた充電ステーションへのロボットのドッキング、印刷されたウェイポイントの軌跡の追従、環境内の既知の複数タグからのカメラ自身の姿勢復元などです。内部パラメータを知り、タグを見て、そのタグの実世界での位置を持つカメラは、同じ計算によって、自分自身の実世界での位置を持つことになります。

5.28.4. どちらを選ぶか

QRコードとAprilTagは異なる問題を解決します。両者の選択は、印刷された記号が何を運ぶかに帰着します。

アプリケーションが印刷された記号を通じて任意のデータ(URL、シリアル番号の文字列、連絡先レコード)を運ぶ必要があるときは、QRコードが正しい選択です。ほどほどのサイズのコードに数百バイトが収まり、エンコーディングは公開されており、あらゆるスマートフォンでサポートされ、デコーダは回転、中程度の損傷、斜めの角度にも対応します。

アプリケーションが遠距離から連続的に読み取られる小さなID(オプションで姿勢付き)(移動ロボット上のフィデューシャル、部屋の中のキャリブレーションターゲット、充電ステーションのドッキングマーカー)を必要とするときは、AprilTagが正しい選択です。用途には数百のIDで十分であり、ハミング符号はQRコードなら破綻するようなビット誤りから復元し、内部パラメータがキャリブレーションされていれば姿勢推定は無料で得られます。

両方を使うアプリケーションもあります。AprilTagが既知の位置をマークし、(並べて印刷された)関連付けられたQRコードが、その位置が何を意味するかに関するメタデータを運びます。2つの検出器は同じフレーム上で独立に動作し、アプリケーションはそれらのバウンディングボックスを相関させて各タグをその相棒のコードに対応付けます。