Miyabiarts.net

一年に一度の更新頻度

カテゴリーアーカイブ: OpenCV

2枚の画像のモザイキング

前に紹介した「OpenCVを用いた特徴点対応付け」を応用することで複数の画像をモザイキングすることができます。
今回は、OpenCVを用いて下に示す2枚の画像をモザイキングして、1枚の大きな画像を作ります。
今回の例では、2枚の画像が大体半分程度重なり合ってないと上手くいかないです。
あと、画像合成の関係上、1枚目の画像に対して2枚目の画像が左側になるように撮影しないと正しい結果が得られませんので気をつけてください。

モザイキングする画像

画像間の対応付け

SURFで検出した特徴点間の対応を全て表示しています。

モザイキング結果

コード

#include <opencv/opencv2.h>

int main()
{
  // モザイキングする画像
  std::string fn1 = "image0.png";
  std::string fn2 = "image1.png";

  // 画像読み込み(グレースケールで読み込み)
  cv::Mat_< unsigned char > image1 = cv::imread( fn1, 0 );
  cv::Mat_< unsigned char > image2 = cv::imread( fn2, 0 );

  // 特徴点の検出
  cv::SurfFeatureDetector detector;
  std::vector< cv::KeyPoint > keys1, keys2;
  detector.detect( image1, keys1 );
  detector.detect( image2, keys2 );

  // 特徴量の記述
  cv::SurfDescriptorExtractor extractor;
  cv::Mat_ desp1, desp2;
  extractor.compute( image1, keys1, desp1 );
  extractor.compute( image2, keys2, desp2 );

  // 特徴点の対応付け
  std::vector< cv::DMatch > matches;
  cv::FlannBasedMatcher matcher;
  matcher.match( desp1, desp2, matches );

  // 対応点をstd::vectorに格納
  std::vector< cv::Vec2f > points1( matches.size() );
  std::vector< cv::Vec2f > points2( matches.size() );

  for( size_t i = 0 ; i < matches.size() ; ++i )
  {
    points1[ i ][ 0 ] = keys1[ matches[ i ].queryIdx ].pt.x;
    points1[ i ][ 1 ] = keys1[ matches[ i ].queryIdx ].pt.y;

    points2[ i ][ 0 ] = keys2[ matches[ i ].trainIdx ].pt.x;
    points2[ i ][ 1 ] = keys2[ matches[ i ].trainIdx ].pt.y;
  }

  // 画像間の平面射影変換行列を取得
  cv::Mat_< float > homography = cv::findHomography( points1, points2, CV_RANSAC );

  // 画像の読み込み(RGB画像で読み込み直し)
  cv::Mat_< cv::Vec3b > rgb_image1 = cv::imread( fn1, 1 );
  cv::Mat_< cv::Vec3b > rgb_image2 = cv::imread( fn2, 1 );

  // 画像1を画像2の空間に投影
  // 出力画像の大きさは適当に決定
  cv::Mat_< cv::Vec3b > result;
  cv::warpPerspective( rgb_image1, result, homography, cv::Size( static_cast( rgb_image1.cols * 1.5 ), rgb_image1.rows ) );

  // 画像2を結果画像にコピー
  // 合成方法は上書き
  for( int y = 0 ; y < rgb_image2.rows ; ++y )
  {
    for( int x = 0 ; x < rgb_image2.cols ; ++x )
    {
      result( y, x ) = rgb_image2( y, x );
    }
  }

  // モザイキング結果を表示
  cv::imshow( "Mosaicing", result );
  cv::waitKey();

  return 0;
}

Boost.勉強会 #5 名古屋で発表してきました

  • 開催日時:2011/05/14 13:00~18:00
  • 開催場所:株式会社エイチーム(名古屋市西区牛島町6番1号 名古屋ルーセントタワー32F)
  • ハッシュタグ:#boostjp

先週の関西ゲームプログラミング勉強会に続いて、今度はBoost.勉強会で発表してきました。

発表内容は「OpenCVを使った画像処理」ということで、Boostは一切関係ありません(笑)

いやまぁ、一応Boost.GILは紹介しましたよ!

 

実際のところ、前日に研究室内の種々の資料作成などで、今回の発表スライドをまともに作り始めたのは前日の夜からでした。

大枠は作っていましたが、デモに至っては当日の勉強会の途中で作っていましたからね。

ただ、作ったのはよいですが、プロジェクタにPCの画面が映らずに結局デモは使えなかったという悲しさです。

 

発表終了後に色々な人から画像処理について聴かれたので、なかなか上手く行った感じがして良かったです。

まだまだ色々な内容について発表するような感じでBoost.勉強会で発表することになっているみたいなので、機会があれば、また何かしら話させていただきます。

運営・発表者・参加者の方々はお疲れ様でした。

モテるOpenCV女子力を磨くための4つの心得

こんにちは、コンピュータビジョンを専攻しているmiyabiartsです。

私は職も知識もありませんしドクターですが、コンピュータビジョンに関してはプロフェッショナル。

今回は、モテるOpenCV女子力を磨くための4つの心得を皆さんにお教えしたいと思います。

1. あえてOpenCV1.0を使う

あえてOpenCV1.0をインストールしましょう。

そして好みのOpenCV男子がいたら話しかけ、わざとらしくOpenCVを出していじってみましょう。

そして「あ~ん!このOpenCV本当にマジでチョームカつくんですけどぉぉお~!」と言って、男に「どうしたの?」と言わせましょう。

言わせたらもう大成功。

「OpenCVとか詳しくなくてぇ~!ずっとコレ使ってるんですけどぉ~!使いにくいんですぅ~!ぷんぷくり~ん(怒)」と言いましょう。

大体の男はOpenCV2.2を使いたがる習性があるので、古かったとしてもOpenCV2.1を使っているはずです。

 

そこでOpenCV男子が「新しいOpenCVにしないの?」と言ってくるはず(言ってこない空気が読めないOpenCV男子はその時点でガン無視OK)。

そう言われたらあなたは「なんかなんかぁ~!最近OpenCV3.0が人気なんでしょー!? あれってどうなんですかぁ? 新しいの入れたいんですけどわかんなぁぁああい!! 私かわいそーなコ★」と返します。

するとOpenCV男子は「OpenCV2.2でしょ? 3.0はまだ出てないよ。本当に良くわからないみたいだね。どんな機能が欲しいの?」という話になって、次の休みの日にふたりでインストール作業でデートできるというわけです。

あなたの女子力が高ければ、OpenCV男子がOpenCV作ってくれるかも!?

 

2. C++でCvMatを使うとモテる

「行列」とか「画像」などを表現する「CvMat」をコードに入れると、OpenCVの男性ユーザーは「なんか構造体カワイイなぁ」や「cv::Mat教えてあげたいかも」と思ってくれます。

C++上ではC言語よりも機能が拡張されたコードが書けるので 「CvMat」 を多用することによって、OpenCV男子はあなたをC++初心者で女の子らしいと勘違いしてくれるのです。

そういうキャラクターにするとほぼ絶対にC++erに嫌われますが気にしないようにしましょう。

3. とりあえずOpenCV男子には「えー! なにそれ!?  知りたい知りたーい♪」と言っておく

ハッカソンなどでOpenCV男子が女性に話すことといえば実装話や趣味(アニメ)の話ばかり。

よって、女性にとってどうでもいい話ばかりです。

でもそこで適当に「へぇーそうなんですかぁ~?」とか「Viewないですけどすごいんですねぇ」と返してしまうと、さすがのOpenCV男子も「この女Boost.GILユーザだな」と気がついてしまいます。

Boost.GILユーザ女だとバレたら終わりです。

そこは無意味にテンションをあげて、「えー! なにそれ!?  知りたい知りたーい♪」と言っておくのが正解。

たとえ興味がない話題でも、KinectとGPU対応でその場を乗り切りましょう。

積極的に話を聞いてくれる女性にOpenCV男子は弱いのです。

いろいろと話を聞いたあと、「KinectはOpenNIで、GPU対応がCUDAなんですね! 覚えたぞぉ! メモメモ!」とコメントすればパーフェクト。

続けてキーボードを打つ振りをしつつ「タタンッ!タタッ!ターーーッン!!」と言って、「どうしたの?」とOpenCV男子に言わせるのもアリ。

そこで「私のcmakeに記録しているのでありますっ☆」と言えば女子力アップ!

そこでまたOpenCV男子は「この子カッコカワイイかも!?」と思ってくれます。

私はVisualStudioもcmakeもありませんしMacユーザですが、こういうテクニックを使えば知識がない私のようなバカドクターのほうがモテたりするのです。

OpenCV男子はビルド作業に浸りたいですからね。

4. コーディングでは顔検出手法を使えない女をアピールせよ

OpenCV男子とコーディングに入ったら、真っ先にAdaBoostなどの検出手法を使ったコードを探して「あーん! 私これ使えないんですよねぇ~(悲)」と言いましょう。

するとほぼ100パーセント「どうして? 嫌いなの?」と聞かれるので、「嫌いじゃないし使いたいけど使えないんですっ><」と返答しましょう。

ここでまた100パーセント「嫌いじゃないのにどうして使えないの?」と聞かれるので、うつむいて3~5秒ほど間をおいてからボソッとこう言います。

「……だって、……だって、AdaBoost使ったら学習画像が大量に必要じゃないですかぁっ! 収集する後輩がかわいそうですぅ! まだ研究進んでないのにぃぃ~(悲)。卒論すら書けないんですよ……」と身を震わせて言うのです。

その瞬間、あなたの女子力がアップします。

きっとOpenCV男子は「なんて賢いViola Jonesのようなコなんだろう!絶対にゲットしてやるぞ! コイツは俺の女だ!」と心のなかで誓い、あなたに惚れ込むはずです。

意中のOpenCV男子と付き合うことになったら、そんなことは忘れて好きなだけ顔検出手法を使って大丈夫です。

「使えないんじゃなかったっけ?」と言われたら「科研費使うので大丈夫になった」とか「後輩の扱い慣れた」、「そんなこと言ってない」と言っておけばOKです。

OpenCV逆引きリファレンス

OpenCV逆引きリファレンス

いつか作れたら良いなーとか思っていたもの(自分だけじゃないはず)を@idojunさんが作ってくれたので、ここでも宣伝です。
やりたいことからコードを引けるという素晴らしいまとめです。

しかし、Sphinxは名前だけは知っていたのですが、こんなに綺麗に作れるならば、どこかで試してみようかなあ。

OpenCV祭り – 第10回コンピュータビジョン勉強会@関東(番外編)

第10回コンピュータビジョン勉強会@関東(番外編)

http://atnd.org/events/12245

日時:2011年03月05日(土) 13:00~
場所:慶応大学矢上キャンパス (東急東横線 日吉駅)
Ustream:http://www.ustream.tv/channel/learncv
公式Twitterタグ:#cvsaisentan

いつの間にか発表することになっていました。
そして、いつの間にか沢山の人達が参加登録していました。
なんだか、自分が関わるイベントが自分の知らない間に大きくなる傾向があるみたいなので、嬉しい反面、内心ドキドキが耐えません。

自分の発表でいっぱいいっぱいだったので、他の方の発表については、Togetterや他の方のブログを参考にしてください。

Togetter: http://togetter.com/li/108392

自分の発表は、「OpenCVを用いた一般物体認識」というタイトルで、前回の名古屋CV・PRML勉強会の内容を色々と追加修正したものを発表しました。
半分は一般物体認識がどういうものであり、通常用いられるフレームワークにおける処理についての説明を行い、もう半分はOpenCVを用いてそれらの処理を実装するにはどのクラスを使えば良いかについて紹介しました。
少し駆け足でしたが、全体を網羅して説明できたかと思います。

雰囲気がつかめていなかったので、ネタを入れるかどうか迷いましたが、前日の夜くらいから思いつくごとに入れていきました。
ネタに走り過ぎて、そっちの印象ばかりになるのもアレなんで、内容が疎かになるとダメだなとは思っていましたが、Twitter見ている限りでは割と大丈夫だったですかな?
ただ、ネタが分からない人が多かったもしれないのはすみませんでした。
自分のTwitterのTLとか見ると、QBとか常識過ぎるもので。。。

OpenCVのcv::Mat / cv::Mat_の使い方(cv::Mat_編)

前回の続きで、今回はcv::Mat_に関してです。
内容はほぼ前回のコードの置き換えなので説明は少ないです。
記事の構成も同じです。

画像の作成

cv::Mat_は見たままですが、cv::Matに対して型Tを与えて用いるためのクラスです。
そのため、変数を定義する際に画素の型を与える必要があります。
cv::Matでは画素の型はOpenCVの定数でしたが、cv::Mat_ではC++の型のことです。

cv::Mat_< T > image( 高さ, 幅 );

// グレースケール画像(8bit)画像
cv::Mat_< unsigned char > image( height, width );

// RGB画像(24bit)画像
cv::Mat_< cv::Vec3b > image( height, width );

画像の入出力

画像の入力は、cv::Matと同様です。

// グレースケール画像(8bit)画像
cv::Mat_< unsigned char > image = cv::imread( "ファイル名", 0 );

// RGB画像(24bit)画像
cv::Mat_< cv::Vec3b > image = cv::imread( "ファイル名", 1 );

画像の出力も同様。

cv::imread( "ファイル名", image );

任意の画素へのアクセス

cv::Mat_だと、変数を定義する際に型を与えているため、任意の画素へとアクセスする際に型を与える必要がありません。
以下の例では、位置(x,y)の画素に0xffを代入して白画素にしています。

// グレースケール画像(8bit)画像
image( y, x ) = 0xff;

// RGB画像(24bit)画像
image( y, x )[ 0 ] = 0xff;
image( y, x )[ 1 ] = 0xff;
image( y, x )[ 2 ] = 0xff;

画像全体の走査

cv::Matの時と同様に3つのアクセス方法で画像全体を走査します。

任意の画素へのアクセスによる走査

// グレースケール画像(8bit)画像
for( int y = 0 ; y < image.rows ; ++y )
{
  for( int x = 0 ; x < image.cols ; ++x )
  {
    image( y, x ) = 0xff;
  }
}

// RGB画像(24bit)画像
for( int y = 0 ; y < image.rows ; ++y )
{
  for( int x = 0 ; x < image.cols ; ++x )
  {
    image( y, x )[ 0 ] = 0xff;
    image( y, x )[ 1 ] = 0xff;
    image( y, x )[ 2 ] = 0xff;
  }
}

ポインタアクセスによる走査

// グレースケール画像(8bit)画像
for( int y = 0 ; y < image.rows ; ++y )
{
  unsigned char *p = &image( y, 0 );
  for( int x = 0 ; x < image.cols ; ++x )
  {
    *p = 0xff;
    ++p;
  }
 }

// RGB画像(24bit)画像
for( int y = 0 ; y < image.rows ; ++y )
{
  cv::Vec3b *p = &image( y, 0 );
  for( int x = 0 ; x < image.cols ; ++x )
  {
    ( *p )[ 0 ] = 0xff;
    ( *p )[ 1 ] = 0xff;
    ( *p )[ 2 ] = 0xff;
    ++p;
  }
}

MatIterator_による走査

// グレースケール画像(8bit)画像
cv::MatIterator_ itr = image.begin();
for( ; itr != image.end() ; ++itr )
{
   *itr = 0xff;
}

// RGB画像(24bit)画像
cv::MatIterator_ itr = image.begin();
for( ; itr != image.end() ; ++itr )
{
   ( *itr )[ 0 ] = 0xff;
   ( *itr )[ 1 ] = 0xff;
   ( *itr )[ 2 ] = 0xff;
}

OpenCVのcv::Mat / cv::Mat_の使い方(cv::Mat編)

OpenCVのC++インタフェースで行列、および画像を扱うためのcv::Mat / cv::Mat_ですが、主要なクラスの割にはまとまった情報が少ないので、基本的な使い方を紹介します。
ここでは、通常の行列として用いる方法ではなく、あくまで画像として扱うものとします。

今回は、cv::Matの方の使い方について書きます。
次回は、cv::Mat_について書きますが、内容としては少し扱い方が違うだけでほぼ同じです。

cv::Matとcv:Mat_

まずは、今回の対象であるcv::Matとcv::Mat_について説明します。
いずれも行列、および画像を扱うための二次元配列を表します。
cv::Matは、行列の1つの要素を型なしとして扱い、アクセスする際に型を与えます。
cv::Mat_は、定義する際に型を与え、要素にアクセスする際には型を必要としません。

これらのクラスを利用するためには、以下のようにヘッダファイルをインクルードします。
パスはちゃんと通しておいてください。

#include <opencv2/opencv.hpp>

画像の作成

任意のサイズ・画素の型で初期化する場合は、 以下のように書きます。
さっきcv::Matは、画素を型なしで扱うと書きましたが、あくまでC++の型のことで、1画素をどのように表現するかの型は必要となります。
この型は、OpenCVの定数で与えます。

cv::Mat image( 高さ, 幅, 画素の型 );

// グレースケール画像(8bit)画像
cv::Mat image( height, width, CV_8UC1 );

// RGB画像(24bit)画像
cv::Mat image( height, width, CV_8UC3 );

ここで、widthとheightを与える順番に気をつけてください。

画像の入出力

画像の入力は、読み込んだ画像をどのような型で扱うかで引数が異なります。

// グレースケール画像(8bit)画像
cv::Mat image = cv::imread( "ファイル名", 0 );

// RGB画像(24bit)画像
cv::Mat image = cv::imread( "ファイル名", 1 );

画像の出力は簡単で、以下のようにするだけです。

cv::imread( "ファイル名", image );

任意の画素へのアクセス

任意の位置の画素へのアクセスは以下のようにします。
cv::Matでは、画素の実際の値を読み書きするため、cv::Mat::atに対して型を与える必要があります。
以下の例では、位置(x,y)の画素に0xffを代入して白画素にしています。

// グレースケール画像(8bit)画像
image.at( y, x ) = 0xff;

// RGB画像(24bit)画像
cv::Vec3b &p = image.at( y, x );
p[ 0 ] = 0xff;
p[ 1 ] = 0xff;
p[ 2 ] = 0xff;

画像全体の走査

画像処理では、画像全体を画素毎にアクセスして処理を行うことが多々ありますが、上記の方法だと呼び出しのオーバヘッドが大きいので、ポインタを利用してアクセスする方法が多く取られます。
ただ、それでは範囲外アクセスの危険があるので、なるべくMatIterator_経由でアクセスすることをお勧めします。

やり方は以下のとおりです。
この例では、全画素に0xffを代入して白画素にしています。

加筆・修正(2011/2/24)(アクセス速度などに関する調査不足があったので、修正しました。)

任意の画素にアクセスする仕方は前述の通りですが、画像全体を走査して処理を行う場合は色々なアクセスの仕方があります。
ここでは、3つのアクセス方法を紹介します。
例では、全画素に0xffを代入して白画素にしています。

任意の画素へのアクセスによる走査

前述した任意の画素へのアクセスを行う方法です。

// グレースケール画像(8bit)画像
for( int y = 0 ; y < image.rows ; ++y )
{
  for( int x = 0 ; x < image.cols ; ++x )
  {
    image.at( y, x ) = 0xff;
  }
}

// RGB画像(24bit)画像
for( int y = 0 ; y < image.rows ; ++y )
{
  for( int x = 0 ; x < image.cols ; ++x )
  {
    cv::Vec3b &p = image.at( y, x );
    p[ 0 ] = 0xff;
    p[ 1 ] = 0xff;
    p[ 2 ] = 0xff;
  }
}

ポインタアクセスによる走査

スキャンラインごとに先頭画素のポインタを取得し、そのポインタ経由で画素にアクセスします。
次の画素へはポインタをインクリメントします。

// グレースケール画像(8bit)画像
for( int y = 0 ; y < image.rows ; ++y )
{
  unsigned char *p = &image.at( y, 0 );
  for( int x = 0 ; x < image.cols ; ++x )
  {
    *p = 0xff;
    ++p;
  }
 }

// RGB画像(24bit)画像
for( int y = 0 ; y < image.rows ; ++y )
{
  cv::Vec3b *p = &image.at( y, 0 );
  for( int x = 0 ; x < image.cols ; ++x )
  {
    ( *p )[ 0 ] = 0xff;
    ( *p )[ 1 ] = 0xff;
    ( *p )[ 2 ] = 0xff;
    ++p;
  }
}

MatIterator_による走査

画像全体をMapIterator_というIteratorを用いて走査することができます。

// グレースケール画像(8bit)画像
cv::MatIterator_ itr = image.begin();
for( ; itr != image.end() ; ++itr )
{
   *itr = 0xff;
}

// RGB画像(24bit)画像
cv::MatIterator_ itr = image.begin();
for( ; itr != image.end() ; ++itr )
{
   ( *itr )[ 0 ] = 0xff;
   ( *itr )[ 1 ] = 0xff;
   ( *itr )[ 2 ] = 0xff;
}

任意の画素を処理する際に隣接画素から値を求めたい場合には、その画素の位置情報が必要となります。
この位置情報については、以下のように取得できます。

for( ; itr != image.end() ; ++itr )
{
   // 画素の位置を取得
   const cv::Point &pos = itr.pos();

   // 隣接画素へアクセス可能
   image.at( pos.y - 1, pos.x - 1 );
}

アクセス速度

3つのアクセス方法を紹介しましたが、それぞれの方法でアクセス速度が異なります。
詳しくは「画像ピクセル値へのアクセスと計算速度」を参照してください。

OpenCV祭コワイ

3/5に第10回コンピュータビジョン勉強会@関東(番外編)、通称「OpenCV祭」が開催されます。
昨年、OpenCV2.2が公開されて、色々と機能が追加されたりと何かと話題だったので、良い機会です。

開催される企画が立ち始めた頃から眺めていたのですが、Twitter上でOpenCVに関することを割と呟いていたおかげか、

@dandelion1124
dandelion
@miyabiarts CV祭りin東京が3/5になりそうですが,名古屋から刺客として参戦されますか?
http://twitter.com/#!/dandelion1124/status/27330319749222401

とまぁ、お誘いを受けたわけで、はじめは参加だけしようかと思って、お茶を濁していたのですが、色々と会話しているうちに調子に乗って、

@miyabiarts
miyabiarts
@dandelion1124 もう、「C++ or pythonインタフェース」でも「OpenCVにある機能をひたすらGUIで見せる」でも「ポエム」でもなんでも良いので、時間が空いたら発表に突っ込んどいてくださいw
http://twitter.com/#!/miyabiarts/status/27385404852277249

と発言したおかげで、いつの間にか発表者側に回っていましたとさ。

まぁ最近良く触っているだけあって、発表するネタには困らないだろうと思っていました。

そして、昨日OpenCV祭のイベントページができて、その日のうちに参加者が定員に達したのですが、参加者の顔ぶれが怖すぎるんですがw
勉強会や研究でよく見かけるC++やARや機械学習詳しい人ばかりですよ((((;゜Д゜)))ガクガクブルブル

特に、私はOpenCV関係者というわけではなく、最近TwitterでOpenCVに呟いているだけの人なんですがw
ただまぁ、発表するからにはなるべく楽しんでもらおうとネタ作りをしてきます。
今のところ、話の流れで「Python+OpenCV」が発表予定になっていますが、内容が変わる可能性は高いです。
というか、まだ深く考えていません(・ω・)b

OpenCVを用いた特徴点対応付け

OpenCV2.2になって、C++インタフェースが随分整理されましたので、色々と触っていました。
主に特徴点周りのクラスを見ていたのですが、画像から特徴点を検出し、画像間で対応付けるコードを簡単に書けるようになりました。
以下に2画像間の特徴点の対応付けのコードを掲載します。

#include <opencv2/opencv.hpp>
//
int main()
{
	// 画像1の読み込み
	cv::Mat_<cv::Vec3b> img1 = cv::imread( "in1.jpg" );

	// 画像2の読み込み
	cv::Mat_<cv::Vec3b> img2 = cv::imread( "in2.jpg" );

	// SIFT用のデフォルトパラメータを設定
	double threshold = cv::SIFT::DetectorParams::GET_DEFAULT_THRESHOLD();
	double edge_threshold = cv::SIFT::DetectorParams::GET_DEFAULT_EDGE_THRESHOLD();
	double magnification = cv::SIFT::DescriptorParams::GET_DEFAULT_MAGNIFICATION();

	// SIFTの特徴点検出・特徴量抽出器を用意
	cv::SiftFeatureDetector detector( threshold, edge_threshold );
	cv::SiftDescriptorExtractor extractor( magnification );
	/*
	// SURFの場合
	cv::SurfFeatureDetector detector();
	cv::SurfDescriptorExtractor extractor();
	*/

	// 画像1から特徴点を検出
	std::vector<cv::KeyPoint> keypoints1;
	detector.detect( img1, keypoints1 );

	// 画像2から特徴点を検出
	std::vector<cv::KeyPoint> keypoints2;
	detector.detect( img2, keypoints2 );

	// 画像1の特徴点における特徴量を抽出
	cv::Mat descriptor1;
	extractor.compute( img1, keypoints1, descriptor1 );

	// 画像2の特徴点における特徴量を抽出
	cv::Mat descriptor2;
	extractor.compute( img2, keypoints2, descriptor2 );

	// 対応点を求める
	std::vector<cv::DMatch> matches;

	// L2距離を用いた全探索対応付け
	cv::BruteForceMatcher< cv::L2 > matcher;
	// Flannベースの対応付け
//	cv::FlannBasedMatcher matcher;

	// 特徴量比較による特徴点の対応付け
	matcher.match( descriptor1, descriptor2, matches );

	// 結果を描画
	cv::Mat_<cv::Vec3b> result;
	cv::drawMatches( img1, keypoints1, img2, keypoints2, matches, result );

	// 結果を出力
	cv::imwrite( "result.jpg", result );

	return 0;
}

といった感じで、簡単なコードだと思いませんか?

簡単にやっていることを説明します。
各画像から、SIFTにより特徴点を検出し、その特徴量を抽出します。
特徴点の検出は、cv::FeatureDetectorを継承したcv::SiftFeatureDectetorを、特徴量の抽出は、cv::DescriptorExtractorを継承したcv::SiftDescriptorExtractorを用いることでそれぞれ行うことが出来ます。
コード中のコメントを入れ替えれば、SIFTからSURFに簡単に切り替えることもできます。
その他の特徴点検出・特徴量抽出手法についても同じです。
そして、特徴量間の対応付けを行います。
こちらも対応付けに用いる手法(cv::DescriptorMatcher)を、定義するクラスで簡単に切り替えることができます。
今回は、L2距離基準を用いた全探索対応付けを行うcv::BruteForceMatcherを用いています。
最後に、特徴点とその対応をcv::drawMatchesにより描画することで、結果を得ることができます。

テスト画像

テスト画像は、以下の2種類を用いています。
左側が原画像で、右側は原画像を45度回転しています。
これらの画像の特徴点対応付けを行います。



特徴点対応付け結果

下の画像がそれぞれの対応付け結果です。
各画像中の円で表されるものが特徴点で、特徴点間に引かれている線分が特徴点の対応を示しています。
1つ目の画像は、特徴点自体があまり出ておらず、対応付けもそれほど精度良くないようです。
2つ目の画像は、特徴点が多く検出され、誤対応もありますが、大半は精度良く対応付いています。

特徴点対応付けの確認

特徴点の対応付けが正しく行われているか、同じ画像同士を対応付けることにより確認しております。
下の画像を見れば、同じ特徴点が対応付いている(対応を表す線が平行である)ことが分かります。

というわけで、随分簡単に特徴点対応付けができるようになりました。
もちろん精度などの問題も残されているのでパラメータなどの調整が必要となってきます。

分からないことや不明なことがあれば、コメントください。

OpenCVでの画素へのアクセス方法

tsunotterで、

「OpenCVで任意のピクセルの画素値に直接どうやってアクセスする? #opencv」

という質問があったので、私のOpenCVでの画素へのアクセス方法を書いておきます。

私は、基本的にOpenCVはC++インタフェースを使っているので、画像はcv::Mat_で扱っています。
画素へのアクセスは、以下の2つの方法を使い分けており、普段は1つ目で、処理を速くしたい場合は2つ目の方法を用いています。
サンプルコードでは、400×400のRGB画像の全画素を赤くするもので、やっていることはcv::Fillと同じです。

任意の画素へのアクセス

単純にcv::Mat_::operator()( y, x )を用いて、画素にアクセスする手法です。
xとyが逆にならないように気をつけてください。
また、cols=width、rows=heightとなっています。

#include <opencv2/opencv.hpp>

int main()
{
  // 400x400 RGB画像を作成
  cv::Mat_<cv::Vec3b> img( cvCreateImage( cvSize( 400, 400 ), IPL_DEPTH_8U, 3 ) );

  // 全画素を赤くする
  for( int y = 0 ; y < img.rows ; ++y )
    {
      for( int x = 0 ; x < img.cols ; ++x )
	{
          // 画素へアクセス
	  img( y, x )[ 0 ] = 0;
	  img( y, x )[ 1 ] = 0;
	  img( y, x )[ 2 ] = 0xff;
	}
    }
  // 画像出力
  cv::imwrite( "result.bmp", img );
  return 0;
}

当然ですが、cv::Vec3bをunsigned charに変えることで、グレースケール画像も扱えます。

任意の画素へのアクセス(高速版)

1つ目の方法だと、1つの画素にアクセスするたびにcv::Mat_::operator ( y, x )を呼び出すオーバヘッドがかかるため、ポインタ操作でなるべくオーバヘッドを減らす方法です。
スキャンラインで画像を走査する際に、スキャンラインの先頭画素のポインタを取得した上で、後はポインタのインクリメンタルによりアクセスする画素を進めます。
気をつけることは、ポインタを進めすぎるとバッファ・オーバランが起こるので、アクセス範囲には十分気をつけてください。

#include <opencv2/opencv.hpp>;

int main()
{
  // 400x400 RGB画像を作成
  cv::Mat_<cv::Vec3> img( cvCreateImage( cvSize( 400, 400 ), IPL_DEPTH_8U, 3 ) );

  // 全画素を赤くする
  for( int y = 0 ; y < img.rows ; ++y )
    {
      // 先頭の画素のポインタを取得
      cv::Vec3b *p = &img( y, 0 );
      for( int x = 0 ; x < img.cols ; ++x )
	{
           // 画素へアクセス
	  ( *p )[ 0 ] = 0;
	  ( *p )[ 1 ] = 0;
	  ( *p )[ 2 ] = 0xff;
     // 次の画素へ進める
	  p++;
	}
    }
  // 画像出力
  cv::imwrite( "result.bmp", img );
  return 0;
}

cv::Vec3bをunsigned charに変えることで、グレースケール画像も扱えます。
このとき、画素へのアクセスに用いるポインタは型情報も持っているため、ポインタのインクリメンタルで画素単位で進めることができ、1画素が何バイトで構成されているかを意識しないで良いので楽ですし、間違いが少なくなります。

他にもやり方は色々ありますが、私は以上の方法で画素にアクセスすることが多いです。