筆者が作成したサンプルについて簡単に説明します。
画像の特徴量検出アルゴリズムとして代表的なものとしては、「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の道路標識をかざすと、英語で説明が表示されると思います。
画像認識アプリの中身を確認しましょう。プロジェクトをインポートすると、図5のようなディレクトリになっています。
この中で今回見ていくファイルは、以下の4つです。
このアプリの処理の流れは基本的には第2回で解説しました「NyARToolkit for Android」と同様で、図6のような処理の流れです。以降は図中の番号に沿って説明していきます。
学習画像の登録は、アプリ起動時に最初に呼ばれる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.