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の道路標識をかざすと、英語で説明が表示されると思います。
ORB特徴量検出器を用いた画像認識アプリの中身
画像認識アプリの中身を確認しましょう。プロジェクトをインポートすると、図5のようなディレクトリになっています。
この中で今回見ていくファイルは、以下の4つです。
- src/com/example/detectimage/DetectImageActivity.java
アプリのメインアクティビティ - src/com/example/detectimage/CameraPreview.java
カメラプレビューの表示とカメラ画像のキャプチャ - src/com/example/detectimage/InfomationView.java
認識した画像の説明表示用ビュー - jni/jni_part.cpp
画像の検出
このアプリの処理の流れは基本的には第2回で解説しました「NyARToolkit for Android」と同様で、図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(); //学習画像の登録
}
このアプリでは、レイアウトに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);
まず、「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);//照合器へすべての学習画像の特徴ベクトルを登録
}
「Java_com_example_detectimage_DetectImageActivity_setTrainingImages()」では、引数で受け取った学習画像のピクセル値からORB特徴量を抽出し、特徴量照合器へ登録します。ORB特徴量はOpenCVのOrbFeatureDetecterクラスを利用することで取得でできます。
次ページでは、引き続きサンプルの中身を解説し、最後にまとめのお話をします。
Copyright © ITmedia, Inc. All Rights Reserved.


