VTK Application - eiichiromomma/CVMLAB GitHub Wiki
(VTK) 独自アプリ
VTKを使ったアプリケーションの作成。
vtkはライブラリが兎に角多くて一々手打でgccを打つ気にはなれないので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」となる。
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とした。
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の特徴。<>で囲まれているのが追加した行になる。
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;
}
片付け。かなり適当。
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で追加していくのが楽。
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には使えないようだ。
こんな感じで表示できる。