2013年04月15日
「ZXing QR コードスキャナー」の内部処理を追う
ZXing ("Zebra Crossing") Team による「QR コードスキャナー」 (以下、"ZXing スキャナ"と略記) は操作性が良く人気の高い定番 Android アプリのひとつです。同アプリでは ZXing Team 自らが開発を継続しているオープンソースのバーコード処理用ライブラリが使用されています。
ResultPoint の描画
コードリーディング時のメモ
※クリックすると大きな図が開きます
MyQRCodeReader - github
試作リーダーの動作の様子
(tanabe)
このライブラリの優秀さはスマートフォンでバーコード/QR コードを処理する際の実質標準の座にある状況からも裏打ちされていますが、ZXing スキャナの「使いやすさ」は本ライブラリの性能のみに依るものではなくアプリの実装に大きく支えられています。ZXing スキャナのソースコードは公開されているので、それに学べば本家と同等の使いやすさを備えた QR コードリーダーの自作が可能となるはずですね。操作性の良いリーダーを自作できるのであれば QR コード読み取り機能をアプリへ組み込む際にわざわざ ZXing スキャナをインテントで呼び出す必要はなく、独自の処理を柔軟に組み込むこともできるでしょう。また、上記ライブラリを知り尽くした ZXing 謹製スキャナのコードはライブラリの性能を最大限に引き出すための最良のお手本となりそうです。zxing Multi-format 1D/2D barcode image processing library with clients for Android, Java
ZXing スキャナのソースコードを正面から分析した情報はほとんど見当たらないようですが、手元での調査を通じて得られた情報と、そのエッセンスを小さくまとめた形で試作した自作リーダーのリソース一式を公開します。なお、この記事では ZXing 2.1 Release 中の zxing-2.1/android/ 配下のソースコードを対象としています。
注目すべき要素
なぜ ZXing スキャナは QR コードリーダーとして使いやすいのでしょう?その要素こそが実装を調べる上で注目すべきポイントとなるでしょう。特長をみっつピックアップしてみます。
- コードの認識が早い
ターゲットにレンズを向けてじっとしていればすぐに認識が完了する - 対象とするコードをファインダ枠内に留める以外の操作が不要
フォーカスを合わせたり画面をタップするといった操作をする必要がない - コード識別パターン検出位置を示すポイントが描画される
プレビュー表示にコード識別パターンを発見した位置を示すポイント (ResultPoint) がリアルタイムで描画されるため端末の位置・角度を加減し易い
ソースコードの追跡とまとめ
コード読みの途中で迷子にならないための道具としてホワイトボード代わりにエクセルシートを使いました。あくまでも作業用なので決して見た目の良いものではありませんが記録としてそのまま掲載します。
以下、ソースコードから得られた情報を整理してみます。
- プレビュー表示中に裏側で走っている太い処理の内容
※カメラには 2 秒ごとに autoFocus() が適用される- 現在プレビュー表示中のフレームイメージを取得
- 取得したイメージをバックグラウンドスレッドへ渡す
- バックグラウンドスレッドはイメージを ZXing ライブラリ処理に渡す
- ライブラリはコードの検出過程で QR コード画像の識別パターンである可能性のある箇所(ResultPoint)を見つけるとその座標を UI 側へ逐次通知する
- ResultPoint を受け取とった UI 側はそれを表示中のプレビュー画面に重ねて描画する
- ライブラリ側処理が今回のフレームイメージから QR コードを検出しなかった場合、1. からの処理が繰り返される
- 重要なクラスとその処理の概要
- com.google.zxing.client.android- CaptureActivity
QR コードスキャナーの Activity クラス - CaptureActivityHandler
CaptureActivity のハンドラ。DecodeThread インスタンスを生成し DecodeHandler からライブラリ処理による QR コード認識結果を受け取る。コードが認識されなかった場合はこのクラスが CameraManager.requestPreviewFrame() を叩くことで、次のフレームイメージ取得〜QR コード認識という全体のループを継続させる - DecodeThread
プレビュー中のフレームイメージから QR コードを検出するための手続きを連続的に繰り返すバックグラウンドスレッド - DecodeHandler
DecodeThread のハンドラ。ZXing ライブラリの MultiFormatReader クラスの QR コード検出メソッドを呼び、認識結果を CaptureActivityHandler へ伝える - ViewfinderView
View の継承クラス。プレビュー画面の描画、ファインダ矩形上に捕捉したイメージについての識別パターン検出点(ResultPoint)の描画を onDraw() メソッド内で実施 - ViewfinderResultPointCallback
ZXing ライブラリ内に宣言のある「com.google.zxing.ResultPointCallback」インターフェイスの実装クラス。DecodeThread 経由でライブラリ側処理へ登録され、ライブラリが ResultPoint 認識時にこのクラスの foundPossibleResultPoint() メソッドをコールバックする。同メソッドは ViewfinderView クラスへ ResultPoint を伝達する
- CameraManager
カメラのオープン・クローズや PreviewCallback へのフレームイメージ取得指示など - PreviewCallback
Camera.PreviewCallback の実装クラス。onPreviewFrame() 発生時に DecodeHandler へキャプチャイメージを渡す - AutoFocusManager
バックグラウンドで 2 秒ごとにカメラに autoFocus() を適用 - CameraConfigurationManager
カメラまわりの解像度や各種パラメータの取得・設定
- CaptureActivity
- 使いやすさを支えているもの
- 「コードの認識が早い」
→ プレビュー表示中、コードを検出するまで絶えずフレームイメージの取得とバックグラウンドスレッドでの分析を繰り返している - 「対象とするコードをファインダ枠内に留める以外の操作が不要」
→ カメラへのオートフォーカスの適用を効果的に利用している - 「コード識別パターン検出位置を示すポイントが描画される」
→ 検出位置を直ちに通知するためにライブラリの深部に用意されたコールバック機構と UI への描画処理を適切に組み合わせて使用している
- 「コードの認識が早い」
リーダーの試作
以上のように、全体像が見えてしまえば ZXing スキャナの操作性を支えているのは意外なほどシンプルなしくみであることがわかります。それを自作のコードに組み込むことは難しくなさそうですね。そこで ZXing スキャナの実装に倣いつつできるだけ短いコードでざっくりとリーダーを作ってみることにしました。認識結果はダイアログ表示のみとしています。動作確認は一部の環境でのみ行っており、不具合があれば適宜手を入れて下さい。
ソースコードの一覧と概要を以下に示します。
- MyActivity.java
本アプリの Activity クラス。implements SurfaceHolder.Callback, Handler.Callback, Camera.PreviewCallback - MyCameraConfigurationManager.java
ZXing スキャナの CameraConfigurationManager より。カメラパラメータの設定を決め打ちに - MyDecodeThread.java
ZXing スキャナの DecodeThreadより。対応コードフォーマットを決め打ちに - MyDecodeHandler.java
ZXing スキャナの DecodeHandlerより。コード認識成功時に MyActivity へ Bitmap は送らず検出したテキストの情報のみに - MyFinderView.java
ZXing スキャナの ViewfinderView および ViewfinderResultPointCallback より。オリジナルの ViewfinderView はプレビュー画面全体を覆っているが単純化のためここではファインダ矩形と一対一に変更。ResultPoint が 4 以上になるとファインダ内に「Warning!」と表示
ビルドずみの apk はここにあります
http://dsas.blog.klab.org/data/zxing_qr_scanner/MyQRCodeReader.apk
(tanabe)