iOS
QRcode
Swift
adventcalendar2017
車検証
iOSDay 11

iOS 11のカメラでは読み取れないQRコードを読み取ってみる

iOS 11のカメラのQRコード読み取り機能の限界

iOS 11では標準カメラアプリでQRコードが読み取れるようになりました。便利ですね。
ただ、このカメラアプリでは1度ではうまく読み取れないQRコードがあるのをご存知でしょうか?

馴染みが薄いですが、実はQRコードは仕様上複数に分割することが可能で、シンボルの連結機能と良います。1

このようなQRコードはiOS 11のカメラでは単品のQRコードとしか認識されません。そのため、連結したひとまとまりのデータとして欲しい場合、ひとつずつ読み込んではコピペひとつずつ読み込んではコピペ…というような作業をしなければいけません。しかも認識結果からはどれとどれが分割されていたのかわからないので、本当に正しく連結できたかどうかは別の手段で確かめなければいけません。

ちなみにAVFoundationでQRコードを読み取る場合も同様で、分割QRコードであることを認識できません。

このシンボルの連結機能を使った分割QRコード、わりと身近にあったりします。例えば車検証です。車検証の右下にはQRコードがありますが、普通自動車の場合いくつかに分割されています。他にも「どうぶつの森」のマイデザインQRコードが分割QRコードでした。

分割QRコードを読み取ってみる

というわけで、本記事ではかなりニッチではありますが、分割QRコードを読み取るためにはどうすれば良いのかを解説していきます。

分割QRコードの仕様

そもそもQRコードの仕様はどうなっているのか。仕様書が販売されています。

はい、有料です。ハードル高い!

QRコードリーダーをつくるには必ず読んだ方が良いでしょうが、今回はそこまで手を出しません。(もし仕様書を読んだことがあって、本記事の内容に不備があることに気づいてしまった人がいたらマサカリ投げてください…)

分割QRコードの仕様を推測してみる

では仕様書を読まずにどうやって仕様を把握するのか。

分割QRコードにも対応しているリーダーライブラリを使います。と言ってしまうと身も蓋もないのですが、さすがニッチ技術だけあってiOSで使えるものという条件も加わるとなかなか見つかりません。

少し前に調べた時に実用的に使えそうだったのは老舗の ZXingObjC くらいでした。zxing のObjective-C移植版ですね。

READMEには特に分割QRコード対応とは書かれていないですが、調べてみると分割QRコード固有のメタデータもきちんと取得できることがわかりました。

では具体的に実装を見ていきましょう。

// MARK: - ZXCaptureDelegate
extension YOUR_ANY_CLASS: ZXCaptureDelegate {

    @objc func captureResult(_ capture: ZXCapture!, result: ZXResult!) {
        // QRコードを読み取れていれば ZXResult オブジェクトが手に入る
        guard let result = result else { return }

        // 分割QRコードの場合、resultMetadata の以下2つを確認してどの部分のQRコードが読み込まれたのか判定することができる
        // - kResultMetadataTypeStructuredAppendSequence
        // - kResultMetadataTypeStructuredAppendParity

        guard let metaData = result.resultMetadata,
            let sequenceValue = metaData[Int(kResultMetadataTypeStructuredAppendSequence.rawValue)] as? Int,
            let parity = metaData[Int(kResultMetadataTypeStructuredAppendParity.rawValue)] as? Int else { return }

        // sequenceValueは以下のような8bitのデータになっている
        // 読み取ったQRコードの番号 4bit + トータルのQRコード数 4bit
        let bit = String(sequenceValue, radix: 2)
        let padding = String(repeating: "0", count: (8 - bit.count))
        let sequence = padding + bit
        let current = String(sequence.prefix(4))
        let total = String(sequence[sequence.index(sequence.startIndex, offsetBy: 4)...])

        // 扱いやすいようにIntに変換
        let position = Int(current, radix: 2)!
        let totalPosition = Int(total, radix: 2)!

        // あとは焼くなり煮るなりお好きにどうぞ!
        // parityは分割されたQRコードそれぞれに同じものが付与されているので、複数分割QRコードがある場合に正しく連結するために使います
    }

}

コメントに書いた通りですが、以下2つのメタデータが手に入るので、それを適切にデコードしてあげるだけです。シンプルな仕様ですね。

  • 読み取ったQRコードの番号とトータルのQRコード数
  • 分割QRコードを正しく連結するためのパリティ

サンプル:車検証のQRコードを読み取ってみる

さて、仕様がわかったところで、試しに車検証のQRコードを読み取ってみましょう。

と言っても連結自体は上記の通りシンプルなことがわかってしまったので、ついでに読み取った車検証のデータをデコードしてみます。

車検証の仕様書をググってみると、それっぽいものが出てきます。

この仕様通りにデコードしてみると、こんな感じでうまく取得できることがわかりました。

result_example_1.PNG

result_example_1.PNG

今回つくったものはgithubにおいてあります。興味があれば見てみてください。
monoqlo/CarQRCodeScanner: Scanner for QR code on Japanese car certificate