由於OpenCV不能使用,只能使用VTK庫的圖像處理庫,暫時還沒有找到其他可以全面替代的庫;
CSDN東靈工作室:http://blog.csdn.net/www_doling_net/article/details/8763686
復制其第一篇:
注:以后將陸續分享一些對經典圖像、圖形算法的介紹,
新浪微博: @東靈工作室
郵箱:www_doling_net@163.com
ITK、VTK研究群:209215671
系列一 《VTK基礎及應用開發教程》
基礎入門篇:
1. 從零開始學習VTK
2. VTK編譯安裝
3. VTK基礎概念
1) VTK基礎概念(1)
2) VTK基礎概念(2)
4. VTK可視化管線
1) VTK可視化管線(1)
2) VTK可視化管線(2)
5. VTK在圖像處理中的應用
6. VTK基本數據結構
1) VTK基本數據結構(1)
2) VTK基本數據結構(2)
3) VTK基本數據結構(3)
4) VTK基本數據結構(4)
5) VTK基本數據結構(5)
高階應用篇:
9. 基於VTK的MFC應用程序開發
1. CMakeLists.txt文件
我們已經知道了VTK工程的管理是用CMake的,而Qt自身有qmake工具,如果對於一些小工程而言,單純的Qt程序用qmake來構建工程,確實很方便,但如果隨着工程復雜度的增加以及工程依賴其他的函數庫時,使用CMake來管理工程或許是一個明智的選擇。而且隨着你對CMake語法的了解,你會發現用CMake來管理工程是一件非常棒的事情。
我們先看看對於單純的Qt工程,怎么來寫CMakeLists.txt腳本文件。
1.1 用CMake來管理Qt工程
官方對於這個話題給出的解釋在這里。我們引用一下這篇博文的圖,然后給出每句CMakeLists.txt腳本的注釋,結合這個圖以及腳本的注釋,相信你應該能明白了。
- #----------------------------------------------
- # 下面這兩行,沒什么好解釋的
- cmake_minimum_required( VERSION 2.8 )
- project( YourProjectName )
- #----------------------------------------------
- # 下面這兩行,也沒什么好解釋的
- find_package( Qt4 REQUIRED )
- include( ${QT_USE_FILE} )
- #----------------------------------------------
- # 程序所有源文件。<TODO:在此處添加源文件>
- # 定義變量Project_SRCS,其值為所列的文件列表
- SET( Project_SRCS
- main.cpp
- )
- #----------------------------------------------
- # 程序所有UI文件。<TODO:在此處添加UI文件>
- # 定義變量Project_UIS,其值為所列的文件列表
- SET( Project_UIS
- YourQtWindows.ui
- )
- #----------------------------------------------
- # 所有包含Q_OBJECT的頭文件。<TODO:在此處添加頭文件>
- # 定義變量Project_MOC_HDRS,其值為所列的文件列表
- SET( Project_MOC_HDRS
- YourQtProjectFiles.h
- )
- #-----------------------------------------------
- # 通過Qt的uic.exe生成UI文件對應的ui_XXXX.h文件
- # 將生成的ui_XXXX.h文件放在變量Project_UIS_H里,
- # QT4_WRAP_UI就是干這個事情。
- QT4_WRAP_UI( Project_UIS_H ${Project_UIS} )
- #-----------------------------------------------
- # 通過Qt的moc.exe生成包含Q_OBJECT的頭文件對應的
- # moc_XXXX.cxx文件,將生成的moc_XXXX.cxx文件放在
- # 變量Project_MOC_SRCS里。QT4_WRAP_CPP就是干這個事情。
- QT4_WRAP_CPP( Project_MOC_SRCS ${Project_MOC_HDRS} )
- #-----------------------------------------------
- # Qt的MOC和UIC程序生成的moc_XXXX.cxx和ui_XXXX.h
- # 等文件是存放在CMake的“Where to build the binaries"
- # 里指定的目錄里,所以必須都這些路徑包含進來。
- INCLUDE_DIRECTORIES( ${Project_SOURCE_DIR}
- ${CMAKE_CURRENT_BINARY_DIR}
- )
- #-----------------------------------------------
- # Qt程序如果有資源文件(*.qrc),要包含資源文件,
- # 然后用Qt的rcc.exe生成相應的qrc_XXXX.cpp文件。
- # QT4_ADD_RESOURCES就是干這個事情。
- SET( Project_RCCS YourProject.qrc)
- QT4_ADD_RESOURCES( Project_RCC_SRCS ${Project_RCCS})
- #-----------------------------------------------
- # 根據程序的cpp文件、頭文件以及中間生成的ui_XXXX.h、
- # moc_XXXX.cxx、qrc_XXXX.cxx等生成可執行文件,並鏈接
- # Qt的動態庫(Qt的動態庫都定義在QT_LIBRARIES變量里了)
- ADD_EXECUTABLE( YourProjectName
- ${Project_SRCS}
- ${Project_UIS_H}
- ${Project_MOC_SRCS}
- ${Project_RCC_SRCS}
- )
- TARGET_LINK_LIBRARIES ( YourProjectName ${QT_LIBRARIES} )
1.2 用CMake來管理Qt與VTK工程
我們在上面的基礎上添加VTK相關的CMake腳本文件,如下:
- #----------------------------------------------------------------------------------
- cmake_minimum_required( VERSION 2.8 )
- project( CombineQtAndVTK )
- #----------------------------------------------------------------------------------
- find_package( VTK REQUIRED )
- find_package( Qt4 REQUIRED )
- include( ${VTK_USE_FILE} )
- include( ${QT_USE_FILE} )
- #----------------------------------------------------------------------------------
- SET( PROJECT_SRCS
- main.cpp
- ProjectMainWindow.cpp
- )
- SET( PROJECT_UIS
- ProjectMainWindow.ui
- )
- SET( PROJECT_MOC_HDRS
- ProjectMainWindow.h
- )
- #----------------------------------------------------------------------------------
- QT4_WRAP_UI( PROJECT_UIS_H
- ${PROJECT_UIS}
- )
- QT4_WRAP_CPP( PROJECT_MOC_SRCS
- ${PROJECT_MOC_HDRS}
- )
- #----------------------------------------------------------------------------------
- INCLUDE_DIRECTORIES( ${PROJECT_SOURCE_DIR}
- ${CMAKE_CURRENT_BINARY_DIR}
- ${VTK_DIR}
- )
- ADD_EXECUTABLE( CombineQtAndVTK
- ${PROJECT_SRCS}
- ${PROJECT_UIS_H}
- ${PROJECT_MOC_SRCS}
- )
- TARGET_LINK_LIBRARIES ( CombineQtAndVTK
- ${VTK_LIBRARIES}
- QVTK
- )
以上的腳本除了紅色字體標注的跟1.1注釋的不太像之外,其他的都一樣,不再解釋。
1.3 CMake腳本里增加工程環境變量的加載
很多非計算機專業的用戶在使用VTK進行編程時,經常會碰到類似下圖所示的一些錯誤。
碰到這樣的錯誤以后,可能很多用戶就不知道怎么處理了,其實上面的提示信息已經寫得非常清楚了,就是缺少“vtkCommon.dll”文件。但是又有人會說:我的電腦里明明有這個文件存在啊,為什么會找不到呢?
一般的解決方法可能是:
方法一:將缺少的dll文件全部拷貝的工程的Debug或者Release目錄下(拷貝的時候要注意你編譯的VTK是Debug版本的還是Release版本的,如果拷錯的話,又會出現其他不可預知的錯誤了)。但是這個方法是你每建一個工程,運行工程之前得把缺少的動態庫文件又要拷貝過去,如果你不嫌麻煩的話,可以采用。
方法二:將缺少的dll文件全部拷貝到Windows系統的目錄下,即C:\Windows\system32或者C:\Windows\system目錄下,這個方法是你拷貝一次,以后再基於你拷貝的VTK動態庫的工程運行的時候問題都解決了。但它同樣有一個問題,假如你電腦里的VTK升級成別的版本,重新編譯了一份動態庫,或者是同時在你電腦里編譯了好幾個版本的VTK,這個時候就有點凌亂了。
為什么這兩種方法都可以解決問題?原來動態編譯的程序在啟動的時候,會搜索程序所在的目錄以及系統環境變量PATH所列的目錄,如果這些目錄有該程序需要的動態庫時,就加載它們,如果沒有,就提示無法加載相應動態庫的錯誤。
可以在工程的CMakeLists.txt文件里添加一些腳本,把系統的PATH環境變量作一些更改,在工程啟動之前加載這些環境變量。也就是(在工程的CMakeLists.txt最后添加):
- #-----------------------------------------------------------------------------------
- # Construct a list of paths containing runtime directories for project applications on Windows
- set(PROJECT_RUNTIME_PATH "${VTK_LIBRARY_DIRS}/@VS_BUILD_TYPE@;
- ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/@VS_BUILD_TYPE@"
- )
- if(QT4_FOUND)
- set(PROJECT_RUNTIME_PATH "${PROJECT_RUNTIME_PATH};${QT_LIBRARY_DIR}/../bin")
- endif()
- include(CreateWindowsBatchScript.cmake)
- # If we are under Windows, create two batch files which correctly
- # set up the environment for the application and for Visual Studio
- if(WIN32)
- set(VS_SOLUTION_FILE "${PROJECT_BINARY_DIR}/${PROJECT_NAME}.sln")
- foreach(VS_BUILD_TYPE debug release)
- CreateWindowsBatchScript("${CMAKE_SOURCE_DIR}/StartVS.bat.in"
- ${PROJECT_BINARY_DIR}/StartVS_${VS_BUILD_TYPE}.bat
- ${VS_BUILD_TYPE})
- endforeach()
- endif(WIN32)
以上的腳本也不是特別復雜,但提到了兩個文件:CreateWindowsBatchScript.cmake以及StartVS.bat.in。這兩個文件的內容分別是:
CreateWindowsBatchScript.cmake:
- function(CreateWindowsBatchScript in out build_type)
- if(VTK_DIR)
- set(VTK_BIN_DIR "${VTK_DIR}/bin/${build_type}")
- else()
- set(VTK_BIN_DIR)
- endif()
- set(VS_BUILD_TYPE ${build_type})
- configure_file(${in} ${out} @ONLY)
- # substitute again
- configure_file(${out} ${out} @ONLY)
- endfunction()
- @set CL=/D_CRT_SECURE_NO_DEPRECATE /D_CRT_NONSTDC_NO_DEPRECATE
- @set LINK=/LARGEADDRESSAWARE
- PATH=@PROJECT_RUNTIME_PATH@;%PATH%
- "@VS_SOLUTION_FILE@"
將工程通過CMake的configure->generate以后,即可生成StartVS_debug.bat和StartVS_release.bat兩個腳本文件。如果你要編譯、運行Debug版本的工程,即雙擊StartVS_debug.bat文件打開對應的工程,同理,Release版本的也一樣。一旦按這種方式打開相應的工程,就不用再擔心類似“無法加載***.dll文件”的錯誤了。如果你的程序還增加了ITK等函數庫,也可以照着上面的腳本作相應的修改。
注意:使用時將CreateWindowsBatchScript.cmake和StartVS.bat.in兩個文件與工程的CMakeLists.txt放在同一級目錄里。即類似下圖的目錄結構:
2. 用QVTKWidget整合Qt&VTK
Qt與VTK的整合可以使用VTK提供的類QVTKWidget,看這個類名就知道這個類其實就是一個Qt里的Widget (QVTKWidget派生自QWidget),所以可以把它當作普通的Qt里的Widget來使用,甚至可以在Qt Designer里像Qt的其他標准控件一樣拖來拖去。
2.1 在Qt Designer里集成
要實現QVTKWidget在Qt Designer里像Qt的其他標准控件一樣拖來拖去,需要把編譯生成的QVTKWidgetPlugin.dll/QVTKWidgetPlugin.lib(Release版本)復制到Qt的安裝目錄里的plugins\designer目錄下。完了以后,你會在Qt Designer里面看到如下的控件:
2.2 讀入一幅圖像,並在Qt界面上顯示
接下來,我們來完成一個小功能,就是讀入一幅JPG圖像,然后在Qt界面上,用VTK來顯示。功能非常簡單,程序也非常簡單。上代碼:
ProjectMainWindow.h:
- #ifndef Project_MainWindow_H
- #define Project_MainWindow_H
- #include <QMainWindow>
- #include "ui_ProjectMainWindow.h"
- #include <vtkSmartPointer.h>
- class vtkImageViewer2;
- class vtkRenderer;
- class ProjectMainWindow : public QMainWindow, public Ui::ProjectMainWindow
- {
- Q_OBJECT
- public:
- ProjectMainWindow();
- ~ProjectMainWindow();
- private slots:
- //響應打開圖像文件的槽函數
- void onOpenSlot();
- private:
- vtkSmartPointer< vtkImageViewer2 > m_pImageViewer;
- vtkSmartPointer< vtkRenderer > m_pRenderder;
- };
- #endif
ProjectMainWindow.cpp:
- #include "ProjectMainWindow.h"
- #include <QFileDialog>
- #include <QDir>
- #include <vtkRenderWindow.h>
- #include <vtkRenderer.h>
- #include <vtkImageViewer2.h>
- #include <QVTKWidget.h>
- #include <vtkJPEGReader.h>
- #include <vtkImageActor.h>
- ProjectMainWindow::ProjectMainWindow()
- {
- setupUi(this);
- m_pImageViewer = vtkSmartPointer< vtkImageViewer2 >::New();
- m_pRenderder = vtkSmartPointer< vtkRenderer >::New();
- // 設置m_QVTKWidget的渲染器
- m_QVTKWidget->GetRenderWindow()->AddRenderer(m_pRenderder);
- //連接打開的信號與相應的槽
- connect( m_OpenAction, SIGNAL( triggered() ), this, SLOT( onOpenSlot() ) );
- }
- ProjectMainWindow::~ProjectMainWindow()
- {
- }
- void ProjectMainWindow::onOpenSlot()
- {
- QString filter;
- filter = "JPEG image file (*.jpg *.jpeg)";
- QDir dir;
- QString fileName = QFileDialog::getOpenFileName( this,
- QString(tr("打開圖像")), dir.absolutePath() , filter );
- if ( fileName.isEmpty() == true ) return;
- // 支持帶中文路徑的讀取
- QByteArray ba = fileName.toLocal8Bit();
- const char *fileName_str = ba.data();
- // 用vtkJPEGReader讀取JPG圖像
- vtkSmartPointer<vtkJPEGReader> reader = vtkSmartPointer<vtkJPEGReader>::New();
- reader->SetFileName(fileName_str);
- // 將reader的輸出作為m_pImageViewer的輸入,並設置m_pImageViewer與渲染器m_pRenderer的關聯
- m_pImageViewer->SetInput(reader->GetOutput());
- m_pImageViewer->UpdateDisplayExtent();
- m_pImageViewer->SetRenderWindow(m_QVTKWidget->GetRenderWindow());
- m_pImageViewer->SetRenderer(m_pRenderder);
- m_pImageViewer->SetupInteractor(m_QVTKWidget->GetRenderWindow()->GetInteractor());
- m_pImageViewer->SetSliceOrientationToXY(); //默認就是這個方向的
- m_pImageViewer->GetImageActor()->InterpolateOff();
- m_pRenderder->ResetCamera();
- m_pRenderder->DrawOn();
- m_QVTKWidget->GetRenderWindow()->Render();
- }
程序運行結果:
2.3 用vtkEventQtSlotConnect實現VTK事件與Qt槽的連接
類vtkEventQtSlotConnect可以實現VTK的事件與Qt的槽函數的連接,VTK的事件主要在vtkCommand.h文件里定義,包括鼠標單擊、鼠標雙擊、鼠標移動等等,如:
vtkCommand::ProgressEvent
vtkCommand::ErrorEvent
vtkCommand::WarningEvent
vtkCommand::PickEvent
vtkCommand::StartPickEvent
vtkCommand::EndPickEvent
vtkCommand::CharEvent
vtkCommand::KeyPressEvent
vtkCommand::KeyReleaseEvent
vtkCommand::LeftButtonPressEvent
vtkCommand::LeftButtonReleaseEvent
vtkCommand::MouseMoveEvent
……
具體的代碼實現:
- private slots:
- //響應鼠標移動的消息,實時輸出鼠標的當前位置
- void updateCoords(vtkObject* obj);
- private:
- vtkEventQtSlotConnect* m_Connections;
源文件:
- //構造函數里:
- m_Connections = vtkEventQtSlotConnect::New();
- m_Connections->Connect(m_QVTKWidget->GetRenderWindow()->GetInteractor(),
- vtkCommand::MouseMoveEvent,
- this,
- SLOT(updateCoords(vtkObject*)));
- //槽函數的實現
- void ProjectMainWindow::updateCoords(vtkObject* obj)
- {
- // 獲取交互器
- vtkRenderWindowInteractor* iren = vtkRenderWindowInteractor::SafeDownCast(obj);
- // 獲取鼠標的當前位置
- int event_pos[2];
- iren->GetEventPosition(event_pos);
- QString str;
- str.sprintf("x=%d : y=%d", event_pos[0], event_pos[1]);
- m_StatusBar->showMessage(str);
- }
示例代碼及該博文文檔下載地址:http://download.csdn.net/detail/www_doling_net/5137375
(3)二維圖像處理:05-VTK在圖像處理中的應用(4)
5.7 區域提取
5.7.1 提取感興趣區域
感興趣區域(Volum of Interest)是指圖像內部的一個子區域。在VTK中vtkExtractVOI類實現由用戶指定的區域范圍提取圖像的子圖像。該Filter的輸入和輸出都是一個vtkImageData,因此其結果可以直接作為圖像保存。
1: vtkSmartPointer<vtkBMPReader> reader =
2: vtkSmartPointer<vtkBMPReader>::New();
3: reader->SetFileName ( "lena.bmp" );
4: reader->Update();
5:
6: int dims[3];
7: reader->GetOutput()->GetDimensions(dims);
8:
9: vtkSmartPointer<vtkExtractVOI> extractVOI =
10: vtkSmartPointer<vtkExtractVOI>::New();
11: extractVOI->SetInputConnection(reader->GetOutputPort());
12: extractVOI->SetVOI(dims[0]/4.,3.*dims[0]/4.,dims[1]/4.,3.*dims[1]/4., 0, 0);
13: extractVOI->Update();
上例代碼實現了提取一副圖像的子區域。首先讀取一個圖像,並獲取圖像的維數。然后定義vtkExtractVOI對象,該對象接收兩個輸入一個是圖像數據,第二個是區域大小。設置區域大小的函數原型:
void SetVOI(int _arg1, int _arg2, int _arg3, int _arg4, int _arg5, int _arg6)
void SetVOI(int _arg[])
其參數是提取的區域各個方向的大小,共6個參數,依次表示x方向最小值,x方向最大值,y方向最小值,y方向最大值,z方向最小值和z方向最大值。上例中由於讀取的是二維圖像,因此z方向的區域為[0,0],而在x方向范圍為[ dims[0]/4 , 3*dims[0]/4 ],y方向范圍為[ dims[1]/4 , 3*dims[1]/4 ],即提取圖像原圖中間1/4圖像。執行結果如下:
圖5.18 提取感興趣區域
5.7.2 三維圖像切片提取
切片是指三維圖像中的一個切面對應的圖像。切面可以是過圖像內部一點且平行於XY、YZ、XZ平面的平面,也可以是任意的過三維圖像內部一點任意方向的平面。通過提取切片可以方便的瀏覽和分析圖像內部組織結構,是醫學圖像瀏覽軟件中的一個重要的功能。在VTK中vtkImageReslice類實現圖像切片提取功能。下面首先看一段切片提取的代碼。
1: vtkSmartPointer<vtkMetaImageReader> reader =
2: vtkSmartPointer<vtkMetaImageReader>::New();
3: reader->SetFileName ( " brain.mhd" );
4: reader->Update();
5:
6: int extent[6];
7: double spacing[3];
8: double origin[3];
9:
10: reader->GetOutput()->GetExtent(extent);
11: reader->GetOutput()->GetSpacing(spacing);
12: reader->GetOutput()->GetOrigin(origin);
13:
14: double center[3];
15: center[0] = origin[0] + spacing[0] * 0.5 * (extent[0] + extent[1]);
16: center[1] = origin[1] + spacing[1] * 0.5 * (extent[2] + extent[3]);
17: center[2] = origin[2] + spacing[2] * 0.5 * (extent[4] + extent[5]);
18:
19: static double axialElements[16] = {
20: 1, 0, 0, 0,
21: 0, 1, 0, 0,
22: 0, 0, 1, 0,
23: 0, 0, 0, 1 };
24:
25: vtkSmartPointer<vtkMatrix4x4> resliceAxes =
26: vtkSmartPointer<vtkMatrix4x4>::New();
27: resliceAxes->DeepCopy(axialElements);
28:
29: resliceAxes->SetElement(0, 3, center[0]);
30: resliceAxes->SetElement(1, 3, center[1]);
31: resliceAxes->SetElement(2, 3, center[2]);
32:
33:
34: vtkSmartPointer<vtkImageReslice> reslice =
35: vtkSmartPointer<vtkImageReslice>::New();
36: reslice->SetInputConnection(reader->GetOutputPort());
37: reslice->SetOutputDimensionality(2);
38: reslice->SetResliceAxes(resliceAxes);
39: reslice->SetInterpolationModeToLinear();
40:
41: vtkSmartPointer<vtkLookupTable> colorTable =
42: vtkSmartPointer<vtkLookupTable>::New();
43: colorTable->SetRange(0, 1000);
44: colorTable->SetValueRange(0.0, 1.0);
45: colorTable->SetSaturationRange(0.0, 0.0);
46: colorTable->SetRampToLinear();
47: colorTable->Build();
48:
49: vtkSmartPointer<vtkImageMapToColors> colorMap =
50: vtkSmartPointer<vtkImageMapToColors>::New();
51: colorMap->SetLookupTable(colorTable);
52: colorMap->SetInputConnection(reslice->GetOutputPort());
53:
54: vtkSmartPointer<vtkImageActor> imgActor =
55: vtkSmartPointer<vtkImageActor>::New();
56: imgActor->SetInput(colorMap->GetOutput());
57:
58: vtkSmartPointer<vtkRenderer> renderer =
59: vtkSmartPointer<vtkRenderer>::New();
60: renderer->AddActor(imgActor);
61: renderer->SetBackground(.4, .5, .6);
62:
63: vtkSmartPointer<vtkRenderWindow> renderWindow =
64: vtkSmartPointer<vtkRenderWindow>::New();
65: renderWindow->SetSize(500, 500);
66: renderWindow->AddRenderer(renderer);
67:
68: vtkSmartPointer<vtkRenderWindowInteractor> renderWindowInteractor =
69: vtkSmartPointer<vtkRenderWindowInteractor>::New();
70: vtkSmartPointer<vtkInteractorStyleImage> imagestyle =
71: vtkSmartPointer<vtkInteractorStyleImage>::New();
72:
73: renderWindowInteractor->SetInteractorStyle(imagestyle);
74: renderWindowInteractor->SetRenderWindow(renderWindow);
75: renderWindowInteractor->Initialize();
76:
77: renderWindowInteractor->Start();
首先通過vtkMetaImageReader讀取一副醫學三維圖像,並獲取得到圖像范圍(extent),原點和像素間隔;由這三個參數可以計算圖像的中心位置center;接下來定義了切面的變換矩陣axialElements,該矩陣的前三列分別表示x、y和z方向向量,第四列為中心點坐標;代碼中的axialElements表示切面變換矩陣與當前坐標系一致,且切面為過中心點center,並平行於XY平面的平面。當前,定義該切面時,也可以是其他平面,甚至是任意平面,但是必須要過圖像內部點。下面給出了一個常用的變換矩陣:
static double coronalElements[16] = {
1, 0, 0, 0,
0, 0, 1, 0,
0,-1, 0, 0,
0, 0, 0, 1 }; 提取平行於XZ平面的切片
static double sagittalElements[16] = {
0, 0,-1, 0,
1, 0, 0, 0,
0,-1, 0, 0,
0, 0, 0, 1 }; 提取平行於YZ平面的切片
static double obliqueElements[16] = {
1, 0, 0, 0,
0, 0.866025, -0.5, 0,
0, 0.5, 0.866025, 0,
0, 0, 0, 1 }; 提取斜切切片
注意使用這些變換矩陣的時候,需要將第四列替換為切片經過圖像的一個點坐標,上例中將圖像的中心添加到axialElements矩陣,並通過函數SetResliceAxes設置變換矩陣,SetOutputDimensionality(2)指定輸出的圖像為一個二維圖像;而函數SetInterpolationModeToLinear()則指定了切面提取中的差值方式為線性差值,另外該類中還提供了其他的差值方式:
SetInterpolationModeToNearestNeighbor():最近鄰方式
SetInterpolationModeToCubic():三次線性差值
設置完畢后,執行Update()即可完成切面計算。運行結果如下圖:
圖5.19 切片提取
5.7.3 擴展
學習三維圖像切面的提取后,我們在上節的程序上做一個擴展,實現一個稍微復雜的程序——通過滑動鼠標來切換三維圖像切片,這也是醫學圖像處理軟件中一個很基本的功能。實現該功能難點是怎樣在VTK中控制鼠標來實時提取圖像切片。在前面的章節中已經介紹觀察者/命令(Observer/Command)模式,我們也采用這種機制來實現。VTK中鼠標消息是在交互類型對象(interactorstyle)中響應,因此通過為交互類型對象(interactorstyle)添加觀察者(observer)來監聽相應的消息,當消息觸發時,由命令模式執行相應的回調函數。閑話少說,放代碼。
1: class vtkImageInteractionCallback : public vtkCommand
2: {
3: public:
4:
5: static vtkImageInteractionCallback *New()
6: {
7: return new vtkImageInteractionCallback;
8: }
9:
10: vtkImageInteractionCallback()
11: {
12: this->Slicing = 0;
13: this->ImageReslice = 0;
14: this->Interactor = 0;
15: }
16:
17: void SetImageReslice(vtkImageReslice *reslice)
18: {
19: this->ImageReslice = reslice;
20: }
21:
22: vtkImageReslice *GetImageReslice()
23: {
24: return this->ImageReslice;
25: }
26:
27: void SetInteractor(vtkRenderWindowInteractor *interactor)
28: {
29: this->Interactor = interactor;
30: }
31:
32: vtkRenderWindowInteractor *GetInteractor()
33: {
34: return this->Interactor;
35: }
36:
37: virtual void Execute(vtkObject *, unsigned long event, void *)
38: {
39: vtkRenderWindowInteractor *interactor = this->GetInteractor();
40:
41: int lastPos[2];
42: interactor->GetLastEventPosition(lastPos);
43: int currPos[2];
44: interactor->GetEventPosition(currPos);
45:
46: if (event == vtkCommand::LeftButtonPressEvent)
47: {
48: this->Slicing = 1;
49: }
50: else if (event == vtkCommand::LeftButtonReleaseEvent)
51: {
52: this->Slicing = 0;
53: }
54: else if (event == vtkCommand::MouseMoveEvent)
55: {
56: if (this->Slicing)
57: {
58: vtkImageReslice *reslice = this->ImageReslice;
59:
60: // Increment slice position by deltaY of mouse
61: int deltaY = lastPos[1] - currPos[1];
62:
63: reslice->Update();
64: double sliceSpacing = reslice->GetOutput()->GetSpacing()[2];
65: vtkMatrix4x4 *matrix = reslice->GetResliceAxes();
66: // move the center point that we are slicing through
67: double point[4];
68: double center[4];
69: point[0] = 0.0;
70: point[1] = 0.0;
71: point[2] = sliceSpacing * deltaY;
72: point[3] = 1.0;
73: matrix->MultiplyPoint(point, center);
74: matrix->SetElement(0, 3, center[0]);
75: matrix->SetElement(1, 3, center[1]);
76: matrix->SetElement(2, 3, center[2]);
77: interactor->Render();
78: }
79: else
80: {
81: vtkInteractorStyle *style = vtkInteractorStyle::SafeDownCast(
82: interactor->GetInteractorStyle());
83: if (style)
84: {
85: style->OnMouseMove();
86: }
87: }
88: }
89: }
90:
91: private:
92: int Slicing;
93: vtkImageReslice *ImageReslice;
94: vtkRenderWindowInteractor *Interactor;
95: };
vtkImageInteractionCallback繼承自vtkCommand類,並覆蓋父類函數Execute()。該類提供了兩個接口:SetImageReslice和SetInteractor。SetImageReslice用以設置vtkImageSlice對象,vtkImageSlice根據設置的變換矩陣提取三維圖像切片。SetInteractor用以設置vtkRenderWindowInteractor,vtkRenderWindowInteractor類對象負責每次提取切片后刷新視圖。
下面我們重點來看一下Execute函數,該函數提供了具體的切片提取功能。在該函數里面,主要監聽了三個消息:
vtkCommand::LeftButtonPressEvent,
vtkCommand::LeftButtonReleaseEvent,
vtkCommand::MouseMoveEvent,
前兩個消息分別是鼠標左鍵的按下和彈起消息。當鼠標左鍵按下時,就設置切片提取標志為1,而當彈起時,將標志置為0。這樣在鼠標移動時,只有在確定切片提取標志為1時,執行切片提取功能。
vtkCommand::MouseMoveEvent即為鼠標移動消息。當檢測到該消息時,首先檢查切片提取標志,當為1時提取切片。提取切片時,需要為vtkImageSlice對象設置變換矩陣。這里在函數開始時,首先獲取了鼠標滑動的前后兩次點的位置lastPos和currPos。然后根據兩點的Y坐標差deltaY,計算新的中心點center並變換至vtkImageSlice當前變換矩陣中,得到變換中心點,將其設置到原來的變換矩陣matrix中,並設置到vtkImageSlice中,最后執行interactor->Render()即可不斷的根據鼠標移動刷新圖像。
Command對象定義完畢后,即可為交互對象InteractorStyle添加觀察者,響應鼠標消息。這里可以在上節的程序上進行修改,前面代碼一致,只需要在最后添加如下代碼:
1: vtkSmartPointer<vtkImageInteractionCallback> callback =
2: vtkSmartPointer<vtkImageInteractionCallback>::New();
3: callback->SetImageReslice(reslice);
4: callback->SetInteractor(renderWindowInteractor);
5:
6: imagestyle->AddObserver(vtkCommand::MouseMoveEvent, callback);
7: imagestyle->AddObserver(vtkCommand::LeftButtonPressEvent, callback);
8: imagestyle->AddObserver(vtkCommand::LeftButtonReleaseEvent, callback);
9:
10: renderWindowInteractor->Start();
這里主要是定義了vtkImageInteractionCallback對象,並設置vtkImageSlice對象和vtkRenderWindowInteractor對象。然后為交互對象vtkInteractorStyle添加觀察者來監控相應的消息,這里主要是三個消息:
vtkCommand::LeftButtonPressEvent,
vtkCommand::LeftButtonReleaseEvent,
vtkCommand::MouseMoveEvent,
當響應到這三個消息時,立即執行vtkImageInteractionCallback的Execute函數,以便實現切片的實時提取和更新。完成以后,運行程序,當鼠標在圖像上移動時,會發現圖像會跟着鼠標的移動而變化,神奇吧?有興趣的話,還可以實現YZ平面、XZ平面切片提取,甚至是任意方向的切面提取。
==========歡迎轉載,轉載時請保留該聲明信息==========
版權歸@東靈工作室所有,更多信息請訪問東靈工作室