【iOS】SwiftでQRコードを表示して目で読んでみた

大阪オフィスの山田です。SwiftでQRコードを表示してみました。それと、おまけとしてQRコードを目で読む方法について調べてみたので書き留めておきます。

開発環境

  • Xcode 11.1
  • macOS 10.14.6

SwiftでQRコードを表示する

以下のような画面を用意しました。上のtextFieldにQRコードにする値を、下のtextFieldには誤り訂正レベルを入力します。 textが更新されるたびに、QRコードを生成して画面に表示します。

ソースコードはこちら。

class QRCodeViewController: UIViewController {
    @IBOutlet weak var messageTextField: UITextField!
    @IBOutlet weak var correctionLevelTextField: UITextField!
    @IBOutlet weak var qrImageView: UIImageView!

    override func viewDidLoad() {
        super.viewDidLoad()

        messageTextField.addTarget(self, action: #selector(self.textFieldDidChange), for: UIControl.Event.editingChanged)
        correctionLevelTextField.addTarget(self, action: #selector(self.textFieldDidChange), for: UIControl.Event.editingChanged)
    }

    enum InputCorrectionLevel: String {
        case L
        case M
        case Q
        case H
    }

    private var QRImage: UIImage? {
        guard let message = messageTextField.text,
            let correctionLevelString = correctionLevelTextField.text,
            let correctionLevel = InputCorrectionLevel(rawValue: correctionLevelString) else { return nil }
        let data = message.data(using: .utf8)!

        let qr = CIFilter(name: "CIQRCodeGenerator", parameters: ["inputMessage": data, "inputCorrectionLevel": correctionLevel.rawValue])!
        let sizeTransform = CGAffineTransform(scaleX: 10, y: 10)
        let qrImage = qr.outputImage!.transformed(by: sizeTransform)
        let image = UIImage(ciImage: qrImage)
        return image
    }

    @objc func textFieldDidChange() {
        qrImageView.image = QRImage
    }
}

QRコードの生成について解説

まず、TextFieldに入力された文字列をData?型に変換します。CIFilterCIQRCodeGeneratorを指定して生成します。parameterとしてinputMessage, inputCorrectionLevelを与えます。inputMessageは、QRコードにする値を指定し、inputCorrectionLevel誤り訂正レベルを指定します。誤り訂正レベルについては後ほど解説します。CoreImageFilterのReferenceはこちらです。次にCore Graphicの二次元画像を生成するためのアフィン変換行列CGAffineTransformを生成し、CIFilterと組み合わせて、QRコード画像のデータ(CIImage)を作成します。CIImageを作成した後、UIImageを作成し、UIImageViewのimageプロパティにセットして、画面に表示します。サンプルコードを見てもらうとわかりますが、生成するQRコードのバージョンを指定することはできないようです。先ほどのCoreImageFilterのRefrerenceを見ても、バージョンを指定するパラメータは無さそうです。

アフィン変換行列について

今回のブログでは詳しく掘り下げません。筆者があまりわかってないためです。深掘りした時はブログを書こうかなと思います。

QRコードについて

QRコードの仕様については、QRコードドットコム:株式会社デンソーウェーブこちらのページがまとまっています。この記事も参考にさせていただいています。

誤り訂正レベルとは

QRコードは汚れていたり破損していたりしていてもある程度自ら復元することが可能です。誤り訂正レベルは4段階存在し、高レベルであれば訂正能力は上がりますが、コードのサイズは大きくなります。同じデータで誤り訂正レベルを変化させた画像を貼っておきます。LよりもHの方がセルのサイズが小さくなってより、多くのセルを含んでいることがわかります。

L M Q H
qr_screenL.png qr_screenM.png qr_screenQ.png qr_screenH.png

おまけ

QRコードを目で読んでみる

こちらの記事が参考になります。肉眼でも読める!QRコード入門 また、QRコードの英語のWikipediaがとても情報量が多いので、目で読む際は参考にしました。以下のQRコードを読んでみたいと思います。 qr_screenL.png

QRコードの構成

全ては上手に紹介仕切れませんが、目で読むのに必要な部分だけ紹介しておきます。

  • タイミングパターン: 白黒パターンが交互に配置されています。データの読み取り歪みを補正します。例えばリーダの仰角、傾角、曲面に貼られていた場合などの歪みを補正します。詳細についてはこちらのPDFの3-3.タイミングパターンを参照してください。
  • フォーマット情報: 誤り訂正レベルと後述するデータ領域のマスクパターンの情報を持っています
  • ファインダパターン: QRコードの位置を検出するためのパターンで、全方向(360°)から検出可能となっています
  • アライメントパターン: タイミングパターン同様、歪みを補正します。詳細についてはこちらのPDFの3-2.アライメントパターンを参照してください。

フォーマット情報を読む

実際に目で読む際は5つのセルだけ読めばOKです。

構成 2.png

この場合ですと、黒を1とすると11101 になります。これは10101でマスクされていますので、解除すると01000 となります。最初の2bitの01 は誤り訂正レベルがLであることを示しています。後ろ3桁はQRコード全体にかかるマスクの種類を表しています。この場合のマスクは (i+j) % 2 = 0 となるのでゴシック模様のようにマスクがかかっていることとなります。

mask.png

フォーマット情報についてはこちらの図がとてもわかりやすいです。マスクのパターンも記載されているので、目でQRコードを読む人は一度みてみてください。

マスクを考慮しつつ読んでいく

データの読み方はQRコードの右下から2列ずつジグザグに読んでいきます。右下、左下、右上、左上、さらにその右上、左上といった感じです。一番上まで行くと、左に移動したあと、折り返してジグザグに下に降りてきます。以下の画像のような読み方になります。

スクリーンショット 2019-11-07 23.43.39.png

エンコードモードとデータの長さ

構成 3.png

右下の4つのセルを読みます。先ほど説明した通り、読み方は2列で右下、左下、右上、左上という風にジグザクに読んでいきます。セルの値を読んだら次はマスクがかかっているので、マスク部分を反転します。

  • 1101 <- セルの値
  • 1001 <- マスク
  • 0100 <- 値: 8bit mode

8ビットバイトモードであることがわかります。他のエンコーディングモードについては省略します。 次の8セルにデータの長さが記載されています。

  • 10000011 <- セルの値
  • 10011001 <- マスク
  • 00011010 <- 値: 0x1A -> 26文字

データを読む

8bitずつ読んでいき、対応表で文字に置き換えていきます。対応表はこちらを参考にさせていただきました。

. .. ...

途中で力尽きました(´・ω・`)

https://dev.cl まで解析しました。アライメントパターンや、フォーマット情報の領域は、避けて読んでいきます。

qr_screenL.png

力尽きたところまでの僕のメモです。

- 11110001
- 10011001
- 01101000 -> 68 -> h

- 11101101
- 10011001
- 01110100 -> 74 -> t

- 11100010
- 10010110
- 01110100 -> 74 -> t

- 00010110
- 01100110
- 01110000 -> 70 -> p

- 00010101
- 01100110
- 01110011 -> 73 -> s

- 01011100
- 01100110
- 00111010 -> 3A -> :

- 01000110
- 01101001
- 00101111 -> 2F -> /

- 10111001
- 10010110
- 00101111 -> 2F -> /

- 00000010
- 01100110
- 01100100 -> 64 -> d

- 00111100
- 01011001
- 01100101 -> 65 -> e

- 11101111
- 10011001
- 01110110 -> 76 -> v

- 01001000
- 01100110
- 00101110 -> 2E -> .

- 11111010
- 10011001
- 01100011 -> 63 -> c

- 00111111
- 01010011
- 01101100 -> 6C -> l

おわり

QRコードは別に目で読まんでもええんちゃうかな。

参考