OpenCV2でSIFT, SURFによる画像の対応付け
大学の講義で画像間の対応付けプログラムを作成するという課題が出されて、無事提出できたのでメモしておこうと思います。このような画像工学のプログラムは初めてで、特にMacOSX + Xcode4 + OpenCV2のサンプルが少なくて色々苦労しました。簡単ではありますがOpenCVのインストールから2画像の対応付け画像を生成するところまでを説明したいと思います。
執筆時のバージョン
OSX 10.7.4 + Xcode4.1 + OpenCV2.4.1
準備
Xcodeはインストール済みであるとして、まずはOpenCVをインストールします。
私はMacPortsを使用しましたが各自で好きな方法でインストールしてください。MacPortsなら
port install opencv
で楽チンにインストールできます
インストールが終わったらXcodeでプロジェクトを作成して、Command Line ToolでC++を選択します。
プロジェクトの作成が終わったらBuild Settingsタブを選択して、
- Valid Architecturesを[i386, x86_64]を[x86_64]に変更
- Header Search Pathsに/opt/local/include/を追加
- Library Search Pathsに/opt/local/lib/を追加
次はOpenCVのライブラリの設定。Build Settingsの隣にあるBuild Phasesタブを選択し、Link Binary With Librariesという項目の+マークでOpenCVの必要なライブラリ(.dylib)を追加します。私の環境ではopencvとフィルタリングすると一つも見つからなかったので(MacPortsだと/opt/local/libにインストールされるから?)Add Other…から/opt/local/libにあるライブラリを選択しました。今回のプログラムで必要なのは
- libopencv_core.2.4.1.dylib
- libopencv_features2d.2.4.1.dylib
- libopencv_highgui.2.4.1.dylib
- libopencv_imgproc.2.4.1.dylib
- libopencv_nonfree.2.4.1.dylib
プログラム
設定が終わったら後はプログラムを書くだけです。画像間の対応付けには特徴量が必要ですが、これはOpenCVで実装されているSIFTとSURFを使用しました。SIFT、SURFがどのような理論で画像間の対応を取るのかについては中部大学の藤吉教授の論文やスライドが日本語で分かりやすいのでオススメです。リンク
SIFTやSURFで特徴点を求めて、対応付けを行なって対応線を描画するということをマジメにやるとかなり大変なはずですが、ほとんどの機能がOpenCVに実装されているのでそれを使うとかなり少ない行数で実現することができます。
#include <iostream> #include <opencv2/highgui/highgui.hpp> #include <opencv2/nonfree/nonfree.hpp> #include <opencv2/legacy/legacy.hpp> //BruteForceMatcheに必要。opencv2.4で移動した? int main(int argc, char *argv[]) { //画像読み込み cv::Mat colorImg1 = cv::imread("box1.jpg"); cv::Mat colorImg2 = cv::imread("box3.jpg"); if(colorImg1.empty() || colorImg2.empty()){ std::cout << "No Image" << std::endl; return -1; } //特徴点抽出用のグレー画像用意 cv::Mat grayImg1, grayImg2; cv::cvtColor(colorImg1, grayImg1, CV_BGR2GRAY); cv::normalize(grayImg1, grayImg1, 0, 255, cv::NORM_MINMAX); cv::cvtColor(colorImg2, grayImg2, CV_BGR2GRAY); cv::normalize(grayImg2, grayImg2, 0, 255, cv::NORM_MINMAX); //SIFT // cv::SiftFeatureDetector detector; // cv::SiftDescriptorExtractor extractor; //SURF cv::SurfFeatureDetector detector(1000); cv::SurfDescriptorExtractor extractor; //画像から特徴点を検出 std::vector<cv::KeyPoint> keypoints1; detector.detect(grayImg1, keypoints1); std::vector<cv::KeyPoint> keypoints2; detector.detect(grayImg2, keypoints2); //画像の特徴点における特徴量を抽出 cv::Mat descriptors1; extractor.compute(grayImg1, keypoints1, descriptors1); cv::Mat descriptors2; extractor.compute(grayImg2, keypoints2, descriptors2); //特徴点の対応付け std::vector<cv::DMatch> matches; cv::BruteForceMatcher<cv::L2<float> > matcher; matcher.match(descriptors1, descriptors2, matches); //ソートしたn番目までの対応線を表示させる。nth_elementは要素を基準要素よりも手前に移動させるある種のソート int N=50; nth_element(matches.begin(), matches.begin()+N-1, matches.end()); matches.erase(matches.begin()+N, matches.end()); //対応づけされた画像の用意 cv::Mat matchedImg; cv::drawMatches(colorImg1, keypoints1, colorImg2, keypoints2, matches, matchedImg); /// 画像を表示するウィンドウの名前,プロパティ // CV_WINDOW_AUTOSIZE : ウィンドウサイズを画像サイズに合わせる // CV_WINDOW_FREERATIO : ウィンドウのアスペクト比を固定しない cv::namedWindow("image", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO); // ウィンドウ名でウィンドウを指定して,そこに画像を描画 cv::imshow("image", matchedImg); // キー入力を(無限に)待つ cv::waitKey(0); return 0; }
OpenCV2.0からC++のインターフェースが追加されたので全てC++で書いてみました。
注意点はOpenCV2.4からSIFTとSURFがfeature2dからnonfreeに移った点です。これに気が付くのに時間がかかってサンプルや他の方のプログラムがコンパイルできないという状況に延々とハマっていました・・・。
その他に手軽に特徴点間の距離を計算して対応を付けを行うBruteForceMatcherのヘッダーがいつからかlegacyに移ったらしいので、ここも注意です。自分は上のnonfreeと合わせてずっと悩んでいましたが、結局stackoverflowで検索して解決に至りました(~_~)
プログラムが何をやっているかは行数も多くないし、コメントを付けてるのでそれほど難しくなく理解できると思います。SIFTとSURFの切り替えはコメントアウトの切り替えで行うことができます。対応線の数はint N=50を変えることで調節できます。類似度が高い特徴点同士から上位N本の対応線を引くので、画像ごとに見やすい数に調節してください。
実行するとこんな感じになります
上:SIFT 下:SURF
SIFTとSURFは回転と拡大縮小に強いので、ある物体が他の画像ではどこに写っているかをこのように見つけることができます。SIFTとSURFの違いは詳しく分かっていないのですが、SURFの方がSIFTを簡略化して計算量を少なくしたもののようです。でも画像で見た感じでは精度にそれほど変わりがないように見えます
上:SIFT 下:SURF
こちらはカメラを平行移動させて、半分ほど共通部分が存在する画像間で対応を取った場合です。両方に写っている部分の対応はかなりいい感じに取れています。いわゆるパノラマ画像はこのようにして画像間の対応を求めてから合成して作られたりするようです.
画像工学のことはほとんど知らなかったので色々と勉強になりました。特に画像を特徴ベクトルで表現する事で今回のプログラムのように画像間の対応付けをしたり、類似画像を探したりすることができることを初めて知りましたが、これって自然言語処理の文書をベクトルで表現するという手法にそっくりですね。音声、自然言語処理、画像、機械学習の分野は手法が似ていると聞いたことがありますが納得。