QRコードを自力で読み取ってみる
こんにちは。Kaidoです。
こちらは某鯖5 Advent Calendar 2021の12月24日分記事です。
本来は昨日出した園田智代子botの記事の分割用に取った枠だったのですが、なんか1つの記事に収められたので今回は空いた枠を使ってQRコードを自力で読み取ってみようと思います。
結構ややこしい話なので、出来る限り画像等を使って読みやすく書いたつもりですが読みにくかったら申し訳ない。
きっかけ
元々アドカレ用のネタとして考えていて最終的にボツにしたネタだったんですが、
たまたま上の方のツイートがバズっていて、鯖でもちょっと話題になっていたので書くか、と思い立った感じです。
このツイート自体は本記事の主目的とは違うのですが、どれぐらい削ったら読めなくなるかは確かに気になるので、この記事の最後にちょっと検証してみます。
QRコードを生成
読み取る前にまず元となるQRコードが無いと本末転倒なので生成します。
生成には、QRコードを開発したデンソーウェーブの公式アプリ「クルクル」のブラウザ版サービス「クルクル manager」を用いました。
今回はこのQRコードを読み取っていこうと思います。
前知識
QRコードの全ての仕様を知ろうとすると滅茶苦茶大変なので、読み取りに最低限必要な知識だけ知っておきます。
セル
QRコードは白黒のマス目みたいので構成されていますよね。この1つ1つをセルと呼びます。日本工業規格の正式な定義だと「モジュール」という名前で定義されています。
四角い模様
QRコードには向きを特定するための四角い輪っかみたいなやつが四隅のうちの3辺に存在します。
これはファインダパターンと呼ばれるもので、これがあるおかげで斜めから読み取ったりしても上手く読み取れるようになっています。
まあこれ自体は中身の読み取りには関係ないです。
バージョン
バージョンとはQRコードの大きさみたいなもので、1から40まで存在します。バージョン1は21×21のセルで構成されていて、1つバージョンが上がるごとに縦横4セルずつ増えていきます。
詳しくはデンソーのQRコード公式を見てね。
モデル
一言にQRコードといっても複数の種類が存在します。
我々が普段よく目にするものは、「QRコードモデル1」もしくは「QRコードモデル2」と呼ばれています。
モデル1は我々がQRコードと聞いて想像するあのQRコードそのもので、モデル2には追加でアライメントパターンと呼ばれるQRコードの歪みを補正するマークが付いています。
ということを理解した上で今回読み取ってみるQRコードを分類すると、
QRコードモデル1のバージョン1のQRコードであることが分かります。
構造を知る
QRコードの構造を知っておくと読み取るときに楽なので知っておきましょう。下の画像は今回読み取るQRコードモデル1、バージョン1のおおまかな構造です。
オレンジのセルは文字のデータと、誤り訂正符号というものが記載されています。誤り訂正符号については今回は省略。
水色のセルにはフォーマット情報と呼ばれるものが記載されています。
フォーマット情報は、QRコードをリーダー等で読み取る際に初めに読み取られる箇所で、ここには今回自力で読んでいく上で重要なマスク情報と呼ばれるものが記載されています。他にも色々記載されているのですが省略。
最後に赤枠内のタイミングパターンですが、これはファインダパターンなどと同じく補正に用いられる部分になっています。
今回人力で読み取るときに必要なのはオレンジセル部分の文字データと、水色セル部分に記載されているマスク情報だけです。
マスク処理を解く
QRコードにはマスク処理と呼ばれるものが施されています。
簡単に言えばQRコードの黒色の部分と、白色の部分が極端に偏らないようにバランスよく配置するための処理です。
これが施されていないと、ファインダパターンでない部分をファインダパターンと誤認する可能性があり、読み込みに支障が出ることがあります。
つまり我々がいつも見ている状態だとマスク処理が施されているため、これを解く必要があるのです。
マスク処理のパターンには8種類あるのですが…
どのパターンが用いられているかは、下の画像の赤枠3マス部分を見て判定します。
この3マスを2進数で読み取ります。白が0で黒が1。
3マスを2進数で読み取るから8種類な訳ですね。
今回の場合は黒白白なので「100」となってます。
でここで間違えてはいけないのが、この読み取った数に「101」をXORした数がマスクのパターンナンバーとなる点。
XORは排他的論理和というもので、要するに同じ入力なら0、異なる値なら1が結果になる論理演算です。
今回の場合なら「100」に「101」をXORするので、「001」がマスクのパターンナンバーとなります。
それぞれどんな感じに掛かっているかと言うとこんな感じ(再掲載)
今回は001なので横縞模様っぽいやつがマスクで掛かっているみたいです。
パターンが分かったらパターンをQRコードに重ね合わせて、重なった部分を反転させます。
じゃあどうやって重ね合わせるんだって話なんですけど、以下の計算式で決定されています。
何だこれって感じだと思うんですけど、これは先ほどのパターンを数式化したものです。というか数式を計算した結果出たのが、あのパターンだったという訳です。
iは行番号、jは列番号です。左上を(0, 0)として計算します(下図参照)
%は剰余(割り算の余り)です。
例:121 % 3なら121 ÷ 3の余りなので結果は1
まあ要するに数式を計算して結果が0ならビット(セル)を反転、1ならそのままという処理を行えばいいわけです。(多分)
ということで、反転する部分をマークしてみたのが下画像左。マスク解除後が右です。
マスクを解除する必要があるのは、オレンジで色付けしてあるデータに関する部分です。他の部分はそもそもマスクが掛かっていなかったり、別の方法でマスクを解除する部分になっています。
また、今回は誤り訂正符号を含んだデータ部分全部をマスク解除しましたが、自力で読み取るだけなら誤り訂正符号を除いた部分だけ解除すればOK↓
※どこからどこまでがデータ部分なのかは後述
これでようやくQRコードを自力で読み取る準備が完了しました。
次はいよいよ読み取るところに入っていきます。
QRコードを読み取る
ここに来るまでが長すぎる。
ということであと一歩。いよいよ読んでいきます。
読む際も今までと同じように2進数で読んでいきます。
ではどこから読めばいいのかというと、右下のセルから順番に読んでいくということが決まっています。
ちなみに今回はアライメントパターンが無いので、この順番で読んでますが、ある場合はその部分を無視して読んでいきます。
ではいよいよ読んでいきます。下のQRコードをご覧ください。
モード指示子
まず初めに読むのが赤枠の部分4セル分(4bit)。この部分にはモード指示子と呼ばれるものが格納されており、以下のように定められています。
厳密にはもっと多くのパターンがあるのですが省略。それぞれがどういうものかも省略するとして、今回のパターンを確認すると「0100」なので8ビットバイトモードであると分かります。
文字数指示子
次に青枠の部分を読んでいきます。ここには文字数指示子と呼ばれるものが記載されています。ここを読み取るとQRコードに保存されている文字数が何文字なのかが分かるようになっています。
文字数指示子に用いられているセル数(bit数)はバージョン(QRコードの種類みたいなやつ)と先ほどのモード指示子で決まっています。
今回の場合だとバージョン1で8ビットバイトモードなので8セル(8bit)分に文字数の情報が格納されています。
ということで読むと「00000111」なのでこれを10進数に直すと「7」、すなわちこのQRコードには7文字のデータが格納されていると分かります。
データ文字列
大変お待たせいたしました。
次はいよいよ今回の本題である格納されている文字を読んでいきます。
8ビットバイトモードの場合8セル(8bit)で1文字を表現しているため、8セルを1つの塊として読んでいきます。
読む順番は先ほど説明した通りの順番で読んでいきます。
全て書き起こすと下のようになります。
01100100
01100101
01101011
01100001
01101001
01110100
01101111
次に書き起こした2進数の数値を10進数に変換し、そこから更に16進数に変換します。
そして最後にUTF-8-character tableを用いて文字に変換していきます。
変換表を見て変換していくと…
「dekaito」……?
でか糸!?
ということでこのQRコードには、dekaitoという文字列が格納されていることがなんとか自力でも分かりました!!!
検証
ここからは検証として気になっていたこの話をお送りします。
じゃあどのぐらいQRコードが削られると読めなくなるのかという話です。
この話の答えを握っていそうなのが、誤り訂正機能と呼ばれる機能です。誤り訂正機能は、QRコードが汚れていたり、破損していたとしても情報を修復して読み取れるようにする機能の事をいいます。
ということは誤り訂正機能で修復できないぐらいQRコードが削られていると読めなくなるはずです。
どのぐらいまで修復出来るかは、誤り訂正レベルというもので決まっています。誤り訂正レベルには4種類あり、下のように決まっています。
訂正レベルを上げれば大部分が破損していたりしても、復元されて読み取れる可能性が上がりますが、その分QRコードのサイズも大きくなります。
さて、そんな誤り訂正レベルですが赤枠の部分に記載されています。
赤枠の部分を読むと「10」とありますが、この値もマスク処理されている値なので、さらに「10」をXORします。この「10」は固定の値です。
XORすると「00」となり上の表から、このQRコードの誤り訂正レベルはMであるため、15%ぐらいまでなら復元可能であると分かりました。
誤り訂正レベルの割合は、公式的には「全コードワードに対する比率」と表現されているのですが、全コードワードというのが正直よく分かりません。多分データ部分と誤り訂正符号の部分のことなのかなと勝手に思ってるのですが…
この画像で言うとオレンジのセルの部分のことですね。
オレンジの部分は全部で208セルあるのですが、その15%というと大体31~32セルぐらいになります。
じゃあ33セル分以上削れば読めなくなるのかというと、これがまた違うっぽくて、下の画像では36セル削ってみたんですが読めるんですねこれが。
ただ確かに認識うまく角度を調整したりしないと認識できないぐらい精度は落ちているみたいです。
まあ公式的にも「約」と付いているように、それ以上削れていてもある程度までは読めるみたいです。
じゃあそれ以下の削れ具合だと余裕で読めるのかというと、そういう訳でもないみたいなんですよね。
どうも削れている場所によっても認識が変わるみたいで、下の画像では25セル分しか削っていないのに読み込めなくなってしまいました。
……真面目に訳分からないです。
もっと詳しく調べれば分かるのかもしれないんですけど、私の処理能力ではここら辺が限界みたいです…
あとがき
二度と自力でQRコード読み取りはやりたくないです!!!!!
もう滅茶苦茶疲れました。仕様調べるのがだいぶ面倒で面倒で。
QRコードリーダーを使えば1秒もかからないのに、自力だと読み方調べたりで時間が取られて1日近くもかかってしまいました。
……こう考えると本当にコンピューターの偉大さには驚かされますね。
明日のこの枠は、やまんばさんによる「Yamanbaが選ぶアイドルマスターミリオンライブ!良曲15選」をお送りします。
ということで私の記事は終わりです。
よいクリスマスをお過ごしください。ありがとうございました!
参考
本記事は以下のサイトを参考に執筆いたしました。
本記事で用いている画像は以下のサイトを参考にし全て自作しています。
この記事が気に入ったらサポートをしてみませんか?