開発部 iOS 担当の金森です。

WWDC2017 での発表にもあったように、 iOS11 からは標準カメラでも QRコード の読み取りが出来るようになるようです。
QRコード を見かけても、今までは「わざわざアプリをダウンロードするほどではない」とスルーしていましたが、今後は別の言い訳が必要になりそうです。

とは言え、アプリ内で他者と手軽に情報交換する際に QRコード を使うシーンは時々あるので、今回はアプリで QRコード の生成/読み取りを行う方法について説明していきます。



注意事項

iOS11 の内容はリリースされるまで、今後大きく変更される可能性があります。
そのため、本記事でも iOS11 についての記載は執筆時点で判明していた情報として参照いただけますようお願いします。


対象読者

iOS アプリで QRコード を使った情報交換をしてみたいと思っている人
(手順さえわかればよいという方は 生成 / 読み取り へ)


動作環境

Xcode 8.3
Deployment target iOS 9.0
Swift 3


QRコード について

QRコード とは以下のような二次元バーコードです。

QR_sample

規格に則って利用する限り、特許の権利行使は行われないと宣言されているため、改変したりしなければ大丈夫です。
(詳しくは デンソーウェーブさんのQRコードに関するページ を参照ください。)


iOS での実現方法

iOS ではバーコード(QRコード含む)に関する生成/読み取りが標準フレームワークでサポートされています。
そのため、それを利用するのが最も手軽でよいでしょう。
生成/読み取りについて利用するフレームワークは以下です。

生成

FrameworkSupport OSSupport function
CoreImageiOS7 or laterRendering barcodes (CICategoryGenerator)
CoreImageiOS11 or laterRendering barcodes (CIBarcodeDescriptor)

生成は CoreImage を利用しますが、 iOS11 から新しい生成方法が追加されています。

読み取り

FrameworkSupport OSSupport function
AVFoundationiOS7 or laterDetection of barcodes during capture
VisioniOS11 or laterDetection of barcodes after capture

読み込みについての両者の大きな違いは、「キャプチャ中に検出するかどうか」です。
AVFoundation での実現方法が今まで知っているカメラに映ったものを読み取る方式のものです。
Vision は画像から読み取るもので、例えば以下のような使い方があるかもしれません。

  • ポスターを写真に撮ったら QRコード が含まれていたので検出
  • SNS への画像 Up 時に QRコード を含めておきブログに誘導

アプリ側が対応してくれると少し幸せになれるかもしれないですね。


QRコード の生成

まずは比較的手軽に実現できる QRコード の生成から説明します。
QRコード を始めとして、幾つかのバーコードは CoreImage の機能を利用することで生成できます。
以下では CICategoryGenerator を利用した方法を説明します。

処理フロー

  1. CIFilter を QRコード 向けに作成 (CIQRCodeGenerator)
  2. パラメータに以下を指定
    1. コード内に含めるデータ
    2. 誤り訂正レベル (Optional)
  3. CIContext から CGImage を生成
  4. CGImage をもとに UIImage を描画
  5. UIImage (QRコード) を適当な大きさで表示


1.~3. の時点で QRコード の生成が行われます。

func generateCode(data: Data) -> CGImage? {
    // 1.~2. CIFilter を QRコード 向けに生成
    let params: [String:Any] = [
        "inputMessage" : data,
        "inputCorrectionLevel" : "L" // 損傷等の心配はないので誤り訂正レベルは低くする
    ]
    let filter = CIFilter(name: "CIQRCodeGenerator",
                          withInputParameters: params)

    // 3. 画像の生成
    guard let image = filter?.outputImage else {
        return nil
    }
    return CIContext().createCGImage(image, from: image.extent)
}


誤り訂正レベルは以下の指定が可能ですが、画面に表示するのであれば汚れなどの心配はないため最低レベルで大丈夫でしょう。
レベル 内容 説明
L 約 7% が復元可能 レベルが高いほど傷や汚れがあっても読み取れるようになります。 なお、ここでの復元対象はデータ部分だけではなく QRコード 全体のため、 QRコード のどの部分が破損していてもある程度復元して読み取れるということになります。
M 約 15% が復元可能
Q 約 25% が復元可能
H 約 30% が復元可能


4.~5. は生成した画像を UIKit で扱うための処理です。
単純に UIImage に変換すると画像サイズが思ったよりも小さくなります。
そのため、表示したい大きさ分の領域に描画してから UIImage を取得します。

UIGraphicsBeginImageContextWithOptions(constraints, true, 0)
let ctx = UIGraphicsGetCurrentContext()
ctx?.interpolationQuality = .none // 画像補間方法に最近傍法を指定
ctx?.draw(image, in: CGRect(x: 0, y: 0, width: size.width, height: size.height))
let resized = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()

これで QRコード の生成は完了です。

最近傍法(ニアレストネイバー)の指定

画像拡大時に画像補間方法を指定していますが、これは拡大/縮小後の画像がボヤけないようにするための対処です。
画像拡大時はその穴を埋めるように「補間」が行われますが、 iOS ではデフォルトの画像補間レベルが高くなっており、見た目が滑らかになるようになっています。
しかし、QRコードのように明暗がはっきりしたものについても同様に補間してしまうと、グレーな部分が出て来てむしろボヤける、ということになります。

→ くっきり →

その対策として、画像拡大時の補間方法を適したもの(最近傍法)となるようにしています。


QRコード の読み取り


次は QRコード の読み取りを行います。
今回の説明では AVFoundation を用いた「キャプチャ中の読み取り」です。
画像からの検出については別の機会で説明します。


処理フロー

  1. AVCaptureSession を生成(以降、session)
  2. session に Input を設定
  3. session に Output を設定
  4. Output に特定情報検出時の delegate と 検出対象 を指定
  5. キャプチャ画面表示用の Layer を生成
  6. Layer に session を設定
  7. キャプチャを開始する
  8. ※キャプチャにはカメラを利用するため、 Privacy 設定を入れておく

Session の生成

キャプチャの中心となるのは AVCaptureSession であり、 Input(入力機器) と Output(検出対象) を繋げる役目です。
今回の場合は Input = カメラ , Output = QRコードの情報(=Metadata) となります。
QRコードの情報とは、埋め込まれたデータや検出位置などです。 (詳細は AVFoundation Programming Guide を参照してください。)

Input はカメラを対象とするので、以下のようにして検出します。
let devices = AVCaptureDevice.devices(withMediaType: AVMediaTypeVideo) as? [AVCaptureDevice]
for device in devices {
    if device.position == .back { // 背面カメラ
        return device
    }
}

Output の設定後、検出対象(QRコードの情報)と delegate 先を指定します。

session.addOutput(metaOutput)
metaOutput.setMetadataObjectsDelegate(self, queue: sessionQueue)
metaOutput.metadataObjectTypes = [AVMetadataObjectTypeQRCode]

この時、 session に Output を設定するよりも先に metadataObjectTypes を指定してしまうとエラーとなります。
どうやら指定可能な対象かどうかをチェックしているようで、 NSInvalidArgumentException となってしまうので注意してください。

また、 delegate 指定時に sessionQueue というのを指定していますが、これは検出時の呼び出しスレッドを指定しています。
Main スレッドを止めないようにする配慮ですが、 queue の生成時は serial queue となるようにしてください。
キャプチャ中はかなりの頻度でこの呼び出しが発生するため、今回のようなケースでは順に処理できる方が適切です。

プレビュー

ここまででキャプチャおよび検出は行えるようになっていますが、このままではカメラが映したものを表示できません。
画面上にキャプチャの様子を表示した方がユーザは圧倒的に便利です。
画面表示を行うには AVCaptureVideoPreviewLayer を利用します。

UIView のサブクラスを作成し、以下のように利用する Layer を指定すると使い勝手がいいでしょう。

override class var layerClass: AnyClass {
    return AVCaptureVideoPreviewLayer.self
}

あとはこの Layer に対して session を設定すると、キャプチャ内容を表示できます。


読み込みの様子

QR_detect


おわりに


QRコード の生成/読み取りについての方法を説明しました。
QRコード は名称も用途も広く浸透しており、特殊なことをしない限り、ユーザに改めて説明する必要もないのがありがたいですね。
コード例の全体については以下を参照してください。
https://github.com/eq-inc/eq-ios-qrcode

聞き馴染みのないクラスも多くとっつきにくそうに感じるかもしれませんが、手順としては単純です。
少し変更すれば他の種類のバーコードを検出したり、顔認識も行えるので、興味があれば試してみてください。

コメントの投稿