SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

近未来の技術トレンドを先取り! 「Tech-Sketch」出張所

Google Glassでの画像認識アプリケーションの開発

近未来の技術トレンドを先取り! 「Tech-Sketch」出張所 第18回

  • X ポスト
  • このエントリーをはてなブックマークに追加

画像認識の基礎 ~特徴とは~

 画像認識部分の実装に入る前に、少し特徴に関する説明をします。特徴とは、画像データの中の特徴的な部分(コーナー、物体の境界線、矩形の領域、動画のフレーム間の差異など)のことで、アプリケーションに応じて処理しやすいものを特徴として扱います。

 画像認識の場合、特定の種類の特徴を検出するアルゴリズムで特徴を見つけ、特徴が何を表しているかを数値化し、あらかじめ取り込まれた訓練画像データとカメラで得られた画像データとの間での差を見ることで特定の画像を検出します。ここでの画像認識に利用しているORBアルゴリズムはパテントフリーで知られるコーナー検出のアルゴリズムです。

画像認識アプリケーションの実装

 画像処理の部分はJNIにより、ネイティブコード(C++)として実装します。

ヘッダファイルの作成

 AndroidのコードからC++のメソッドを呼び出すためには、JavaのJNI(Java Native Interface)という機能を利用します。Javaのコード内でどのメソッドを呼び出すかを宣言し、javahコマンドを実行することでC++のヘッダファイル(.hpp)が生成されます。javahコマンドによって生成されたファイル内のメソッドを実装ファイル内(.cpp)に実装することでJavaとC++間で連携できます。

CameraActivity.java

 ネイティブメソッドを呼び出すクラスに以下の宣言を追記します。

private native void setTrain(long addrTrainImage);
private native boolean detectImage(long addrGray, long addrRgba);

 次にターミナルを開き、プロジェクトのルートディレクトリでjavahコマンドを実行します。Javahコマンドを実行する前にクラスファイルでメソッドが宣言されていなければならないことに注意してください。Eclipseの場合、プロジェクトをクリーンかビルドすることでクラスファイルが更新されます。

$ javah -classpath "./bin/classes:<path to android-sdk>/platforms/android-18/android.jar:<path to android-sdk>/platforms/android-18/data/layoutlib.jar:<OpenCV Library home>/bin/classes/" -o orbdetect.hpp <パッケージ名>.<クラス名>

orbdetect.cpp

 続いてネイティブメソッドを実装します。javahメソッドにより得られたメソッド名に合わせて実装します。OpenCVのモジュールからはcore.hpp、improc.hpp、features2d.hppをそれぞれインクルードする必要があります。Java_com_orb_CameraActivity_setTrainというメソッドとJava_com_orb_CameraActivity_detectImageというメソッドが、それぞれ訓練画像を登録するメソッドと画像の中に訓練画像が含まれているかを調べ、訓練画像が検出できた場合に画面上にテキストを表示するメソッドです。

 orb.operatorメソッド(24、39行目)により画像から特徴を抽出し、matcher.matchメソッド(44行目)により、2つの画像から得られた特徴を比較して似ている特徴点を抽出します。抽出された情報には似た特徴同士のペアの一覧がcv::Pointクラスのvector配列として得られます。この中には、特徴がどれだけ似ているかという指標やその特徴点の画面内の座標情報などが含まれていて、ここではその指標の値が閾値よりも近い特徴がどれだけあるかを判断することで画像の検出を判定しています。OpenCVのorbクラスのリファレンスも併せて参照してください。

#include <jni.h>
#include <android/log.h>
#include <../../../sdk/native/jni/include/opencv2/core/core.hpp>
#include <../../../sdk/native/jni/include/opencv2/imgproc/imgproc.hpp>
#include <../../../sdk/native/jni/include/opencv2/features2d/features2d.hpp>

extern "C"{
    // Global変数
    cv::ORB orb;
    std::vector<cv::KeyPoint> train_features;
    cv::Mat train_descriptor;

    JNIEXPORT void JNICALL Java_com_orb_CameraActivity_setTrain
    (JNIEnv *env, jobject obj, jlong addrTrainImage)
    {
        cv::Mat gray_train_img;

        // grayscale変換
        cv::Mat& train_img = *(cv::Mat*)addrTrainImage;
        cv::cvtColor(train_img, gray_train_img, CV_RGB2GRAY);

        // ORB検出器による処理
        orb = cv::ORB();
        orb.operator()(train_img, cv::noArray(), train_features, train_descriptor, false);
    }

    JNIEXPORT jboolean JNICALL Java_com_orb_CameraActivity_detectImage
        (JNIEnv *env, jobject obj, jlong addrRgba, jlong addrGray)
    {
            // 特徴量ベクターと特徴量記述子
        std::vector<cv::KeyPoint> query_features;
        cv::Mat query_descriptor;

        // 画像を取得
        cv::Mat& rgba_query_img = *(cv::Mat*)addrRgba;
        cv::Mat& gray_query_img = *(cv::Mat*)addrGray;

        // 特徴量を計算
        orb.operator()(gray_query_img, cv::noArray(), query_features, query_descriptor, false);

        // マッチング
        std::vector<cv::DMatch> matches;
        cv::BFMatcher matcher(cv::NORM_HAMMING, true);
        matcher.match(query_descriptor, train_descriptor, matches);

        // 閾値を設定
        const double threshold =  45.0;

        std::vector<cv::DMatch> matches_good;
        for (int i = 0; i < (int)matches.size(); i++) {
            // よい特徴だけ残す
            if (matches[i].distance < threshold)
                    matches_good.push_back(matches[i]);

            // 全体の5%以上が閾値よりも低い値を持っていた場合、検出したものとする
            float eval = (float)matches.size() * 0.05;

            if(matches_good.size() > eval){
                    // よい特徴の数が基準以上であれば、"image detected"と画面上部に表示
                    cv::putText(rgba_query_img,
                                            "image detected”,
                                            cv::Point(50,50),
                                            cv::FONT_HERSHEY_SIMPLEX,
                                            1.5,
                                            cv::Scalar(255,255,0),
                                            2,
                                            CV_AA);
                    return true;
            }else{
                    return false;
            }
        }
    }
}

Android.mk

 Android.mkを以下を追記し、カメラプレビューの実装時と同様にndk-buildを実行します。これにより、orbdetect.cppがコンパイルされ、実行可能なファイルが作られます。

# Custom Library
LOCAL_MODULE    := imdetector
LOCAL_SRC_FILES := orbdetect.cpp
LOCAL_LDLIBS    := -L$(SYSROOT)/usr/lib -llog
 
include $(BUILD_SHARED_LIBRARY)

Androidからのネイティブメソッドの呼び出し

 Androidのコードの修正です。アプリ起動時にライブラリをロードする処理とカメラを利用するクラス内でメソッドの呼び出しを追加する必要があります。

ORBDetect.java

 新しく作成したモジュールを追加で読み込みます。

} else {
    // 追加で読み込むJNIライブラリがあればここでロードする
    System.loadLibrary("imdetector");
}

CameraActivity.java

 JNIでOpenCVのオブジェクトをネイティブとの間でやりとりするには、nativeObjというアドレスを指すlong型変数を使います。訓練画像を登録するメソッドはonResume()メソッド内で呼び出しています。

// 訓練画像を登録するメソッド
private void initTrain(){
    // リソースから訓練画像を取得してMat型に変換
    Bitmap image = BitmapFactory.decodeResource(getResources(), R.drawable.train_img);
    Mat mat = new Mat();
    Utils.bitmapToMat(image, mat);
 
    // ネイティブメソッドの呼び出し
    setTrain(mat.nativeObj);    
}
 
// CvCameraViewListener2のメソッドの実装
public Mat onCameraFrame(CvCameraViewFrame inputFrame) {
    Mat frame = inputFrame.rgba();
    Mat gray = inputFrame.gray();
    
    // ネイティブメソッドの呼び出し   
    detectImage(frame.nativeObj, gray.nativeObj);
    return frame;
}

画像認識実装アプリの実行結果

 対象画像の検出に成功すると、Java_com_orb_CameraActivity_detectImageメソッド内のcv::putTextが呼び出されて、画面上部に検出したことを示す文字が表示されます。ただし、処理速度は2fps程度とかなり低速です。次は処理速度の改善を行います。

次のページ
パフォーマンス改善

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
近未来の技術トレンドを先取り! 「Tech-Sketch」出張所連載記事一覧

もっと読む

この記事の著者

飯田 勝也(TIS株式会社)(イイダ カツヤ)

TIS株式会社 エンタープライズソリューション推進部所属。2012年入社後、ERPの保守・追加機能開発の業務を経て、現在はエンタープライズモバイルの分野でMobile Platformを活用したiOS、Androidのアプリ開発およびコンサルティング業務に携わっています。 新しいもの好きな性格で、い...

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/8454 2015/02/23 14:00

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング