2013年04月15日

「ZXing QR コードスキャナー」の内部処理を追う

はてなブックマークに登録

ZXing ("Zebra Crossing") Team による「QR コードスキャナー (以下、"ZXing スキャナ"と略記) は操作性が良く人気の高い定番 Android アプリのひとつです。同アプリでは ZXing Team 自らが開発を継続しているオープンソースのバーコード処理用ライブラリが使用されています。
zxing Multi-format 1D/2D barcode image processing library with clients for Android, Java
このライブラリの優秀さはスマートフォンでバーコード/QR コードを処理する際の実質標準の座にある状況からも裏打ちされていますが、ZXing スキャナの「使いやすさ」は本ライブラリの性能のみに依るものではなくアプリの実装に大きく支えられています。ZXing スキャナのソースコードは公開されているので、それに学べば本家と同等の使いやすさを備えた QR コードリーダーの自作が可能となるはずですね。操作性の良いリーダーを自作できるのであれば QR コード読み取り機能をアプリへ組み込む際にわざわざ ZXing スキャナをインテントで呼び出す必要はなく、独自の処理を柔軟に組み込むこともできるでしょう。また、上記ライブラリを知り尽くした ZXing 謹製スキャナのコードはライブラリの性能を最大限に引き出すための最良のお手本となりそうです。

ZXing スキャナのソースコードを正面から分析した情報はほとんど見当たらないようですが、手元での調査を通じて得られた情報と、そのエッセンスを小さくまとめた形で試作した自作リーダーのリソース一式を公開します。なお、この記事では ZXing 2.1 Release 中の zxing-2.1/android/ 配下のソースコードを対象としています。

注目すべき要素

なぜ ZXing スキャナは QR コードリーダーとして使いやすいのでしょう?その要素こそが実装を調べる上で注目すべきポイントとなるでしょう。特長をみっつピックアップしてみます。

  • コードの認識が早い
    ターゲットにレンズを向けてじっとしていればすぐに認識が完了する
  • 対象とするコードをファインダ枠内に留める以外の操作が不要
    フォーカスを合わせたり画面をタップするといった操作をする必要がない
  • コード識別パターン検出位置を示すポイントが描画される
    プレビュー表示にコード識別パターンを発見した位置を示すポイント (ResultPoint) がリアルタイムで描画されるため端末の位置・角度を加減し易い
ResultPoint の描画

ソースコードの追跡とまとめ

コード読みの途中で迷子にならないための道具としてホワイトボード代わりにエクセルシートを使いました。あくまでも作業用なので決して見た目の良いものではありませんが記録としてそのまま掲載します。

   コードリーディング時のメモ

以下、ソースコードから得られた情報を整理してみます。

※クリックすると大きな図が開きます

  • プレビュー表示中に裏側で走っている太い処理の内容
    ※カメラには 2 秒ごとに autoFocus() が適用される
    1. 現在プレビュー表示中のフレームイメージを取得
    2. 取得したイメージをバックグラウンドスレッドへ渡す
    3. バックグラウンドスレッドはイメージを ZXing ライブラリ処理に渡す
    4. ライブラリはコードの検出過程で QR コード画像の識別パターンである可能性のある箇所(ResultPoint)を見つけるとその座標を UI 側へ逐次通知する
    5. ResultPoint を受け取とった UI 側はそれを表示中のプレビュー画面に重ねて描画する
    6. ライブラリ側処理が今回のフレームイメージから QR コードを検出しなかった場合、1. からの処理が繰り返される
    つまり、ZXing スキャナ は QR コードを見つけるまで絶えずフレームイメージの取得とその画像分析を繰り返している

  • 重要なクラスとその処理の概要
    - 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 を伝達する
    - com.google.zxing.client.android.camera
    • CameraManager
      カメラのオープン・クローズや PreviewCallback へのフレームイメージ取得指示など
    • PreviewCallback
      Camera.PreviewCallback の実装クラス。onPreviewFrame() 発生時に DecodeHandler へキャプチャイメージを渡す
    • AutoFocusManager
      バックグラウンドで 2 秒ごとにカメラに autoFocus() を適用
    • CameraConfigurationManager
      カメラまわりの解像度や各種パラメータの取得・設定

  • 使いやすさを支えているもの
    • 「コードの認識が早い」
      → プレビュー表示中、コードを検出するまで絶えずフレームイメージの取得とバックグラウンドスレッドでの分析を繰り返している
    • 「対象とするコードをファインダ枠内に留める以外の操作が不要」
      → カメラへのオートフォーカスの適用を効果的に利用している
    • 「コード識別パターン検出位置を示すポイントが描画される」
      → 検出位置を直ちに通知するためにライブラリの深部に用意されたコールバック機構と UI への描画処理を適切に組み合わせて使用している

リーダーの試作

以上のように、全体像が見えてしまえば ZXing スキャナの操作性を支えているのは意外なほどシンプルなしくみであることがわかります。それを自作のコードに組み込むことは難しくなさそうですね。そこで ZXing スキャナの実装に倣いつつできるだけ短いコードでざっくりとリーダーを作ってみることにしました。認識結果はダイアログ表示のみとしています。動作確認は一部の環境でのみ行っており、不具合があれば適宜手を入れて下さい。

MyQRCodeReader - github

ソースコードの一覧と概要を以下に示します。

  • 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)
klab_gijutsu2 at 17:00│Comments(0)Android | win

この記事にコメントする

名前:
URL:
  情報を記憶: 評価: 顔   
 
 
 
Blog内検索
Archives
このブログについて
DSASとは、KLab が構築し運用しているコンテンツサービス用のLinuxベースのインフラです。現在5ヶ所のデータセンタにて構築し、運用していますが、我々はDSASをより使いやすく、より安全に、そしてより省力で運用できることを目指して、日々改良に勤しんでいます。
このブログでは、そんな DSAS で使っている技術の紹介や、実験してみた結果の報告、トラブルに巻き込まれた時の経験談など、広く深く、色々な話題を織りまぜて紹介していきたいと思います。
最新コメント