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;
            }
        } 
     }
}





0 件のコメント:

コメントを投稿