ちょっと話題の記事

Android Tips #41 ZXing ライブラリ (2.1) を使って QR コードを読み取る

2013.02.28

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

ZXing ライブラリとは

ZXing ("Zebra Crossing"と呼ぶ) はバーコードをアプリ内で読み取るためのオープンソース・ライブラリです。Google が提供しています。このライブラリを使うと1次元バーコードと2次元バーコード (QRコード) を画像から簡単に読み取ることができます。Android だけではなく iOS 用や ActionScript 用などさまざまなプラットフォーム向けのライブラリが提供されています。
QR コードを読み取るアプリのほとんどが ZXing ライブラリを使っているといっても過言ではないでしょう。Android アプリでは「QRコードスキャナー」という有名なアプリがありますが、もちろんこのライブラリを使って実装されています。
今回はそんな ZXing ライブラリを使って QR コードを読み取るアプリを実装してみたいと思います!

zxing_capture

今回つくるアプリの機能

今回つくるアプリの機能は以下のような感じです。

  1. カメラを起動し、プレビューを表示する
  2. 画面をタップしてオートフォーカスさせる
  3. QRコードを読み取る
  4. 読み込み結果をトーストで表示

QRコードを読み取るタイミングですが、今回は分かりやすく画面をタップしてオートフォーカスされたあとにしてみました。

ZXing ライブラリを導入する

はじめに ZXing ライブラリをアプリプロジェクトにインポートしましょう。まずは以下のダウンロードサイトにアクセスします。

http://code.google.com/p/zxing/downloads/list

zxing_import01

ダウンロードしてきた zip ファイルを解答して以下のファイルをコピーします。

  • core/core.jar
  • javase/javase.jar

zxing_inport02

コピーしたファイルは android プロジェクトの libs フォルダにつっこみます。

zxing_inport03

以上で事前準備は完了です!
なお、以前のバージョンは PlanarYUVLuminanceSource をサンプルプロジェクトから移植してこないといけなかったり、いろいろ面倒だったみたいですが、現在のバージョンは非常に簡単に実装できるようになっているようでした。

カメラでプレビューを表示する(おさらい)

まずはじめにカメラを起動してプレビューを表示するところを実装します。カメラのプレビューは以前の記事で執筆していますので、そちらも参照してください。

Android Tips #16 カメラプレビューで顔を検出する

まずは AndroidManifest.xml を編集し、カメラのパーミッションを許可します。

<uses-permission android:name="android.permission.CAMERA" />

あとはカメラプレビューを Activity に実装します。画面タップでオートフォーカスし、オートフォーカス完了後にプレビューをデータに変換するところまでの実装です。ここまではサクサクといきましょう。

CameraPreviewActivity.java

package jp.classmethod.android.sample.barcodescanner;

import java.util.List;

import android.app.Activity;
import android.hardware.Camera;
import android.hardware.Camera.AutoFocusCallback;
import android.hardware.Camera.PreviewCallback;
import android.hardware.Camera.Size;
import android.os.Bundle;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Toast;

public class MainActivity extends Activity {
    
    private SurfaceView mSurfaceView;
    private Camera mCamera;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mSurfaceView = new SurfaceView(this);
        mSurfaceView.setOnClickListener(onClickListener);
        setContentView(mSurfaceView);
    }
    
    @Override
    protected void onResume() {
        super.onResume();
        SurfaceHolder holder = mSurfaceView.getHolder();
        holder.addCallback(callback);
    }
    
    private SurfaceHolder.Callback callback = new SurfaceHolder.Callback() {
        @Override
        public void surfaceCreated(SurfaceHolder holder) {
            // 生成されたとき
            mCamera = Camera.open();
            try {
                // プレビューをセットする
                mCamera.setPreviewDisplay(holder);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        @Override
        public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
            // 変更されたとき
            Camera.Parameters parameters = mCamera.getParameters();
            List<Camera.Size> previewSizes = parameters.getSupportedPreviewSizes();
            Camera.Size previewSize = previewSizes.get(0);
            parameters.setPreviewSize(previewSize.width, previewSize.height);
            // width, heightを変更する
            mCamera.setParameters(parameters);
            mCamera.startPreview();
        }
        @Override
        public void surfaceDestroyed(SurfaceHolder holder) {
            // 破棄されたとき
            mCamera.release();
            mCamera = null;
        }
    };
    
    private OnClickListener onClickListener = new OnClickListener() {
        @Override
        public void onClick(View v) {
            // オートフォーカス
            if (mCamera != null) {
                mCamera.autoFocus(autoFocusCallback);
            }
        }
    };
    
    private AutoFocusCallback autoFocusCallback = new AutoFocusCallback() {
        @Override
        public void onAutoFocus(boolean success, Camera camera) {
            if (success) {
                // 現在のプレビューをデータに変換
                camera.setOneShotPreviewCallback(previewCallback);
            }
        }
    };
    
    private PreviewCallback previewCallback = new PreviewCallback() {
        @Override
        public void onPreviewFrame(byte[] data, Camera camera) {
            // TODO バーコード読み取り
        }
    };

}

ZXing の使いかた

次にいよいよ QR コードを読み取る部分の実装です。PreviewCallback.onPreviewFrame() 内にプレビューのデータからバーコードを読み取る処理を実装します。読み取り完了後、データを Toast で出力しています。

private PreviewCallback previewCallback = new PreviewCallback() {
    @Override
    public void onPreviewFrame(byte[] data, Camera camera) {
        // 読み込む範囲
        int previewWidth = camera.getParameters().getPreviewSize().width;
        int previewHeight = camera.getParameters().getPreviewSize().height;
        
        // プレビューデータから BinaryBitmap を生成 
        PlanarYUVLuminanceSource source = new PlanarYUVLuminanceSource(
                data, previewWidth, previewHeight, 0, 0, previewWidth, previewHeight, false);
        BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
        
        // バーコードを読み込む
        Reader reader = new MultiFormatReader();
        Result result = null;
        try {
            result = reader.decode(bitmap);
            String text = result.getText();
            Toast.makeText(getApplicationContext(), text, Toast.LENGTH_LONG).show();
        } catch (Exception e) {
            Toast.makeText(getApplicationContext(), "Not Found", Toast.LENGTH_SHORT).show();
        }
    }
};

ここで出てくる PlanarYUVLuminanceSource ですが、これは読み取る対象のデータの範囲などを設定するクラスです。引数は以下の様な感じです。

第一引数 byte[] byte 配列の YUV 画像データ (プレビューのデータ)
第二引数 int 画像データの width
第三引数 int 画像データの height
第四引数 int 画像データのうち、読み取りの開始 X 座標
第五引数 int 画像データのうち、読み取りの開始 Y 座標
第六引数 int 画像データのうち、読み取る width
第七引数 int 画像データのうち、読み取る height
第八引数 boolean 画像データを反転するか否か (フロントカメラの場合は true)

以上でひとまず動作します。 QR コードをカメラで移してタップすると読み取り結果が Toast で表示されると思います!

zxing_capture

今回は簡単なサンプルなので、カメラのプレビューから QR コードを読み取る範囲の設定などは特に行なっていません。「矩形の範囲内だけ読み取る」といった実装にすると読み取り精度がより安定し、処理速度も上がると思います。読み取り結果が表示されるまで時間がかかる場合は、QR コードを画面いっぱいに表示するか、画面上がモノクロになる状態で読み取ってみてください。
なお QR コード作成は以下のサイトを使わせていただきました。素晴らしいサービスありがとうございます!

http://www.cman.jp/QRcode/

まとめ

ZXing を使うと QR コードの読み込みがとっても簡単に実装できます!ぜひ活用していきたいですね。でも何より一番大事なのはこの技術を使って何を作るかですよね。つまり URL や メールアドレス、ネットショッピングの決済情報…そんなデータが手軽に取れるということです!ここからはアイデア次第ですが、技術をふんだんに活用し、面白いアプリを作りましょう!
QR コード作成についてはまた別のエントリでご紹介したいと思います。
ところで QR コードの QR って Quick Response の略らしいです。知りませんでした…。

参考