OpenCVの最近のブログ記事



なんだかんだいって最終的に自分で書くことになると思うので、大先輩であるOpenCVのcvMatchTemplate関数を読んでおべんきょー。コードは次のURLから入手できます。



テンプレートマッチングについてもまとめたのでどうぞ。


ちゃちゃっと読んでいくよ!



323 CV_IMPL void
324 cvMatchTemplate( const CvArr* _img, const CvArr* _templ, CvArr* _result, int method )

引数がたくさん!



  • const CvArr* _img

  • const CvArr* _templ

  • CvArr* _result

  • int method


CvArrはOpenCVで用いられる配列用の型。_imgはソース画像、_templはテンプレート画像。_resultはマッチングの結果を返すための変数。各座標ごとにマッチングの結果を配置して返すので、これもグレースケール画像のような出で立ちのデータになる。


methodはマッチングアルゴリズムの指定で、以下の定数が使える。



  • CV_TM_SQDIFF

  • CV_TM_CCORR

  • CV_TM_CCOEFF

  • CV_TM_SQDIFF_NORMED

  • CV_TM_CCORR_NORMED

  • CV_TM_CCOEFF_NORMED


これらの意味するところはOpenCVのリファレンスで確認することができるのですが、とりあえずコードを読んでいきたいと思います。


以下、しばらく変数の初期化や作業用バッファの確保などが続きます。



347 int num_type = method == CV_TM_CCORR || method == CV_TM_CCORR_NORMED ? 0 :
348 method == CV_TM_CCOEFF || method == CV_TM_CCOEFF_NORMED ? 1 : 2;
349 int is_normed = method == CV_TM_CCORR_NORMED ||
350 method == CV_TM_SQDIFF_NORMED ||
351 method == CV_TM_CCOEFF_NORMED;

若い世代のC使いは三項演算子が苦手です(ホントか?


まあそれはそれとして、まずはnum_typeの確定から。コードを見れば一目瞭然ですが、num_typeは_NORMEDの有無に関係なくSQDIFFか、CCORRか、CCOEFFかを表すようになります。CCORRなら0、CCORFFなら1、そうでなければ(つまりSQDIFFなら)2。


逆にis_normedは末尾に_NORMEDがついた定数かどうかをチェックします。_NORMEDのつく定数の場合はis_normedが真になる(Cでは真は0以外だけどC++でもそうなのかな?)。


続く3行。



353 CV_CALL( img = cvGetMat( img, &stub, &coi1 ));
354 CV_CALL( templ = cvGetMat( templ, &tstub, &coi2 ));
355 CV_CALL( result = cvGetMat( result, &rstub ));

CV_CALL?


たぶんプリプロセッサマクロっぽいけど、見慣れない記法なのでちょっと不安。


ググってみたら先達の足跡が。



CV_CALLってなによ?[後で調べる]

わかんねーきっとプリプロセッサで処理されるのだろうが・・・


MEMO - OpenGL/Cudaツホクウオュ

あとで調べたけど追記するの忘れたパターンですね!!(ごめんなさいじょうだんです


もうちょいググってみるとこんなのが。OpenCV開発者向けのコーディング規約みたいなものかなー。



Instead of above functions one can use macros, which are more convenient to use:



  • CV_ERROR and OPENCV_ERROR instead of cvError.

  • CV_CALL and OPENCV_CALL instead of calling function and checking the status.



いくつかの関数は、それ自体をコールする代わりにマクロを利用すると便利で、推奨されているみたい。


CV_CALLの場合は、関数呼び出しとその値のチェックの代わりに利用するとエラー処理とかをライブラリにおまかせできてイイカンジっぽい。


とりあえず突っ込んだことは必要ないので、とりあえず今は普通にコールしていると考えることにします。


エラー処理


357行目からしばらくエラー処理が続く。



357 if( CV_MAT_DEPTH( img->type ) != CV_8U &&
358 CV_MAT_DEPTH( img->type ) != CV_32F )
359 CV_ERROR( CV_StsUnsupportedFormat,
360 "The function supports only 8u and 32f data types" );

入力画像のデータ単位が8bit符号なし整数か32bit浮動小数点のときのみ許容され、それ以外の場合はエラー。



362 if( !CV_ARE_TYPES_EQ( img, templ ))
363 CV_ERROR( CV_StsUnmatchedSizes, "image and template should have the same type" );

入力画像とテンプレート画像のデータ単位が同じでなければエラー。



365 if( CV_MAT_TYPE( result->type ) != CV_32FC1 )
366 CV_ERROR( CV_StsUnsupportedFormat, "output image should have 32f type" );

結果を受け取るためのデータ単位は32bit浮動小数点でなければエラー。



368 if( img->rows < templ->rows || img->cols < templ->cols )
369 {
370 CvMat* t;
371 CV_SWAP( img, templ, t );
372 }

条件は入力画像よりテンプレートのほうが行・列どちらかでも大きい場合。


CV_SWAPに違和感があるなーと思ったのは、普通Swapと名のつく関数は二引数のものが多いからかなー。もうひとつはなんだろ。ワーキングバッファかな。


ググると先ほどの先達のページが。



CV_SWAP( a, b, t )

aとbを入れ替える関数.


どうやらOpenCVで対応しているCvPointやIplImageなどでも可能なようだ.


tは入れ替えの際に必要になる一時データ保存用変数.当然aとbと同じ型.


関数について - OpenCV Memo

お、当たり。


ってことは入力画像よりテンプレート画像のほうが大きいときは入力とテンプレートを入れ替えちゃうのかな。cvMatchTemplateには「入力>テンプレートでなければならない」って制約があったような気がするんだけど、これで対応されてるってことは大丈夫なのかな?(もっともテンプレートのほうが大きい状況もなかなかないと思うけど)



374 if( result->rows != img->rows - templ->rows + 1 ||
375 result->cols != img->cols - templ->cols + 1 )
376 CV_ERROR( CV_StsUnmatchedSizes, "output image should be (W - w + 1)x(H - h + 1)" );

結果格納用のバッファは幅・高さともに(入力 - テンプレート + 1)でなければならない。これはテンプレートマッチングの性質上自明な制約だったはずなんだけど、その理由をド忘れ中。書いてたら思い出すかなー。



378 if( method < CV_TM_SQDIFF || method > CV_TM_CCOEFF_NORMED )
379 CV_ERROR( CV_StsBadArg, "unknown comparison method" );

アルゴリズムの指定が定数として準備されているものかどうかの確認。ここでこういう記述をするなら347行目あたりのフラグの処理ももっと高速かつ簡潔に記述できるのに!わかりやすさを優先してるならそれで統一したらいいじゃない!



381 depth = CV_MAT_DEPTH(img->type);
382 cn = CV_MAT_CN(img->type);

depthは入力画像の色深度。cnは何かよくわからないけどとりあえずスルー。



384 if( is_normed && cn == 1 && templ->rows > 8 && templ->cols > 8 &&
385 img->rows > templ->cols && img->cols > templ->cols )
386 {

条件。is_normedが立ってて、cnが1で、テンプレートが8x8より大きく、入力画像がテンプレートより大きくなければならない。該当する場合、以下の処理。



387 CvTemplMatchIPPFunc ipp_func =
388 depth == CV_8U ?
389 (method == CV_TM_SQDIFF_NORMED ? (CvTemplMatchIPPFunc)icvSqrDistanceValid_Norm_8u32f_C1R_p :
390 method == CV_TM_CCORR_NORMED ? (CvTemplMatchIPPFunc)icvCrossCorrValid_Norm_8u32f_C1R_p :
391 (CvTemplMatchIPPFunc)icvCrossCorrValid_NormLevel_8u32f_C1R_p) :
392 (method == CV_TM_SQDIFF_NORMED ? (CvTemplMatchIPPFunc)icvSqrDistanceValid_Norm_32f_C1R_p :
393 method == CV_TM_CCORR_NORMED ? (CvTemplMatchIPPFunc)icvCrossCorrValid_Norm_32f_C1R_p :
394 (CvTemplMatchIPPFunc)icvCrossCorrValid_NormLevel_32f_C1R_p);

これはとんでもなく長い三項演算子の連続ですね!アクロバティックだけどifを書き連ねるよりはきれいかなー。


この長い三項演算子をスルーしつつ今日はおひらき。続きはまた明日。




ひょんなことからモーションキャプチャシステムみたいなものを作ることになって、その実験として小規模なものを開発しているところ。キャプチャシステム自体は未完成です。


準備するもの


以下のページに従って開発環境を整える。


インストール - OpenCV@Chihara-Lab.


具体的にインストールしたものは次のとおり。



  • Visual C++ 2005 Express Edition

    • SP1 for Windows Vista

    • Platform SDK



  • OpenCV

  • IPL


経験のある人は知ってると思うけど、Platform SDKのインストールには異常に時間がかかるので、本の一冊や二冊は準備しておくといいと思う。


開発環境


使用したマシンは工人舎のSH8WP12A。USBカメラが手元にないと思っていたところ、こいつが内臓していたのでこれを使うことにした。


ところが内臓しているため、PlatformSDKのインストール中にカメラを別のマシンで使うことができず、もう一台のマシン(以前にPlatform SDKインストール済み)で先んじてテストしておくことができなかった。


あとOpenCVのインストーラをメディアにコピーしておくのを忘れたために、これをインストールすることもできなかったので、ManyCamsを使ってRubyからキャプチャして時間を潰してた。これは本当に時間を無駄にしたと思うので、事前に準備しておくといいと思う。


サンプルをチェキ!


OpenCVをインストール後、パスを通せばサンプルが動くということなのでチェックしてみた。


OpenCV/samples/を開くと、ソースファイルやプロジェクトファイルなどにまぎれて実行ファイルがあったので、ビルドなどをする前にこっちを見てみた。エッジの抽出やフラクタルの描画みたいなことをやっていて面白かったので見てみるといいと思う。


で、肝心のビルドはというと、これは失敗した。読み取り専用がどうとか、見慣れないエラーが出てたので、そろそろ時間も遅いし帰るかーという流れに。


部室を出て電車に乗った頃に気づいたんだけど、サンプルが動くというのは「サンプルプロジェクトがビルドできる」という意味ではなく「サンプルの実行ファイルが動作する」という意味だったのかも、と思った。


ここまでが昨日の話。


テスト用プロジェクトの作成


そういう流れで、もう面倒くさいのでプロジェクト作成して依存ファイルの追加設定なども済ませてやろうと思った(今冷静に考えると、これはプロジェクト単位での設定なのでサンプルとは関係ないのよね)。


そのままここに則ってカメラキャプチャをしようとしたのだけど、動作はするしGUIも生成される一方でカメラの映像が取得できない。


色々調べてみると、映像の取得ではなくカメラの取得(デバイスハンドルの取得みたいな感じ)でコケてることが判明。cvCaptureFromCAMはその引数で取得するカメラの種類を指定するのだけど、サンプルではこれが -1 となっていた。引数に -1 を与えると、cvCaptureFromCAMはイイカンジにはからって適当にカメラを取得してくれる。


これが不審だったので、試しに定義されている CV_CAP_ANY, CV_CAP_MIL, CV_CAP_VFW, CV_CAP_IEEE1394をそれぞれ与えて実行してみたところ、CV_CAP_VFW以外は取得に失敗。自動選択されているのはCV_CAP_CFWらしい。


VFWはVideo For Windowsの略で、正攻法ではこいつとDirectShowを組み合わせて実装するらしい。今回はGW中に完成させる必要があるので、手っ取り早く使えるものが欲しいので、別の方法を考えた。


まず、サンプルでカメラが取得できるかどうか調べてみた。camcapture.c(みたいな感じの(うろ覚え))をビルドしてみると、先ほどテスト用プロジェクトで発生したのと同じ状態になった。


ところが、OpenCV/otherlibs/cvcams/samples/にあるサンプルを実行してみると、普通にキャプチャできた。


CvCamを使うことに


どうやらcvcamは廃れる運命にある(具体的にはOpenCVのchangeLogにおいてその撤廃が告知されている)ようなのだけど、別にそんな末永く使うつもりじゃないし、それまでにはDirectShow+VFWに移行できると思うので、今回はcvcamを使うことにした。


cvcamを使うと、普通にキャプチャできていい感じですね。


赤い部分の抽出


カメラベースでマーカーを利用したキャプチャを行おうと考えているので、特定の色を抽出する処理が必要。


(R,G,B) = (255,0,0)な画素だけ残して、あとは(0,0,0)で埋めてしまえばいいんじゃね!? とか思うほどバカではないですが、程度問題で結局のところバカでした。


(R > 100,G,B)な画素ryということで実装してみたところ、当然ながら白も黄色も拾ってしまって話にならず、まずはさらに安直に考えを進めて「G, Bが小さければいいんじゃね?」という発想に至る。


というわけで(R > 100, G < 70, B < 70)の画素を残すようにしてみる。そこそこきれいにはなったものの、影になっている部分(暗い赤)などを拾えず、形状が欠損してしまう。


というわけで今度は比にして考えてみた。100:70ということで( R/G > 1.42 && R/B > 1.42)の画素のみを残してみると、意外とイイカンジになった。比率を調整して、今日は最終的に以下のような感じに。


f:id:Tnzk:20080504221429j:image:w200


赤い領域の抽出(1)


f:id:Tnzk:20080504221428j:image:w200


赤い領域の抽出(2)


画面左下のウィンドウが原画像、左上のウィンドウが抽出した画像を表示している。


あとはこいつらの重心を求めたりして、なんとかして座標値にする。もちろんペンとかだとどこの座標にするかという話にもなるので、ちゃんとマーカーを作ってやる。この辺は明日。


で、問題点は同様に実装できるのがR,G,Bを抽出する場合のみということ。紫とかそういう色が欲しいときにどうすればいいかというのも問題なので、そこも明日の課題。比率からもう少し考えを進めれば実現できると思うけど、どうだろう。


今回のことですごく思ったんだけど、画像処理(というほどのことはやっていないけど)は楽しいよ!3Dレンダリングが楽しいのと同じで、処理結果が目に見えるのはうれしい。


このアーカイブについて

このページには、過去に書かれたブログ記事のうちOpenCVカテゴリに属しているものが含まれています。

前のカテゴリはMODxです。

次のカテゴリはPICです。

最近のコンテンツはインデックスページで見られます。過去に書かれたものはアーカイブのページで見られます。

ウェブページ

Powered by Movable Type 4.32-ja