一、 系統(tǒng)概況
我們實(shí)現(xiàn)了一個(gè)系統(tǒng),可以從CT圖像中將肺部從胸腔中分離出來,并且通過三維重建實(shí)現(xiàn)可視化。該系統(tǒng)是基于Visual Studio 2013平臺,借助VTK-7.0和Qt5.6開源庫通過C++語言實(shí)現(xiàn)。
二、 系統(tǒng)設(shè)計(jì)
肺部CT圖像分割及重建系統(tǒng)的實(shí)現(xiàn)需要幾個(gè)方面的工作,一是CT圖像的肺部分割;二是CT圖像的三維重建;三是可視化界面的設(shè)計(jì)。
根據(jù)工作內(nèi)容的不同,需要用到不同的開源庫。我們通過VTK實(shí)現(xiàn)了CT圖像的三維重建,包含體繪制和面繪制兩種重建算法;通過Qt實(shí)現(xiàn)了UI的設(shè)計(jì)。因此在windows10環(huán)境下用VS2013編譯支持Qt的VTK庫,是前期的準(zhǔn)備工作。
在肺部分割這一部分工作上,我們采用以閾值分割為主,其余簡單圖像處理操作如濾波操作等為輔,進(jìn)行肺部的分割。
三、系統(tǒng)實(shí)現(xiàn)(含源代碼)
3.1 前期準(zhǔn)備工作——Windows10環(huán)境下用VS2013編譯支持Qt的VTK庫
網(wǎng)上有大量的相關(guān)文章,但由于VTK,VS,Qt的版本不同,可能導(dǎo)致Qt與VTK的聯(lián)動(dòng)失敗。在此,我們參考了兩位博主的搭建方式:
(1) http://www.cnblogs.com/tianhu9102/p/7641397.html
(2) https://blog.csdn.net/u011017966/article/details/40984473
這兩篇文章對應(yīng)兩種不同的搭建方式,一種是在兩種庫編譯成功后通過直接添加庫目錄,包含目錄的方法實(shí)現(xiàn)VTK與Qt聯(lián)動(dòng);另一種則跳過了添加庫目錄,包含目錄等繁瑣操作,程序全部用cmake來管理,直接通過cmake進(jìn)行編譯實(shí)現(xiàn)一個(gè)工程文件。
經(jīng)過驗(yàn)證,兩種方法均可實(shí)現(xiàn)VTK + QT的聯(lián)動(dòng)。
3.2三維重建的實(shí)現(xiàn)
我們采用了兩種三維重建方法:體繪制和面繪制。
面繪制是將感興趣的部分以等值面的形式抽取出來便于利用真實(shí)感技術(shù),通過任意旋轉(zhuǎn)和變換光照效果來生成高質(zhì)量的三維圖像,并可以方便的對其進(jìn)行觀察和分析。
面繪制的代碼如下:
void MainWindow::open(){//抽取等值面為骨頭的信息 vtkSmartPointer< vtkMarchingCubes > boneExtractor =vtkSmartPointer< vtkMarchingCubes >::New();boneExtractor->SetInputConnection(threshould->GetOutputPort());//boneExtractor->SetValue(0, 500); //設(shè)置提取的等值信息boneExtractor->SetValue(0, 1000);boneExtractor->SetNumberOfContours(1);boneExtractor->Update();//剔除舊的或廢除的數(shù)據(jù)單元,提高繪制速度(可略去這一步) vtkSmartPointer< vtkStripper > boneStripper =vtkSmartPointer< vtkStripper >::New(); //三角帶連接 boneStripper->SetInputConnection(boneExtractor->GetOutputPort());boneStripper->Update();//建立映射 vtkSmartPointer< vtkPolyDataMapper > boneMapper =vtkSmartPointer< vtkPolyDataMapper >::New();boneMapper->SetInputData(boneStripper->GetOutput());boneMapper->ScalarVisibilityOff();//建立角色 vtkSmartPointer< vtkActor > bone =vtkSmartPointer< vtkActor >::New();bone->SetMapper(boneMapper);bone->GetProperty()->SetDiffuseColor(.1, .94, .52);bone->GetProperty()->SetSpecular(.3);bone->GetProperty()->SetSpecularPower(20);bone->GetProperty()->SetOpacity(1.0);bone->GetProperty()->SetColor(1,1,1);bone->GetProperty()->SetRepresentationToSurface();//定義繪制器 vtkSmartPointer< vtkRenderer > aRenderer =vtkSmartPointer< vtkRenderer >::New();//定義繪制窗口 vtkSmartPointer< vtkRenderWindow > renWin = ui->qvtkWidget_Volume->GetRenderWindow();//vtkSmartPointer< vtkRenderWindow >::New();renWin->AddRenderer(aRenderer);//定義窗口交互器 vtkSmartPointer< vtkRenderWindowInteractor > iren =vtkSmartPointer< vtkRenderWindowInteractor >::New();iren->SetRenderWindow(renWin);//創(chuàng)建一個(gè)camera vtkSmartPointer< vtkCamera > aCamera =vtkSmartPointer< vtkCamera >::New();aCamera->SetViewUp(0, 0, -1);aCamera->SetPosition(0, 1, 0);aCamera->SetFocalPoint(0, 0, 0);aRenderer->AddActor(bone);aRenderer->SetActiveCamera(aCamera);aRenderer->ResetCamera();aCamera->Dolly(1.5);aRenderer->SetBackground(0, 0, 0);aRenderer->ResetCameraClippingRange();vtkInteractorStyleTrackballCamera *style = //設(shè)置交互方式vtkInteractorStyleTrackballCamera::New();iren->SetInteractorStyle(style);iren->Initialize();iren->Start();
}
體繪制的原理和面繪制完全不相同。面繪制需要生成中間圖元,而體繪制則是直接在原圖上進(jìn)行繪制,內(nèi)容需求較面繪制小。每切換一個(gè)視角需要重新對所有的像素點(diǎn)進(jìn)行顏色和透明度計(jì)算,需要時(shí)間比面繪制長。VTK中基于體繪制實(shí)現(xiàn)三維重建,使用的是光線投射法(Ray-casting)。
體繪制的具體代碼如下:
void MainWindow::volume(){VTK_MODULE_INIT(vtkRenderingVolumeOpenGL2);vtkSmartPointer<vtkFixedPointVolumeRayCastMapper> volumeMapper =vtkSmartPointer<vtkFixedPointVolumeRayCastMapper>::New();volumeMapper->SetInputConnection(threshould->GetOutputPort());volumeMapper->SetBlendModeToComposite();volumeMapper->SetSampleDistance(0.3);volumeMapper->AutoAdjustSampleDistancesOff();vtkSmartPointer<vtkVolumeProperty> volumeProperty =vtkSmartPointer<vtkVolumeProperty>::New();volumeProperty->SetInterpolationTypeToLinear();volumeProperty->ShadeOn(); //打開或者關(guān)閉陰影測試 volumeProperty->SetAmbient(0.2);volumeProperty->SetDiffuse(1.2); //漫反射 volumeProperty->SetSpecular(0.1); //鏡面反射 volumeProperty->SetSpecularPower(10);//設(shè)置不透明度 vtkSmartPointer<vtkPiecewiseFunction> compositeOpacity =vtkSmartPointer<vtkPiecewiseFunction>::New();compositeOpacity->AddPoint(70, 0.00);compositeOpacity->AddPoint(90, 0.40);compositeOpacity->AddPoint(180, 0.60);volumeProperty->SetScalarOpacity(compositeOpacity); //設(shè)置不透明度傳輸函數(shù) //設(shè)置梯度不透明屬性 vtkSmartPointer<vtkPiecewiseFunction> volumeGradientOpacity =vtkSmartPointer<vtkPiecewiseFunction>::New();volumeGradientOpacity->AddPoint(10, 0.0);volumeGradientOpacity->AddPoint(90, 0.5);volumeGradientOpacity->AddPoint(100, 1.0);volumeProperty->SetGradientOpacity(volumeGradientOpacity);//設(shè)置梯度不透明度效果對比 //設(shè)置顏色屬性 vtkSmartPointer<vtkColorTransferFunction> color =vtkSmartPointer<vtkColorTransferFunction>::New();color->AddRGBPoint(64.00, 1.00, 0.52, 0.30);volumeProperty->SetColor(color);/********************************************************************************/vtkSmartPointer<vtkVolume> volume =vtkSmartPointer<vtkVolume>::New();volume->SetMapper(volumeMapper);volume->SetProperty(volumeProperty);vtkSmartPointer<vtkRenderer> ren = vtkSmartPointer<vtkRenderer>::New();ren->SetBackground(0, 0, 0);ren->AddVolume(volume);vtkSmartPointer<vtkRenderWindow> rw =ui->qvtkWidget_Volume_6->GetRenderWindow();rw->AddRenderer(ren);rw->Render();vtkSmartPointer<vtkRenderWindowInteractor> rwi =vtkSmartPointer<vtkRenderWindowInteractor>::New();rwi->SetRenderWindow(rw);vtkInteractorStyleTrackballCamera *st = //設(shè)置交互方式vtkInteractorStyleTrackballCamera::New();rwi->SetInteractorStyle(st);/********************************************************************************/ren->ResetCamera();rw->Render();
}
3.3 UI設(shè)計(jì)
Qt的UI界面設(shè)計(jì)如下圖所示:
我們主要用到了Qt的幾個(gè)常用Widget:
PushButton:用于啟動(dòng)某個(gè)功能
horizontalScrollBar: 用于手動(dòng)調(diào)整上界閾值和下界閾值
textEdit:用于輸入圖像分割的矩形框大小(后面分割會(huì)提到)
textBrowser: 用于顯示程序運(yùn)行狀態(tài)
3.4 分割操作的實(shí)現(xiàn)
為了盡可能提高分割的效果,我們采用了多管齊下的方法。
首先,我們設(shè)置了以交互的方式,讓用戶輸入上界閾值和下界閾值,則CT圖像的CT值在這個(gè)閾值內(nèi)的保留原值不變,小于下界閾值的變黑,大于上界閾值的變白。
void MainWindow::segmentBetween(){int upper, lower;// 從滑條里分別獲取上界和下界閾值upper = ui->horizontalScrollBar->value();lower = ui->horizontalScrollBar_2->value();threshould->ThresholdBetween(lower, upper);// 利用ThresholdBetween進(jìn)行分割threshould->SetInValue(1024);threshould->SetOutValue(-1024);threshould->Update(); //算法執(zhí)行后必須添加的更新消息slice();
}
其次,因?yàn)镃T原圖像中有CT機(jī)床等干擾,我們在原CT圖像上進(jìn)行截取,只需輸入矩形框的兩個(gè)對角頂點(diǎn)的坐標(biāo),就可以輕松截取框內(nèi)的圖像,框外的圖像全部置黑。
最后,我們發(fā)現(xiàn)通過簡單的閾值分割并不能很好地去除一些細(xì)節(jié)上的噪聲和胸腔組織,因此我們寫了一個(gè)簡單的濾波算法,進(jìn)行濾波操作,根據(jù)該像素點(diǎn)的鄰域是否具有像素值進(jìn)行判斷,從而極大程度上去除了噪聲,取得了不錯(cuò)的分割效果。
void MainWindow::Filter(){int dims[3];int upperNum, upperNum2, upperNum3, upperNum4;QString upper, upper2, upper3, upper4;upper = ui->textEdit->document()->toPlainText();upperNum = upper.toInt();upper2 = ui->textEdit_2->document()->toPlainText();upperNum2 = upper2.toInt();upper3 = ui->textEdit_3->document()->toPlainText();upperNum3 = upper3.toInt();upper4 = ui->textEdit_4->document()->toPlainText();upperNum4 = upper4.toInt();reader->GetOutput()->GetDimensions(dims);for (int k = 0; k < dims[2] ; k++){for (int j = 0; j < dims[1] ; j++){for (int i = 0; i < dims[0] ; i++){short *pixel = (short *)(threshould->GetOutput()->GetScalarPointer(i, j, k));if (j < upperNum2 || j > upperNum4)*pixel = -1024;if (i < upperNum || i > upperNum3)*pixel = -1024;if ((j > 5) && (j < dims[1] - 6)){short *pixel2 = (short *)(threshould->GetOutput()->GetScalarPointer(i, j + 5, k));short *pixel3 = (short *)(threshould->GetOutput()->GetScalarPointer(i, j - 5, k));if ((*pixel2 < -500) && (*pixel3 <-500))*pixel = -1024;}}}}threshould->Update();slice();
}
四、結(jié)果展示
分割:手動(dòng)設(shè)置上界閾值和下界閾值,并在去噪輸入框內(nèi)輸入矩形框的對角頂點(diǎn)坐標(biāo),則可達(dá)到分割的效果。
顯示:左上為三維重建的體繪制(左)和面繪制(右),可以通過鼠標(biāo)拖動(dòng),從不同視角觀看肺部結(jié)構(gòu)。右邊為水平面,矢狀面,冠狀面的顯示,通過拖動(dòng)進(jìn)度條可以看不同層的截面顯示。
資源已經(jīng)上傳:
https://download.csdn.net/download/h_swhite/10901427
總結(jié)
以上是生活随笔為你收集整理的肺部CT图像分割及重建系统的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。