VTK Application - eiichiromomma/CVMLAB GitHub Wiki

(VTK) 独自アプリ

VTKを使ったアプリケーションの作成。

Makefileのひながた

vtkはライブラリが兎に角多くて一々手打でgccを打つ気にはなれないのでMakefileを書いておく。

Makefile

CC=gcc
CFLAGS = -Wno-deprecated -I /usr/X11R6/include -I /usr/local/include/vtk-5.0
LDLIBS = -L/usr/X11R6/lib -L /usr/local/lib -lvtkRendering -lvtkIO\
         -lvtkGraphics -lvtkImaging -lvtkftgl -lvtkfreetype -lGL -lXt -lSM\
         -lICE -lSM -lICE -lSM -lICE -lX11 -lXext -lX11 -lXext -lX11 -lXext\
         -lvtkFiltering -lvtkCommon -lvtksys -lpthread -lm -lvtkDICOMParser\
         -lvtkpng -lvtktiff -lvtkzlib -lvtkjpeg -lvtkexpat -lvtkMPEG2Encode

ターゲット名:

ビルド対象ファイルは「ターゲット名.cxx」となる。

CMakeを使う場合

CMakeLists.txtの作成

PROJECT (作成するプログラム名)

FIND_PACKAGE(VTK REQUIRED)
IF(NOT VTK_USE_RENDERING)
  MESSAGE(FATAL_ERROR "Example ${PROJECT_NAME} requires VTK_USE_RENDERING.")
ENDIF(NOT VTK_USE_RENDERING)
INCLUDE(${VTK_USE_FILE})

ADD_EXECUTABLE(作成するプログラム名 作成するプログラム名.cxx)

TARGET_LINK_LIBRARIES(作成するプログラム名 必要なライブラリ名)

のような記述になる。 弄る箇所は少ない。

あとはCMakeでVTKをビルドした場所を指定すると.slnや.vcprojファイルが作成される。

実例

OpenCVを使って画像の濃度値を取得して表示するR/色ベクトルのプロットで作ったものと同等の処理。注:数時間で試行錯誤しつつ急造したのでソースはかなり荒い。

VTKのサンプルソースに含まれるfinance.cxxをベースとして、プログラム名はCAnalyseとした。

CMakeLists.txt

PROJECT (CAnalyse)

FIND_PACKAGE(VTK REQUIRED)
IF(NOT VTK_USE_RENDERING)
  MESSAGE(FATAL_ERROR "Example ${PROJECT_NAME} requires VTK_USE_RENDERING.")
ENDIF(NOT VTK_USE_RENDERING)
INCLUDE(${VTK_USE_FILE})

ADD_EXECUTABLE(CAnalyse CAnalyse.cxx)

TARGET_LINK_LIBRARIES(CAnalyse vtkRendering vtkHybrid cv highgui cxcore)

必要なライブラリはvtkRendering.lib, vtkHybrid.libの他OpenCVのcv.lib, highgui.lib cxcore.lib。

ソース解説

ヘッダ〜グローバル

    #include "vtkActor.h"
    #include <vtkActorCollection.h>
    #include "vtkAxes.h"
    #include <vtkCellData.h>
    #include "vtkContourFilter.h"
    #include "vtkDataSet.h"
    #include <vtkDataSetCollection.h>
    #include "vtkFloatArray.h"
    #include "vtkGaussianSplatter.h"
    #include <vtkGlyph3D.h>
    #include "vtkImageData.h"
    #include <vtkLegendBoxActor.h>
    #include <vtkMapper.h>
    #include "vtkPointData.h"
    #include "vtkPoints.h"
    #include "vtkPolyDataMapper.h"
    #include "vtkProperty.h"
    #include "vtkRenderWindow.h"
    #include "vtkRenderWindowInteractor.h"
    #include "vtkRenderer.h"
    #include <vtkSphere.h>
    #include <vtkSphereSource.h>
    #include "vtkTubeFilter.h"
    #include "vtkUnstructuredGrid.h"
    #include "vtkTestUtilities.h"
    #include <stdio.h>
    #include <cv.h>
    #include <highgui.h>
    
    IplImage* tmpImg = 0;
    IplImage* srcImg = 0; 
    CvPoint selectedPosition[50];
    int currentNumber=0;
    int winSize=50;
    int endFlag=0;

とにかくincludeが多くなるのがVTKの特徴。<>で囲まれているのが追加した行になる。

main関数(OpenCVでの処理)

    int main( int argc, char *argv[] )
    {
      double bounds[6];
     
      if(argc<2){
        printf("Usage: main <image-file-name>\n\7");
        exit(0);
      }
      // load an image  
      srcImg=cvLoadImage(argv[1],1);
    
      if(!srcImg){
        printf("Could not load image file: %s\n",argv[1]);
        exit(0);
      }

cvLoadImageで画像を読込んでいる。 画像ファイルの指定はコマンドラインでの第1引数としておく。こうしておくとWindowsでのドラッグ&ドロップになる。

      tmpImg = cvCloneImage(srcImg);
      cvNamedWindow("mainWin", CV_WINDOW_AUTOSIZE); 
      cvMoveWindow("mainWin", 100, 100);
      cvSetMouseCallback("mainWin",(CvMouseCallback)myMouseCB);
      cvShowImage("mainWin", srcImg );

tmpImgをsrcImgのクローンとし、コールバック関数にmyMouseCBを指定して画像を表示。

      for (;;){
        int c;
        c=cvWaitKey(0);
        switch(c){
          case '\x1b':
          case 'q':
            printf("Exiting ...\n");
            goto exit_opencv;
          case '1':
            winSize=10;
            break;
          case '2':
            winSize=32;
            break;
          case '3':
            winSize=50;
            break;
          case '4':
            winSize=64;
            break;
          case '5':
            winSize=100;
            break;
          case '6':
            winSize=128;
            break;
          default:
            break;
        }
      }
    exit_opencv:
      endFlag=1;

キー入力、マウスによる領域選択待ちのループ。qかESCにより選択モードの終了。1〜6でウィンドウの大きさの変更。 モード終了のフラグを立てておく。

      if (currentNumber == 0 || currentNumber > 12){
        exit(0);
      }

安全のため非選択や12個以上の指定の場合は終了。

      //VTK
      vtkDataSet *dataSet;
      vtkDataSetCollection *dataCollection = vtkDataSetCollection::New();
      vtkActorCollection *actorCollection = vtkActorCollection::New();
      vtkSphereSource *balls;
      vtkGlyph3D *glyphPoints;
      vtkPolyDataMapper *ballsMapper;
      vtkActor *ballsActor;

VTKでは描画のソース->Glyph->Mapper->Actor->Rendererと渡すのが流れで、完全分業になっている。 コンストラクタは使用せず、コンストラクタ関数Newを使うのもVTKの約束事になる。 従ってオブジェクトの配列は作成できない。

配列の代わりとして○○Collectionというクラスが存在するので、これにオブジェクトを追加する形となる。 ここでもDataSet, SphereSource, Glyph3D, PolyDataMapper, Actorを複数作成してリストに読ませるため、Newはループの中で行なう。

      for (int i=0; i<currentNumber; i++){
        dataSet=ReadColorDataFromWindow(selectedPosition[i].x,selectedPosition[i].y);
        dataCollection->AddItem(dataSet);
      }

Collectionの例となる部分で、ReadColorDataFromWindow関数で読込んだ3次元の座標データをAddItemによってリストに追加している。

      double axesScale;
      //create legends
      vtkLegendBoxActor *legendBox = vtkLegendBoxActor::New();
      legendBox->SetNumberOfEntries(currentNumber);

凡例の準備。

      // construct pipeline for original population
      double rgb[12][3]={
        {1.0,0.0,0.0},
        {0.0,1.0,0.0},
        {0.0,0.0,1.0},
        {1.0,1.0,0.0},
        {1.0,0.0,1.0},
        {0.0,1.0,1.0},
        {0.0,0.0,0.0},
        {0.5,0.0,0.0},
        {0.0,0.5,0.0},
        {0.0,0.0,0.5},
        {0.5,0.5,0.0},
        {0.5,0.0,0.5}};

カラーテーブル。適当な関数が見付からなかったので手作業で作成。

      char legendLabel[50];
      for (int i=0; i<currentNumber; i++){
        balls = vtkSphereSource::New();
        glyphPoints = vtkGlyph3D::New();
        ballsMapper = vtkPolyDataMapper::New();
        ballsActor = vtkActor::New();
        balls->SetRadius(0.02);
        balls->SetPhiResolution(5);
        balls->SetThetaResolution(5);
        glyphPoints->SetInput(dataCollection->GetDataSet(i));
        glyphPoints->SetSource(balls->GetOutput());
        ballsMapper->SetInputConnection(glyphPoints->GetOutputPort());
        ballsMapper->ScalarVisibilityOff();
        ballsActor->SetMapper(ballsMapper);
        ballsActor->GetProperty()->SetOpacity(0.3);
        ballsActor->GetProperty()->SetColor(rgb[i][0],rgb[i][1],rgb[i][2]);
        axesScale = balls->GetOutput()->GetLength()/2;
        actorCollection->AddItem(ballsActor);
        sprintf(legendLabel,"Window%d",i);
        legendBox->SetEntry(i,balls->GetOutput(),legendLabel,rgb[i]);
        balls->Delete();
        glyphPoints->Delete();
        ballsMapper->Delete();
        ballsActor->Delete();
      } 

長いが大した事はしていない。Newで作成->Actorまでの処理->ActorCollectionへの追加->Deleteで消去の繰り返しをする。 ついでに凡例を作るためlegendBox->SetEntryを行なっている。

      // create axes
      vtkAxes *axes = vtkAxes::New();
      axes->SetOrigin(0,0,0);
      axes->SetScaleFactor(axesScale);
      vtkTubeFilter *axesTubes = vtkTubeFilter::New();
      axesTubes->SetInputConnection(axes->GetOutputPort());
      axesTubes->SetRadius(axes->GetScaleFactor()/300.0);
      axesTubes->SetNumberOfSides(6);
      vtkPolyDataMapper *axesMapper = vtkPolyDataMapper::New();
      axesMapper->SetInputConnection(axesTubes->GetOutputPort());
      vtkActor *axesActor = vtkActor::New();
      axesActor->SetMapper(axesMapper);

軸の作成。 Axesで作成したデータをTubeFilterに渡して軸用の円筒を作成し、Actorに渡している。

      //change axes color
      axes->Update();
      axes->GetOutput()->GetPointData()->GetScalars()->SetTuple1(2,0.5);
      axes->GetOutput()->GetPointData()->GetScalars()->SetTuple1(3,0.5);
      axes->GetOutput()->GetPointData()->GetScalars()->SetTuple1(4,1.0);
      axes->GetOutput()->GetPointData()->GetScalars()->SetTuple1(5,1.0);

軸の色変更。

      // graphics stuff
      vtkRenderer *renderer = vtkRenderer::New();
      vtkRenderWindow *renWin = vtkRenderWindow::New();
      renWin->AddRenderer(renderer);
      vtkRenderWindowInteractor *iren = vtkRenderWindowInteractor::New();
      iren->SetRenderWindow(renWin);
      renderer->AddActor(axesActor);
      renderer->AddActor(legendBox);

VTK用のウィンドウ(renderer)を作成。Actorを渡している。

      actorCollection->InitTraversal();
      for (int i=0; i<actorCollection->GetNumberOfItems(); i++){
        renderer->AddActor(actorCollection->GetNextActor());
      }

ActorCollectionを渡すのは1つずつループ内で渡していく。 InitTraversalを実行して始めてCollectionの内部カウントが初期化され、GetNextActorで次々と呼び出せる。

      renderer->SetBackground(0.3,0.3,0.8);
      renWin->SetSize(500,500);
    
      // interact with data
      iren->Initialize();
    
      renWin->Render();
      iren->Start();

背景の指定、ウィンドウサイズの指定。 VTKの処理の実行。

      // Clean up
      renderer->Delete();
      renWin->Delete();
      iren->Delete();
      axes->Delete();
      axesTubes->Delete();
      axesMapper->Delete();
      axesActor->Delete();
      return 0;
    }

片付け。かなり適当。

ReadColorDataFromWindow関数

    vtkDataSet *ReadColorDataFromWindow(int mousex, int mousey)
    {
      int x,y;
      float xyz[3];
      vtkUnstructuredGrid *dataSet = vtkUnstructuredGrid::New();
      float *xV = new float[winSize*winSize];
      float *yV = new float[winSize*winSize];
      float *zV = new float[winSize*winSize];
      vtkPoints *newPts = vtkPoints::New();
      for (y=mousey; y< mousey+winSize; y++){
        for (x=mousex; x< mousex+winSize; x++){
          xyz[0] = (((uchar *)(srcImg->imageData + y*srcImg->widthStep))[x*srcImg->nChannels+2])/255.0;
          xyz[1] = (((uchar *)(srcImg->imageData + y*srcImg->widthStep))[x*srcImg->nChannels+1])/255.0;
          xyz[2] = (((uchar *)(srcImg->imageData + y*srcImg->widthStep))[x*srcImg->nChannels+0])/255.0;
          newPts->InsertNextPoint(xyz);
        }
      }
      dataSet->SetPoints(newPts);
      newPts->Delete();
      return dataSet;
    }

殆どOpenCVの処理。vtkDataSetはvtkUnstructuredGridを使い、x,y,zデータにRed,Green,Blueを割り当てる。 PointsはInsertNextPointで追加していくのが楽。

myMouseCB関数

    void myMouseCB(int event, int x, int y, int flags)
    {
      int i;
      CvFont font;
      cvInitFont(&font,CV_FONT_VECTOR0,0.5,0.5,0.0);
      char imgLabel[10];
      switch(event){
      case CV_EVENT_LBUTTONDOWN:
        selectedPosition[currentNumber].x=x;
        selectedPosition[currentNumber].y=y;
        currentNumber++;
        break;
      case CV_EVENT_MOUSEMOVE:
        cvCopyImage(srcImg,tmpImg);
        for(i=0; i<currentNumber; i++){
          sprintf(imgLabel,"win %d",i);
          cvRectangle(tmpImg,cvPoint(selectedPosition[i].x,selectedPosition[i].y),
            cvPoint(selectedPosition[i].x+winSize-1,selectedPosition[i].y+winSize-1),
            cvScalar(1.0,1.0,1.0,1.0));
            cvPutText(tmpImg,imgLabel,cvPoint(selectedPosition[i].x,selectedPosition[i].y),&font,cvScalar(1.0,1.0,1.0,1.0));
        }
        if (!endFlag){
          cvRectangle(tmpImg,cvPoint(x,y),cvPoint(x+winSize-1,y+winSize-1),cvScalar(1.0,1.0,1.0,1.0));
        }
    
        cvShowImage("mainWin", tmpImg );
        break;
      default:
        ;
      }
    }

OpenCVでのマウスのコールバック関数。CV_EVENT_LBUTTONDOWNで左クリックの処理。座標を取得している。

CV_EVENT_MOUSEMOVEはマウスが動いた時の処理でかなり力技。HighguiライブラリにはXORの四角形を書くAPIが無いので、原画のコピー作成->既に取得した座標にウィンドウを表示->現在のカーソル位置にウィンドウを表示という無謀な事をしている。 VTKと一緒に配布されているITKを使った方が良かったかも。

注意点

finance.cxxで使用しているvtkGaussianSplatterはActorCollectionには使えないようだ。

スクリーンショット

こんな感じで表示できる。

⚠️ **GitHub.com Fallback** ⚠️