2010/06/28

OpenCVで動画の動き補正

前回、パーティクルフィルタで対象の物体を追跡するプログラムを書きました。
今回は使い方をちょっと変えて、対象の物体を常に画面の中央に持ってくるようにしてみました。

元の動画は手でデジカメを持ち、前後左右に動きながら対象の車を撮影したもので、
車は動画の中心から離れたところにあります。

パーティクルフィルタでその車(今回は赤い色を抽出)の位置を特定し、
中心からの距離を使って動画をシフトすることで、対象の車が常に中心になるように
しています。



この方式だと、上下左右の移動は補正できますが、カメラが斜めに傾いたりすると
対応できなくなります。

斜めの補正はcvMinAreaRect2を使えば出来るようになるかもしれません。

ではまた。

2010/06/24

パーティクルフィルタ その2

前回の続きです。

前回はRGB形式の画像をHSV形式に変換し、Hを元に尤度を計算してましたが、なかなかうまくいかなかったのでRGB形式のまま、検索対象の色とのチャネルごとの差を求めて尤度を計算する方式に変えました。

検索対象の色は、マウスをクリックした座標近くにある色の平均としています。
Hのみを元に計算していたときよりは精度がよいようです。



サンプルコードを元にこれを作ってみましたが、
実際のパーティクルフィルタの理論はよくわかってません。

OpenCVの本にカルマンフィルターの説明があったので、
まずそれから理解しようかな~と思います。

ではまた。

今回のソースコードはこちら

2010/06/21

パーティクルフィルタ

今日はパーティクルフィルタの実験をしました。
パーティクルフィルタは推測器の1つです。詳しい説明はこちら

モーショントラッキングや、その他もろもろに利用されているようです。

パーティクルフィルタのサンプルコードに、昨日のRGB→HSV変換コードを加えて、
好きな色相の物体を追跡できるようにしてみました。


ソースコードはもう少し整形してから掲載します。

ではまた。

2010/06/20

OpenCV 色相情報の抽出

マーカー認識の次は物体認識と物体追跡を試してみることに。

物体認識には、形状の特徴をベースにしたものと、色情報をベースにしたものがあるようで、今回は色情報のものからやってみることにしました。

下準備として、特定の色を抽出するプログラムを作ってみました。

色情報で判断する場合、通常使用しているRGBだとどの色かがわかりにくいため、HSVに変換して処理しています。
HSVは色相、彩度、明度からなっているので、色相(H)だけ調査すればどの色かがわかります。(細かくはWikipediaを参照ください)

下記の動画は、表示する色相を0度から360度まで変化させたものです。
(OpenCVでは0~180で色相が一周していました)




家の中をWebカメラで色々写したところ、我が家では黄色か黄緑成分が少ないので
対象物の色は黄色か黄緑にすることにしました。
逆に、赤と青はそこらじゅうにあったため、このあたりの色は適さない模様です。
(壁、肌は赤成分に反応し、髪の毛や黒は青成分で反応してました)


今回のコードは短いのでこちらに掲載します。

//////////////////////////////////////
// 
// OpenCVで遊ぼう!
// http://playwithopencv.blogspot.com/
//
//hsv.cpp

//カラー画像を読み込み、BGR画像からHSV画像に変換し、
//特定の色相だけ抽出したものを表示します。

//色による物体追跡用のマスクとして使用する予定です。

#include "cv.h"
#include "highgui.h"
#include 

int main(int argc, char** argv)
{
 IplImage * source;
 source=cvLoadImage("HSV.png",1);
 if(source==NULL) return -1;

 IplImage * hsvimage;
 IplImage * bgrimage;
 IplImage * hueimage;
 IplImage * simage;
 IplImage * vimage;
 
 hsvimage=cvCreateImage(cvGetSize(source),IPL_DEPTH_8U,3);
 bgrimage=cvCreateImage(cvGetSize(source),IPL_DEPTH_8U,3);
 //H S Vの3つに分けるためのスペースを確保
 hueimage=cvCreateImage(cvGetSize(source),IPL_DEPTH_8U,1);
 simage=cvCreateImage(cvGetSize(source),IPL_DEPTH_8U,1);
 vimage=cvCreateImage(cvGetSize(source),IPL_DEPTH_8U,1);

 cvNamedWindow("orig");
 cvNamedWindow("result");
 
 int hue=0;   //0から180まで
 int huestep=1; //hueの増加・減少単位。
 int huerange=1; //許容する範囲。1以上にしてください。
 
 
 char pressd;

 //フォントの設定
 CvFont dfont;
 float hscale      = 0.5f;
 float vscale      = 0.5f;
 float italicscale = 0.0f;
 int  thickness    = 1;
 char text[255] = "";
 cvInitFont (&dfont, CV_FONT_HERSHEY_SIMPLEX , hscale, vscale, italicscale, thickness, CV_AA); 

 while(1)
 {
  
   cvCvtColor(source,hsvimage,CV_BGR2HSV);
 
   //Hue成分のみ抽出。
   cvSplit(hsvimage,hueimage,NULL,NULL,NULL);

   //現在のHue+許容範囲以上を切り捨てる
   //CV_THRESH_TOZERO_INVは、閾値以上の値を0にするフラグ。
   cvThreshold(hueimage,hueimage,hue+huerange,hue,CV_THRESH_TOZERO_INV);
 
   //現在のHue-許容範囲以下を0にする
   cvThreshold(hueimage,hueimage,hue-huerange,hue,CV_THRESH_BINARY);
   
   cvZero(simage);
   cvZero(vimage);
      
   cvOrS(simage,cvScalarAll(255),simage,hueimage);
   cvOrS(vimage,cvScalarAll(255),vimage,hueimage);

  
   //H S Vを合体させる
   cvMerge(hueimage,simage,vimage,NULL,hsvimage);
   cvCvtColor(hsvimage,bgrimage,CV_HSV2BGR);
 
   
   //OpenCVは0-180°でHueを扱っているが、本来は0-360°なので2倍しておく
   sprintf(text,"hue %d",hue*2);
   cvPutText(bgrimage,text,cvPoint(bgrimage->width/2,bgrimage->height-30),&dfont,CV_RGB(255,255,255));

   cvShowImage("orig",source);
   cvShowImage("result",bgrimage);

   pressd=cvWaitKey(100);
   if(pressd==27) break;

   if(hue>180) hue=0;
   hue++;
  
 }

 cvReleaseImage (&hsvimage);
 cvReleaseImage (&bgrimage);
 cvReleaseImage (&hueimage);
 cvReleaseImage (&simage);
 cvReleaseImage (&vimage);
 cvDestroyWindow("orig");
 cvDestroyWindow("result");
}

物体追跡のよい参考書がなかなか見つかりません。。。
Webで調べるしかないのかな~


ではまた。

2010/06/16

AIの世界に介入してみた

今週はオライリーの「実例で学ぶゲームAIプログラミング」という本を読んで
AIの勉強をしています。

サンプルコードの中に、小魚の群れとそれを捕食する魚のAIがありました。
小魚の群れや捕食する魚は、すべて自立的に動き回るのですが、
今回はOpenCVでマーカーを認識してその座標を捕食する魚の座標としてみました。



AIに対する理解がもっと進めば、自動マッピング+経路探索するお茶運びロボットとかも
出来るようになるかもしれません。

2010/06/13

OpenCV マーカーの識別化

昨日、今日とマーカーの識別プログラムを書いてました。

以前はマーカー内に文字を書き、輪郭で識別する方法も試しました。
マーカー内部に「」を書いたものと「」を書いたものの2つを識別させた場合、
カメラとの距離が近い場合はきちんと識別できましたものの、
距離が離れるとどうしても輪郭がぼやけるため、ばらつきが出てしまいました。


輪郭での識別はあきらめて、2次元コードによる識別を行うことにしました。
原理は単純で、小さい四角(以後、ドット)を縦横4つずつ並べ、ドットの組み合わせで
マーカーを識別することにしました。

ドットの黒は二進数の1に対応し、ドットの白は0に対応します。
ドット16個で16ビットとし、6万通り以上のパターンを作ることが出来ます。
下の画像のパターンだと、一番左は16進数で000Aなので10進数だと10、
真ん中は16進数で0309なので777、一番右は16進数で8000なので327678となります。
(灰色の線は見やすくするために書いているだけです。実際にはありません。)











マーカーの仕様は下記の通りとなります。
・マーカー全体のサイズ:縦横90mm
・マーカーの外側の太さ:10mm
・方向ドットとマーカーのフチとの間隔:5mm
・方向ドット:10mm四方を塗りつぶす
・数値ドット:10mm四方の枠内を8mm四方の正方形で塗りつぶす





















ドット解読の方法は、かなり適当ですが下記の通りとなります。
例:一番左上のドットの場合
マーカーをカメラで写してマーカー認識を行うと、白黒が反転された状態の画像となり、
左上のドットは、始点が(15,15)、終点が(25,25)の四角形の中に描かれます。

ドットの中心(20,20)の上下左右3画素((17,17)から(23,23))の画素の値を調べて、
白くなっているドットの数が一定以上あれば、左上のドットは塗りつぶされていると認識して、2進数の1になります。
これを16回繰り返すと、ドットの解読が完了します。

また、これと同じ方法を使って、方向ドットがどちらを向いているかを検出することも可能です。

実際の動作模様は下記の通りです。






最近コードが長くなってきたので、Googleサイトでコードの保管場所を作りました。
URLはhttp://sites.google.com/site/playwithopencv/ です。
マーカーの図面データもこっちに置きました。

ではまた。

2010/06/12

マーカー検出 向きがわかるようになりました

今週は色々とマーカーの向きを検出する方法を実験してましたが、
なかなかうまくいきませんでした。

うまくいかなかった例
・マーカーの内側を四角ではなく、5角形(SDカードみたいに、4角形の角の一つを切り落としたもの)にしてみた

期待:内側の輪郭点のうち、隣り合う輪郭点との距離を計算し、2点間の距離が最短になれば、その2点が切り落とされた角になるはず。
結果:角の落とし方が足りない場合、カメラとの距離があると4角形と認識されてしまった。また、角を落としすぎた場合は2点間の距離がほかの4辺と似たような値になってしまい、安定しなかった。

・5角形を再び利用。
期待:輪郭点間の距離ではなく、2組の輪郭点が成す直線が並行であれば、その輪郭点が基準点となるはず。(図形の赤い線)












結果:これも上記と同じく、誤判定が多く失敗。


いいマーカーの形は無いかな~とマーカー検出のネタを探してみると、
四角か丸のマーカーがほとんどでした。

んで、気を取り直して四角のマーカーで再挑戦。マーカーも作り直しました。


新マーカーの外枠の太さは10ミリ。
枠の内側に5ミリの余白をもうけました。
その内側は60ミリ四方の余白がありますが、10ミリは基準マーカー専用となるため、自由に使える余白は40ミリ四方となります。細かい説明はまた今度。










今回は下記の処理を行いました。

1.画像を2値化して、マーカーを検出します。
2.マーカーの内側の輪郭のデータを使って、マーカーの内側を正方形に透視変換します。
3.透視変換した結果は、下記の4パターンのいずれかになります。(今回は基準マーカーのみ)










上記の4枚の画像と、透視変換した画像のANDをとり、明るい画素の数を数えて
一番多い角度をマーカーの向いている方向とします。

下記画像が実際の画面です。上辺と左辺に若干白い部分がありますが、これは透視変換したときに補間できなかったところのようです。













上辺と左辺のノイズ部分に対応するために、
基準マーカーを内側の輪郭から5mm離してあります。
これにより、ノイズに影響されずに方向の検出ができるようになります。
向きがわかったで、向きに対応するようにマーカーの内側の座標の並びを変えて、
今まで使っていたマーカー上に立方体を表示する部分を流用して
X軸、Y軸、Z軸を表示するようにしてみました。

今回の結果は下記の通りとなります。





今回のコードは下記の通りです。
だんだん長くなるなぁ。。。

//////////////////////////////////////
// 
// OpenCVで遊ぼう!
// http://playwithopencv.blogspot.com/
//
//markerdetector.cpp
//マーカーを探します。
//
//処理の概要
//マーカーを探し、マーカー上にX,Y,Z軸を書きます。
//

#include "cv.h"
#include "highgui.h"
void BubSort(float arr[ ], int n);


int main(int argc, char * argv[])
{

 ///////////////////////////////////////////
 //画像に表示させる立方体の準備。
 
 #define MARKER_SIZE (90)       /* マーカーの外側の1辺のサイズ[mm] */
 CvMat *intrinsic = (CvMat*)cvLoad("intrinsic.xml");
 CvMat *distortion = (CvMat*)cvLoad("distortion.xml");
 int i,j,k;

 CvMat object_points;
 CvMat image_points;
 CvMat point_counts;

 CvMat *rotation = cvCreateMat (1, 3, CV_32FC1);
 CvMat *translation = cvCreateMat (1 , 3, CV_32FC1);
 //立方体生成用
 CvMat *srcPoints3D = cvCreateMat (4, 1, CV_32FC3);//元の3次元座標
 CvMat *dstPoints2D = cvCreateMat (4, 1, CV_32FC3);//画面に投影したときの2次元座標
 CvPoint2D32f *corners =(CvPoint2D32f *) cvAlloc (sizeof (CvPoint2D32f) * 4);//四角形
 
 CvPoint3D32f baseMarkerPoints[4];
 //四角が物理空間上ではどの座標になるかを指定する。
 //コーナー      実際の座標(mm)
 //   X   Y     X    Y  
 //   0   0   = 0    0
 //   0   1   = 0    20
 //   1   0   = 20   0
 baseMarkerPoints[0].x =(float) 0 * MARKER_SIZE;
 baseMarkerPoints[0].y =(float) 0 * MARKER_SIZE;
 baseMarkerPoints[0].z = 0.0;
  
 baseMarkerPoints[1].x =(float) 0 * MARKER_SIZE;
 baseMarkerPoints[1].y =(float) 1 * MARKER_SIZE;
 baseMarkerPoints[1].z = 0.0;

 baseMarkerPoints[2].x =(float) 1 * MARKER_SIZE;
 baseMarkerPoints[2].y =(float) 1 * MARKER_SIZE;
 baseMarkerPoints[2].z = 0.0;
 
 baseMarkerPoints[3].x =(float) 1 * MARKER_SIZE;
 baseMarkerPoints[3].y =(float) 0 * MARKER_SIZE;
 baseMarkerPoints[3].z = 0.0;
 
  //軸の基本座標を求める。
 for ( i=0;i<4;i++)
 { 
  switch (i)
  {
   case 0: srcPoints3D->data.fl[0]     =0;
        srcPoints3D->data.fl[1]     =0;
     srcPoints3D->data.fl[2]     =0;
     break;
   case 1: srcPoints3D->data.fl[0+i*3] =(float)MARKER_SIZE;
     srcPoints3D->data.fl[1+i*3] =0;
     srcPoints3D->data.fl[2+i*3] =0;
     break;
   case 2: srcPoints3D->data.fl[0+i*3] =0;
     srcPoints3D->data.fl[1+i*3] =(float)MARKER_SIZE;
     srcPoints3D->data.fl[2+i*3] =0;
     break;
   case 3: srcPoints3D->data.fl[0+i*3] =0;
     srcPoints3D->data.fl[1+i*3] =0;
     srcPoints3D->data.fl[2+i*3] =-(float)MARKER_SIZE;;
     break;
  
  }
 } 

 ///軸の準備 ここまで
 ////////////////////////////////////




 IplImage* image;
 IplImage* gsImage;
 IplImage* gsImageContour;

 CvCapture *capture = cvCaptureFromCAM(1);
 IplImage * capimg;

 capimg=cvQueryFrame(capture);
 
 double process_time;
 image=cvCreateImage(cvGetSize(capimg),IPL_DEPTH_8U,3);
 cvCopy(capimg,image);
 
 gsImage=cvCreateImage(cvGetSize(image),IPL_DEPTH_8U,1);
 gsImageContour=cvCreateImage(cvGetSize(image),IPL_DEPTH_8U,1);
 char presskey;
 
 //フォントの設定
 CvFont dfont;
    float hscale      = 0.5f;
    float vscale      = 0.5f;
    float italicscale = 0.0f;
    int  thickness    = 1;
    char text[255] = "";
    cvInitFont (&dfont, CV_FONT_HERSHEY_SIMPLEX , hscale, vscale, italicscale, thickness, CV_AA);

 CvFont axisfont;
    float axhscale      = 0.8f;
    float axvscale      = 0.8f;
    cvInitFont (&axisfont, CV_FONT_HERSHEY_SIMPLEX , axhscale, axvscale, italicscale, thickness, CV_AA);
 
 
 //輪郭保存用のストレージを確保
 CvMemStorage *storage = cvCreateMemStorage (0);//輪郭用
 CvMemStorage *storagepoly = cvCreateMemStorage (0);//輪郭近似ポリゴン用
 
 CvSeq *firstcontour=NULL;
 CvSeq *polycontour=NULL;

 int contourCount;
 
 IplImage *marker_inside=cvCreateImage(cvSize(70,70),IPL_DEPTH_8U,1);
 IplImage *marker_inside_zoom=cvCreateImage(cvSize(marker_inside->width*2,marker_inside->height*2),IPL_DEPTH_8U,1);
 IplImage *tmp_img=cvCloneImage(marker_inside);
  
 CvMat *map_matrix;
 CvPoint2D32f src_pnt[4], dst_pnt[4], tmp_pnt[4];

 //マーカーの内側の変形先の形
 dst_pnt[0] = cvPoint2D32f (0, 0);
 dst_pnt[1] = cvPoint2D32f (marker_inside->width, 0);
 dst_pnt[2] = cvPoint2D32f (marker_inside->width, marker_inside->height);
    dst_pnt[3] = cvPoint2D32f (0, marker_inside->height);
 map_matrix = cvCreateMat (3, 3, CV_32FC1);
 
 cvNamedWindow ("marker_inside", CV_WINDOW_AUTOSIZE);
 cvNamedWindow ("capture_image", CV_WINDOW_AUTOSIZE);

 //マスク画像の読み込み。
 //検出したマーカーと、この画像のANDを取り、cvCountNonZeroが一番大きかったものをマーカーの向きとする。
 IplImage * mask0  =cvLoadImage("C:\\OpenCVMarker\\mask_0.bmp",0);
 IplImage * mask90 =cvLoadImage("C:\\OpenCVMarker\\mask_90.bmp",0);
 IplImage * mask180=cvLoadImage("C:\\OpenCVMarker\\mask_180.bmp",0);
 IplImage * mask270=cvLoadImage("C:\\OpenCVMarker\\mask_270.bmp",0);
 IplImage * tempmask=cvCloneImage(mask0);//作業用

 while(1)
 {
  cvClearMemStorage(storage);
  cvClearMemStorage(storagepoly);

  process_time = (double)cvGetTickCount();
  capimg=cvQueryFrame(capture);
  cvCopy(capimg,image);
  //グレースケール化
  cvCvtColor(image,gsImage,CV_BGR2GRAY);  
  
  //平滑化
  cvSmooth(gsImage,gsImage,CV_GAUSSIAN,3);
 
  //二値化
  cvThreshold (gsImage, gsImage, 0, 255, CV_THRESH_BINARY | CV_THRESH_OTSU);//マーカーが浮き出る

  //反転
  cvNot(gsImage,gsImage);
  
  //輪郭を探します。Ncには探した輪郭の数が入ります。
  //CV_RETR_LISTに設定すると、見つけた輪郭がすべて同じレベルに入ります。。
  //first=輪郭1⇔輪郭2⇔輪郭3⇔輪郭4
  
  contourCount=0;
  
  //輪郭抽出
  cvCopy(gsImage,gsImageContour);  
  contourCount=cvFindContours (gsImageContour, storage, &firstcontour, sizeof (CvContour), CV_RETR_CCOMP);
  
  //輪郭に近似しているポリゴンを求める(最小直線距離3ピクセルに設定)
  polycontour=cvApproxPoly(firstcontour,sizeof(CvContour),storagepoly,CV_POLY_APPROX_DP,3,1); 
 
  for(CvSeq* c=polycontour;c!=NULL;c=c->h_next)
  {
   //外側の輪郭が大きすぎても小さすぎてもダメ。さらに四角形で無いとダメ ということにする
   if((cvContourPerimeter(c)<2000)&&(cvContourPerimeter(c)>60)&&(c->total==4))
   {
    //四角形の中に四角形があればマーカーとする。
    if(c->v_next!=NULL)
    {
     if(c->v_next->total==4)
     {
      int nearestindex=0;
            
      CvSeq* c_vnext=c->v_next;
   //   cvDrawContours(image,c,CV_RGB(255,255,0),CV_RGB(200,255,255),0);
   //   cvDrawContours(image,c_vnext,CV_RGB(255,0,0),CV_RGB(0,255,255),0);
      
      
      float xlist[4];
      float ylist[4];
      for(int n=0;n<4;n++)
      {
       CvPoint* p=CV_GET_SEQ_ELEM(CvPoint,c->v_next,n);
 
       
       tmp_pnt[n].x=(float)p->x;
       tmp_pnt[n].y=(float)p->y; 
       xlist[n]=(float)p->x;
       ylist[n]=(float)p->y;
      }
      
      //四角の情報だけ渡す。どちらを向いているかはまだわからない
      cvGetPerspectiveTransform (tmp_pnt, dst_pnt, map_matrix);
      
      //マーカーの内側を正方形に変形させる
      cvWarpPerspective (gsImage, marker_inside, map_matrix, CV_INTER_LINEAR + CV_WARP_FILL_OUTLIERS, cvScalarAll (0));
         
     
      //marker_inside(マーカーの内側だけを抽出し、正方形に透視変換したもの)
      //を、マスク画像を指定して一時イメージにコピー。
      //一時イメージに白い点が多数あれば、マスク画像と同じ方向を向いていることになる。
  
      int notzeroCount=0;
      
      int maxCount=0;
      int markerDirection=0;//基本は0deg
      cvResize(marker_inside,marker_inside_zoom);


      cvCopy(marker_inside,tempmask,mask0);//cvCopy時にマスク画像を入れると、マスクの0じゃないところのみコピーされる。
      notzeroCount=cvCountNonZero(tempmask);
      
     
      if(maxCount<notzeroCount)
      {
       maxCount=notzeroCount;
       markerDirection=0;      
       sprintf(text,"0deg", notzeroCount);
      
      }
      cvZero(tempmask);

      cvCopy(marker_inside,tempmask,mask90);
      notzeroCount=cvCountNonZero(tempmask);
      if(maxCount<notzeroCount)
      {
       maxCount=notzeroCount;
       markerDirection=90;
       sprintf(text,"90deg");
      }   
      cvZero(tempmask);

      cvCopy(marker_inside,tempmask,mask180);
      notzeroCount=cvCountNonZero(tempmask);
      if(maxCount<notzeroCount)
      {
       maxCount=notzeroCount;
       markerDirection=180;
       sprintf(text,"180deg");
      }
      cvZero(tempmask);

      cvCopy(marker_inside,tempmask,mask270);      
      notzeroCount=cvCountNonZero(tempmask);
      if(maxCount<notzeroCount)
      {
       maxCount=notzeroCount;
       markerDirection=270;
       sprintf(text,"270deg");
      }

      cvPutText(marker_inside_zoom, text, cvPoint(70, 70), &dfont, cvScalarAll(255));
    
      cvZero(tempmask);

      
      cvShowImage ("marker_inside", marker_inside_zoom);


      //四角の向きを反映させる。
      
      if(markerDirection==0)
      {
       src_pnt[0].x=tmp_pnt[0].x;
       src_pnt[0].y=tmp_pnt[0].y;
       src_pnt[1].x=tmp_pnt[3].x;
       src_pnt[1].y=tmp_pnt[3].y;
       src_pnt[2].x=tmp_pnt[2].x;
       src_pnt[2].y=tmp_pnt[2].y;
       src_pnt[3].x=tmp_pnt[1].x;
       src_pnt[3].y=tmp_pnt[1].y;
        
      }
      if(markerDirection==90)
      {
       src_pnt[0].x=tmp_pnt[1].x;
       src_pnt[0].y=tmp_pnt[1].y;
       src_pnt[1].x=tmp_pnt[0].x;
       src_pnt[1].y=tmp_pnt[0].y;
       src_pnt[2].x=tmp_pnt[3].x;
       src_pnt[2].y=tmp_pnt[3].y;
       src_pnt[3].x=tmp_pnt[2].x;
       src_pnt[3].y=tmp_pnt[2].y;
       
       
      }

      if(markerDirection==180)
      {


       src_pnt[0].x=tmp_pnt[2].x;
       src_pnt[0].y=tmp_pnt[2].y;
       src_pnt[1].x=tmp_pnt[1].x;
       src_pnt[1].y=tmp_pnt[1].y;
       src_pnt[2].x=tmp_pnt[0].x;
       src_pnt[2].y=tmp_pnt[0].y;
       src_pnt[3].x=tmp_pnt[3].x;
       src_pnt[3].y=tmp_pnt[3].y;
      }
   

      if(markerDirection==270)
      {
       src_pnt[0].x=tmp_pnt[3].x;
       src_pnt[0].y=tmp_pnt[3].y;
       src_pnt[1].x=tmp_pnt[2].x;
       src_pnt[1].y=tmp_pnt[2].y;
       src_pnt[2].x=tmp_pnt[1].x;
       src_pnt[2].y=tmp_pnt[1].y;
       src_pnt[3].x=tmp_pnt[0].x;
       src_pnt[3].y=tmp_pnt[0].y;
      }
     //  cvPutText(image,"0", cvPoint((int)src_pnt[0].x,(int)src_pnt[0].y), &dfont, CV_RGB(255, 0, 255));
     //  cvPutText(image,"1", cvPoint((int)src_pnt[1].x,(int)src_pnt[1].y), &dfont, CV_RGB(255, 0, 255));
      
       
      //マーカーのイメージ上での座標を設定。
      cvInitMatHeader (&image_points, 4, 1, CV_32FC2, src_pnt);

      //マーカーの基本となる座標を設定
      cvInitMatHeader (&object_points, 4, 3, CV_32FC1, baseMarkerPoints);

      //カメラの内部定数(intrinsticとdistortion)から、rotationとtranslationを求める 
      cvFindExtrinsicCameraParams2(&object_points,&image_points,intrinsic,distortion,rotation,translation);

      //求めたものを使用して、現実空間上の座標が画面上だとどの位置に来るかを計算
      cvProjectPoints2(srcPoints3D,rotation,translation,intrinsic,distortion,dstPoints2D);     
    
      //軸を描画
      CvPoint startpoint;
      CvPoint endpoint;

      startpoint=cvPoint((int)dstPoints2D->data.fl[0], (int)dstPoints2D->data.fl[1]);
      for(j=1;j<4;j++)
      {
       endpoint=  cvPoint((int)dstPoints2D->data.fl[(j)*3],(int)dstPoints2D->data.fl[1+(j)*3]);
      
       if(j==1)
       {
        cvLine(image,startpoint,endpoint,CV_RGB(255,0,0),2,8,0);
        cvPutText(image, "X", endpoint, &axisfont,CV_RGB(255,0,0));
       }
       if(j==2)
       {
        cvLine(image,startpoint,endpoint,CV_RGB(0,255,0),2,8,0);
        cvPutText(image, "Y", endpoint, &axisfont,CV_RGB(0,255,0));
       }
       if(j==3)
       {
        cvLine(image,startpoint,endpoint,CV_RGB(0,0,255),2,8,0);
        cvPutText(image, "Z", endpoint, &axisfont,CV_RGB(0,0,255));
       }
      }
     }
    }
   
   }
  }

  process_time = (double)cvGetTickCount()-process_time;
 
  sprintf(text,"process_time %gms", process_time/(cvGetTickFrequency()*1000.)); 
  cvPutText(image, "http://playwithopencv.blogspot.com", cvPoint(10, 20), &dfont, CV_RGB(255, 255, 255));
  cvPutText(image, text, cvPoint(10, 40), &dfont, CV_RGB(255, 255, 255));
  cvShowImage("capture_image",image);
  
  
  presskey=cvWaitKey (100);
  if(presskey==27)
  {
   break;
  }
 }

 cvReleaseImage(&image);
 cvReleaseImage(&gsImage); 
 cvReleaseImage(&gsImageContour);
 cvReleaseImage(&marker_inside); 
 cvReleaseImage(&tmp_img);
 cvReleaseImage(&mask0);
 cvReleaseImage(&mask90);
 cvReleaseImage(&mask180);
 cvReleaseImage(&mask270);
 cvReleaseImage(&tempmask);
 cvReleaseMat (&map_matrix);
 cvReleaseMemStorage(&storagepoly);
 cvReleaseMemStorage(&storage);
 cvReleaseImage(&image);
 cvReleaseImage(&gsImage);
 cvDestroyWindow("marker_inside");
 cvDestroyWindow("capture_image");
return 0;
}

void BubSort(float arr[ ], int n)
{
    int i, j;
 float temp;

    for (i = 0; i < n - 1; i++)
 {
        for (j = n - 1; j > i; j--)
  {
            if (arr[j - 1] > arr[j])
   {  /* 前の要素の方が大きかったら */
                temp = arr[j];        /* 交換する */
                arr[j] = arr[j - 1];
                arr[j - 1]= temp;
            }
        } 
     }
}





2010/06/05

OpenCVでマーカー検出

前回の続き。
前回は画像の輪郭を抽出するところまでやりました。


今回はもう少し工夫して、マーカーを検出するところまで作ってみました。
今回の成果は下記のような感じになります。



現時点ではマーカーの識別までしか出来ていませんが、今後はマーカーがどちらを向いているかがわかるようにする予定です。

処理の流れは下記の通りとなります。
0.カメラからイメージを取得し、グレースケール化した後に二値化します。
1.二値化したものからcvFindContoursを使って輪郭を抽出します。
2.輪郭をcvApproxPolyを使ってポリゴンに近似させます。
3.ポリゴンに近似した輪郭のうち、外側の輪郭と内側の輪郭の両方とも四角形のものを中秋します。
4.抽出した四角形と、マーカーの大きさ情報をcvFindExtrinsicCameraParams2にかけ、回転ベクトルと移動ベクトルを算出します。
5.求めた回転ベクトルと移動ベクトルを使い、cvProjectPoints2を使って立方体の各ポイントを変換します。
6.変換したポイント間に線を引きます。

ソースコードは動作が安定してから載せます。

ではまた。

2010/06/03

画像の輪郭抽出

おひさしぶりです。

Lost Planet2が発売されてたのでそっちをずっとやってました。
ひとまずHardもクリアしたのでOpenCVに戻ります。


今日はオライリーのOpenCV本を読みつつ、輪郭抽出で遊んでみました。

輪郭抽出にかける画像は下記のようなものを用意しました。
マーカーはARToolKitで使われている形式のモノです。
それを部屋の画像(画像検索で適当に探しました)に貼り付けてます。



これに輪郭抽出をかけると、10msぐらいで輪郭の抽出が出来ました。
輪郭だけを描画したものが下の画像になります。


この画像のうち、青が外側の輪郭で、緑が内側の輪郭となります。

以前やっていたチェスボード検出で使ったFindChessboardCornersの場合は、場所と傾きを正確に計算してくれるのですが、1フレーム処理するのに100ms~200msかかるので動画をリアルタイムで処理するには向いてなさそうです。(速いマシンを使えば別でしょうけども。。)


輪郭検出の場合は、10msぐらいで処理できるようなので、この輪郭抽出を応用できないかな~と考えてます。

今日のコードは以下のような感じです。


//////////////////////////////////////
// 
// OpenCVで遊ぼう!
// http://playwithopencv.blogspot.com/
//
//rinkaku.cpp
//画像から輪郭を抽出します。
//
//処理の概要
//1.画像を読み込み、グレイスケールに変換して輪郭を抽出します。
//2.その後、輪郭を1つずつ表示していきます。
//
#include "stdafx.h"
#include "cv.h"
#include "highgui.h"

using namespace System;

int main(array<System::String ^> ^args)
{
    IplImage * image;
 IplImage* gsImage;
 IplImage* colorImage;

 double process_time;
 image=cvLoadImage("c:\\rinkaku.png",1);

 gsImage=cvCreateImage(cvGetSize(image),IPL_DEPTH_8U,1);
 colorImage=cvCreateImage(cvGetSize(image),IPL_DEPTH_8U,3);

 cvNamedWindow("window",1);

 //輪郭保存用のストレージを確保
 CvMemStorage *storage = cvCreateMemStorage (0);
 CvSeq *firstcontour=NULL; 
 
 cvCvtColor(image,gsImage,CV_BGR2GRAY);
 process_time = (double)cvGetTickCount();
 
 //輪郭を探します。Ncには探した輪郭の数が入ります。
 //CV_RETR_LISTに設定すると、見つけた輪郭がすべて同じレベルに入ります。。
 //first=輪郭1⇔輪郭2⇔輪郭3⇔輪郭4
 //これだと内側と外側の両方の輪郭が同じレベルに入るので、外側だけほしい場合はCV_LETR_CCOMPに設定します。
 
 int Nc=cvFindContours (gsImage, storage, &firstcontour, sizeof (CvContour), CV_RETR_LIST);
 process_time = (double)cvGetTickCount()-process_time;
 printf("process_time %gms\n", process_time/(cvGetTickFrequency()*1000.)); 
 printf("MODE:CV_RETR_LIST ",Nc);
 printf("Total Contour %d\n",Nc);
 
 int n=0;
 
 //輪郭を1つずつ取り出して描画します。
 for(CvSeq * seq=firstcontour;seq!=NULL;seq=seq->h_next)
 {
  colorImage=cvCloneImage(image);
  cvDrawContours(colorImage,seq,cvScalar(255,0,0),cvScalar(0,255,0),0,2,8);
 
  printf("Contour no:%d\n",n);
  cvShowImage("window",colorImage);

  for(int i=0;i<seq->total;i++)
  {
   CvPoint* p=CV_GET_SEQ_ELEM(CvPoint,seq,i);
   printf(" (%d %d)\n",p->x,p->y);

  }
  cvWaitKey(0);
  n++;
 }
 cvReleaseImage(&image);
 cvReleaseImage(&gsImage);
 cvDestroyWindow("window");
 return 0;
}

ではまた。