パフォーマンス改善
デバッガでの時間計測の結果からパフォーマンスのボトルネックになる部分は特徴量の計算に関わる部分だと分かりました(計測結果は省略)。
他にもいい方法があると思いますが、特徴量計算の速度を最も手軽に改善する方法は計算対象そのものを少なくする、つまり処理する画像のサイズを小さくすることです。今回は画像をリサイズしてから特徴量計算を行うことで、処理速度の改善に一定の効果が見られました。
その一方で、単純にリサイズするだけだと画像が粗くなってしまい、認識精度が落ちてしまいました。この点に関しては、ガウシアンフィルタをかけて画像を平滑化することが有効でした。では、これらの画像データ前処理について詳しく見てみましょう。
処理する画像を少なくする
画像のリサイズ
下の図はMacのデスクトップアプリでORB検出器による特徴量検出のパフォーマンス測定を行った結果です。棒グラフが測定時間を表しており、右側ほど小さい画像で処理した結果になるのですが、画像サイズを小さくすることでかなりの処理速度の改善が望めることが予想できます。画像をリサイズするには、OpenCVライブラリのresizeメソッドが使えます。
ORB検出器のパラメータ
特徴量計算には、前のページで画像処理を実装した際にはORBクラスのインスタンスを初期パラメータのみで生成していました。ORBインスタンスを生成する際に設定できるパラメータがないか確認してみましょう。リファレンスからコンストラクタのパラメータを要約したものを以下に示します。
- nfeatures:抽出する特徴の数。
- scalefactor, nlevels:画像ピラミッドの倍率と段数にあたる。
- edgeThreshold:特徴として認識するための領域のサイズを指定する。
- WTA_K:特徴量記述をどのように行うかを指定する。具体的には特徴量記述時に使用する特徴点の数を指定し、デフォルトの"2"の場合は2つの特徴点のペアから特徴量を記述する。
- scoreType:特徴点のランキングのために使用されるアルゴリズム。
- patchSize:特徴記述時の画像パッチのサイズを設定する。
ぱっと見るとnfeatures(デフォルト値は500)を変更することによって、処理が速くなりそうですが、内部的な処理ではすべての特徴点になりうる候補を画像の中から抽出後、ランキングをつけてパラメータで指定された特徴の数を抜き出しているので、訓練画像とのマッチングでは多少の速度向上が見られますが、認識の精度も落ちるためそれほど効果はありません。
いくつかのパラメータを変更したところ、scalefactorとnlevelsを変更することで処理速度の向上がみられました。ORBアルゴリズムでは1つの画像を処理する際に複数の倍率の画像を処理することにより、距離の違いによって検出できなくなるという事態を回避しています。デフォルト値では、scalefactorが1.2、nlevelsが8という値が設定されており、1倍から1/1.2^1、1/1.2^2、・・・、1/1.2^8という9段階の倍率の画像に対して処理が行われています。この値の倍率を1.2→1.4まで上げることで、画像の認識も可能なレベルで処理速度の改善が見られました。
フィルタについて
画像を処理する際には目的に応じてさまざまなフィルタが使われます。ガウシアンフィルタに代表される平滑化フィルタは、画像をぼかしてノイズを除去することができます。ラプラシアンフィルタが有名な例として紹介される鮮鋭化フィルタを使えば、輪郭がぼけた画像をシャープにすることができます。今回はガウシアンフィルタを用いることでリサイズした画像でも認識精度を維持することに成功しました。下の2つの画像のうち、左がフィルタなしで特徴検出した写真、右はガウシアンフィルタをかけて特徴検出した写真です。色のついた円が画像の中で、訓練画像に近いと判断された箇所を示しています。ぼかしを行うことでノイズが低減され、画像の中でも目的に合った特徴検出を行うことができました。ガウシアンフィルタをかけるには、GaussianBlurメソッドを使います。
画像データ前処理の実装
ここまでの内容を実装した例が以下になります。まず、7行目のようにorbのコンストラクタを呼び出す部分でパラメータを変更します。また、27,28行目で画像前処理としてリサイズと平滑化を行うこと認識精度を維持して実行速度が改善できます。まったく同じなので省略しましたが、画像を登録する際にも画像の前処理を実装します。
JNIEXPORT void JNICALL Java_com_orb_CameraActivity_setTrain
(JNIEnv *env, jobject obj, jlong addrTrainImage)
{
// 省略
// ORBコンストラクタの呼び出し
orb = cv::ORB(500, 1.4f, 8, 31, 0, 2, cv::ORB::HARRIS_SCORE, 31);
// 省略
}
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 resized_query_img, blurred_query_img;
// 画像を取得
cv::Mat& rgba_query_img = *(cv::Mat*)addrRgba;
cv::Mat& gray_query_img = *(cv::Mat*)addrGray;
// 画像前処理
cv::resize(gray_query_img, resized_query_img, cv::Size(gray_query_img.cols * 0.60, gray_query_img.rows * 0.60));
cv::GaussianBlur(resized_query_img, blurred_query_img, cv::Size(5,5), 12, 12);
// 特徴量を計算
orb.operator()(blurred_query_img, cv::noArray(), query_features, query_descriptor, false);
// 以下は省略
}
これらの変更点を実装後、ndk-buildコマンドでビルドし直さなければならないことに注意してください。ネイティブコードで書かれたソースコードは普通にADTでビルドしようとしてもコンパイルはされません。ndk-build実行後、プロジェクトをADTでビルド、実行すると、結果として8fps程度の速度で動くようになり、感的なストレスはかなり軽減されました。ただし、画像ピラミッドのパラメータを変更した影響で画像との距離の変化により認識の精度がやや落ちたという印象は受ける結果になりました。
まとめ
Google GlassでOpenCVを活用した画像認識アプリケーションの実装の紹介は以上です。
今回の検証を行った感想ですが、ウェアラブル端末にはまだ課題があると感じる部分はある一方、2020年には数兆円規模になるとも言われている分野だけのことはあって、わくわくさせられるものを感じました。
また、画像認識の実装に利用したOpenCVについても思ったよりも手軽に扱えて、今後活用のシーンは増えてくるのではないかと感じました。流行といわれて久しいビッグデータで同様に、画像処理の分野でも数学的な考え方がバックグラウンドにありますが、数学がそれほど得意ではない人でもプログラムの実装ができるように工夫がされていて、一見敷居は高そうに見えますがこれからライブラリが充実してくることを予想するとこれから使えそうだなと感じました。
ウェアラブルもOpenCVもこれから面白くなっていくと思うので今のうちから少しでも技術を吸収しておいて、未来に備えたいところです。



