ARに使えるOpenCVで作る画像認識AndroidアプリモバイルARアプリ開発“超”入門(6)(2/3 ページ)

» 2012年03月01日 00時00分 公開
[朝香貴寛TIS株式会社]

ORB特徴量検出器を用いた画像認識アプリの実行

 筆者が作成したサンプルについて簡単に説明します。

「ORB特徴量検出器」を選択した理由

 画像の特徴量検出アルゴリズムとして代表的なものとしては、「SIFT」「SURF」が挙げられます。また、これらのアルゴリズムはOpevCVにも実装されており、利用可能です。

 SIFTやSURFは画像が拡縮や回転した場合でも、あまり影響を受けないため、画像認識を行う際によく利用されます。しかし、モバイル端末で利用するには計算量が多く、処理に時間がかかります。

 また、どちらも特許が取得されているため、SIFTやSURFに比べると精度は落ちますが、今回は完全にパテントフリーで軽快な「ORB」を利用しました。OpenCVはオープンソースのライブラリですが、このように特許が取得されているものもあるため、商用利用する場合は注意が必要です。

サンプルを実行する準備

 サンプルアプリは、ここからダウンロードしてください。ダウンロードできたらビルドします。

 まず、ファイルを解凍します。

unzip detect_image_sample.zip

 Eclipseへインポートし、ターミナルから環境変数「OPENCV_MK_PATH」に「OpenCV.mk」のパスを設定します(2012年4月11日追記※Cygwinの場合は、detect_image_sampleプロジェクトルートからの相対パスを設定してください)。

export OPENCV_MK_PATH=(OpenCV-2.3.1)/share/OpenCV/OpenCV.mk

 プロジェクトルートへ移動し、ネイティブコードをビルドします。

cd detect_image_sample 
ndk-build

 Eclipseでプロジェクトをリフレッシュし、アプリをデプロイします。

サンプルを実行

 筆者が作成したサンプルを実行してみましょう。アプリで図2、図3の道路標識をかざすと、英語で説明が表示されると思います。

図2 歩行者横断禁止の画像 図2 歩行者横断禁止の画像
図3 通行止めの画像 図3 通行止めの画像
図4 実行結果の画像 図4 実行結果の画像

ORB特徴量検出器を用いた画像認識アプリの中身

 画像認識アプリの中身を確認しましょう。プロジェクトをインポートすると、図5のようなディレクトリになっています。

図5 プロジェクトのディレクトリ構成 図5 プロジェクトのディレクトリ構成

 この中で今回見ていくファイルは、以下の4つです。

  1. src/com/example/detectimage/DetectImageActivity.java
    アプリのメインアクティビティ
  2. src/com/example/detectimage/CameraPreview.java
    カメラプレビューの表示とカメラ画像のキャプチャ
  3. src/com/example/detectimage/InfomationView.java
    認識した画像の説明表示用ビュー
  4. jni/jni_part.cpp
    画像の検出

 このアプリの処理の流れは基本的には第2回で解説しました「NyARToolkit for Android」と同様で、図6のような処理の流れです。以降は図中の番号に沿って説明していきます。

図6 処理の流れ 図6 処理の流れ

【1】オーバーレイするビューと学習画像(認識させたい画像)の登録

 学習画像の登録は、アプリ起動時に最初に呼ばれるDetectImageActivity.onCreate()で行っています。

    @Override
    public void onCreate(Bundle savedInstanceState) {
        Log.i(TAG, "onCreate");
        super.onCreate(savedInstanceState);
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
 
        LayoutParams params = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
 
        FrameLayout fl = new FrameLayout(this); // ビューを重ねて表示するためのレイアウト
        fl.setLayoutParams(params);
 
        mCameraPreview = new CameraPreview(this, new MainHandler());
        mCameraPreview.setLayoutParams(params);
        fl.addView(mCameraPreview);
 
        mInfoview = new InfomationView(this);
        mInfoview.setLayoutParams(params);
        fl.addView(mInfoview);
 
        setContentView(fl);
 
        init(); //学習画像の登録
    }
DetectImageActivity.onCreate()

 このアプリでは、レイアウトにFrameLayoutを利用しています。このFrameLayoutは追加された順番にViewを重ねて表示するというレイアウトです。これにより、CameraPreviewの上に重ねてInfomationViewを表示できます。

 学習画像の登録はDetectImageActivity.init()で行っています。

        int[] ids = {R.raw.no_crossing, R.raw.closure};
        int[] widths = new int[2];
        int[] heights = new int[2];
        int[][] rgbas = new int[2][];
        for(int i = 0; i < ids.length; i++){
            Bitmap bitmap = BitmapFactory.decodeResource(getResources(), ids[i]); // 学習画像の読み込み
            widths[i] = bitmap.getWidth();
            heights[i] = bitmap.getHeight();
            rgbas[i] = new int[widths[i] * heights[i]];
            bitmap.getPixels(rgbas[i], 0, widths[i], 0, 0, widths[i], heights[i]); // 各学習画像のピクセル値をrgbasに格納
        }
        setTrainingImages(widths, heights, rgbas, 2);
DetectImageActivity.init()

 まず、「res/drawableディレクトリに配置された図2、図3の学習画像を読み込み、Bitmapへ変換しています。そしてBitmapから画像の幅、高さ、ピクセル値を取得し、配列へ格納します。また、ピクセル値を配列に格納した順番で「0」「1」「2」……と画像に番号を付け、この番号を画像のIDとします。

 この処理を画像数回行った後、取得した値を引数にネイティブメソッド「setTrainingImages()」を呼び出しています。setTrainingImages()は「jni/jni_part.cpp」で定義されています。

 Java側で「setTrainingImages()」を呼ぶと、jni_part.cppの「Java_com_example_detectimage_DetectImageActivity_setTrainingImages()」が呼ばれます。

// グローバル変数
float THRESHOLD = 45; //しきい値
int IMAGE_NUM = 0;   //学習画像の枚数
 
OrbFeatureDetector detector(300); //ORB特徴点検出器
OrbDescriptorExtractor extractor; //ORB特徴量抽出機
 
BruteForceMatcher< Hamming > matcher; //特徴量照合器
 
JNIEXPORT void JNICALL Java_com_example_detectimage_DetectImageActivity_setTrainingImages(JNIEnv* env, jobject thiz, jintArray widths, jintArray heights, jobjectArray rgbas, jint imageNum)
{
    LOGV("setTrainingImages");
 
    jint* _widths = env->GetIntArrayElements(widths, 0); 
    jint* _heights = env->GetIntArrayElements(heights, 0); 
    jintArray rgba;
 
    vector<Mat> trainDescriptorses;
    vector<KeyPoint>trainKeypoints;
    Mat trainDescriptors;
 
    IMAGE_NUM = imageNum;
 
    //各画像に対し、特徴量を抽出し特徴量照合器(matcher)へ登録
    for(int i = 0; i < imageNum; i++){
       rgba = (jintArray)env->GetObjectArrayElement(rgbas, i);
       jint*  _rgba = env->GetIntArrayElements(rgba, 0);
       Mat mrgba(_heights[i], _widths[i], CV_8UC4, (unsigned char *)_rgba); //ピクセルデータをMatへ変換
 
       Mat gray(_heights[i], _widths[i], CV_8UC1);
       cvtColor(mrgba, gray, CV_RGBA2GRAY, 0); //グレースケールへ変換
 
       detector.detect(gray, trainKeypoints); // 特徴点をtrainKeypointsへ格納
       extractor.compute(gray, trainKeypoints, trainDescriptors); //各特徴点の特徴ベクトルをtrainDescriptorsへ格納
       trainDescriptorses.push_back(trainDescriptors); 
    }
    matcher.add(trainDescriptorses);//照合器へすべての学習画像の特徴ベクトルを登録
}
jni/jni_part.cpp

 「Java_com_example_detectimage_DetectImageActivity_setTrainingImages()」では、引数で受け取った学習画像のピクセル値からORB特徴量を抽出し、特徴量照合器へ登録します。ORB特徴量はOpenCVのOrbFeatureDetecterクラスを利用することで取得でできます。

 次ページでは、引き続きサンプルの中身を解説し、最後にまとめのお話をします。

Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。