背景
QRコードの真ん中にロゴを置くやつ、ずっと感覚で作っていました。
「誤り訂正レベルを H にしておけば 30% までは隠せるらしい」と誰かに聞いて、
それをそのまま信じていた気がします…
今日ツールを1つ作るついでに調べたら、その理解がわりと間違っていたので書いておきます。
結論
下記が、今日作ったツールで実際に測った値です。同じ文字列・同じロゴ画像で、レベルだけ変えています。
誤り訂正レベル H / ロゴの一辺 40% → 覆う面積 21.1% → デコード: 成功
誤り訂正レベル L / ロゴの一辺 40% → 覆う面積 23.3% → デコード: 失敗
誤り訂正レベル L / ロゴの一辺 10% → 覆う面積 3.0% → デコード: 成功
面積が 21.1% と 23.3% でほとんど同じなのに、結果が割れています。
そして 23.3% のほうは、型番が小さくなったせいでロゴが切り出しシンボルに掛かっていました。
つまり「隠してよい面積」という量は、そもそも存在しないっぽいです。
なぜ面積ではないのか
デンソーウェーブの公式解説にはこう書かれています。
誤り訂正レベルは全コードワードに対する比率で表示されています。
コードワードというのはデータを構成する単位で、QRコードでは1コードワードが8ビットです。
L / M / Q / H の 7% / 15% / 25% / 30% は、このコードワードの個数に対する復元率であって、
絵として見たときの面積の割合ではない。
だから同じ面積を隠しても、そこに何個のコードワードが乗っているかは、
型番(コードの大きさ)とロゴの位置で変わります。
上の実測で 21.1% が通って 23.3% が落ちたのは、面積が 2 ポイント違ったからではなく、
型番が 5(37×37)から 3(29×29)に下がって、1モジュールあたりの重みが上がったからです。
もう一つ大きいのが、誤り訂正がまったく効かない領域があることです。
四隅のうち3つにある大きな四角(切り出しシンボル)と、
それをつなぐ白黒交互の線(タイミングパターン)は、読み取り側がコードの位置・向き・大きさを
決めるために使う部分で、誤り訂正の対象外だ。
ここが隠れると、データを復元する以前に、どこがコードなのかが決まらない。
そもそも公式は推奨していない
ついでに見つけたのですが、デンソーウェーブの FAQ にはこうあります。
QRコードにイラストを重ねたり、デザインをのせて変形してしまうと、
ちょっとした汚れや欠けでも読み取りが出来なくなったり、
読み取りの反応が悪くなってしまうことがありますので推奨しておりません。
デザインを載せたいならフレームQRを使ってください、という案内も付いています。
なので正しい態度は「やらない」なのですが、現実には名刺やポスターで求められることがあります。
それなら、せめて作った画像が実際に読めるかを手元で確かめてから出すしかない、
というのが今回の結論です。
どう測るか
やり方は単純で、生成したそのままの画像をデコードライブラリに渡すだけです。
下記は、キャンバスに描いた結果を jsQR で読み直している部分です。
canvas は QR とロゴを描き終わったあとの <canvas>、text は元の入力文字列です。
var img = ctx.getImageData(0, 0, size, size);
var decoded = jsQR(img.data, size, size, { inversionAttempts: 'dontInvert' });
var ok = !!(decoded && decoded.data === text);
inversionAttempts: 'dontInvert' を付けているのがポイントで、
これを外すと jsQR は白黒を反転してでも読もうとします。
甘い判定になってしまうので、切っています。
decoded.data === text まで見ているのは、
「何かは読めたが中身が違う」を成功にしないためです。
覆ってはいけない領域に掛かっているかは、モジュール座標で持っておくと素直に判定できます。
n は一辺のモジュール数で、QRCode.create(text, opts).modules.size から取れます。
function hitsCriticalArea(n, cx, cy, half) {
var x0 = Math.floor(cx - half), x1 = Math.ceil(cx + half);
var y0 = Math.floor(cy - half), y1 = Math.ceil(cy + half);
var finders = [[0, 0], [n - 8, 0], [0, n - 8]]; // 8x8(分離帯込み)
for (var i = 0; i < finders.length; i++) {
var fx = finders[i][0], fy = finders[i][1];
if (x1 >= fx && x0 <= fx + 7 && y1 >= fy && y0 <= fy + 7) return true;
}
if (y0 <= 6 && y1 >= 6) return true; // 横のタイミングパターン
if (x0 <= 6 && x1 >= 6) return true; // 縦のタイミングパターン
return false;
}
中央にロゴを置く限り切り出しシンボルには掛からないだろう、と最初は思っていたのですが、
上の L / 40% の実測でふつうに掛かりました。
型番が小さいと、一辺の 40% は思ったより端まで届きます。
ツールはここに置いてあります。
https://hashitosystem.com/tools/qrlogo/
画面で読めても、印刷とカメラ撮影を挟めば余裕は減ります。
測ったうえで、本番の紙と実機でもう一度試してください。面積の数字を信じるのはやめましょう。
本記事はAI補助で執筆した、個人開発の紹介記事です。