3D血管表面重建

时间:2017-01-20 22:02:25

标签: vtk itk

我有一个包含一个血管的3D血管自由手超声波体积,我正在尝试重建血管表面。 3D体积由一堆2D图像/ B扫描构成,并且每个B扫描中的血管轮廓已被分割;也就是说,我有一个椭圆,表示体积中每个B扫描中血管的轮廓。我试图通过遵循'GenerateModelsFromLabels.cxx'(http://www.vtk.org/Wiki/VTK/Examples/Cxx/Medical/GenerateModelsFromLabels)的VTK示例来重建血管的轮廓。然而,结果不是从一帧到另一帧的光滑表面,正如我希望的那样。它是不连续和不规则的,如果椭圆之间的位移很大,则表面不会连接体积中两个相邻框架之间的血管轮廓。在我的方法中,我基本上使用了DiscreteMarchingCubes - > WindowedSincPolyDataFilter - > GeometryFilter。

我使用了passband,smoothingIterations和featureAngle参数,并且我能够获得以下最佳结果:

remove diag

remove diag

remove diag

正如你所看到的,它不是一个平滑的连续表面,在相邻的框架之间有很多未插入的“孔”,但它没有问题。可以做得更好吗?我也试过使用3D Delaunay三角测量,但它只给了我凸壳,这不是我预期的输出。我想知道是否有更好的方法来重建一个紧密跟随容器轮廓的表面,从一个B扫描到另一个B扫描?

最小的工作示例如下所示:

    vtkSmartPointer<vtkImageData> vesselVolume =
    vtkSmartPointer<vtkImageData>::New();

int totalImages = 210;

for (int z = 0; z < totalImages; z++)
{
    std::string strFile = "E:/datasets/vasc/rendering/contour/" + std::to_string(z + 1) + ".png";

    cv::Mat im = cv::imread(strFile, CV_LOAD_IMAGE_GRAYSCALE);

    if (z == 0)
    {
        vesselVolume->SetExtent(0, im.cols, 0, im.rows, 0, totalImages - 1);
        vesselVolume->SetSpacing(1, 1, 1);
        vesselVolume->SetOrigin(0, 0, 0);
        vesselVolume->AllocateScalars(VTK_UNSIGNED_CHAR, 0);
    }   

    std::vector<cv::Point2i> locations;   // output, locations of non-zero pixels 
    cv::findNonZero(im, locations);

    for (int nzi = 0; nzi < locations.size(); nzi++)
    {
        unsigned char* pixel = static_cast<unsigned char*>(vesselVolume->GetScalarPointer(locations[nzi].x, locations[nzi].y, z));
        pixel[0] = 255;
    }
}

vtkSmartPointer<vtkDiscreteMarchingCubes> discreteCubes =
    vtkSmartPointer<vtkDiscreteMarchingCubes>::New();

discreteCubes->SetInputData(vesselVolume);
discreteCubes->GenerateValues(1, 255, 255);
discreteCubes->ComputeNormalsOn();

vtkSmartPointer<vtkWindowedSincPolyDataFilter> smoother =
    vtkSmartPointer<vtkWindowedSincPolyDataFilter>::New();

unsigned int smoothingIterations = 10;
double passBand = 2;
double featureAngle = 360.0;

smoother->SetInputConnection(discreteCubes->GetOutputPort());
smoother->SetNumberOfIterations(smoothingIterations);
smoother->BoundarySmoothingOff();
//smoother->FeatureEdgeSmoothingOff();
smoother->FeatureEdgeSmoothingOn();
smoother->SetFeatureAngle(featureAngle);
smoother->SetPassBand(passBand);
smoother->NonManifoldSmoothingOn();
smoother->BoundarySmoothingOn();
smoother->NormalizeCoordinatesOn();
smoother->Update();

vtkSmartPointer<vtkThreshold> selector =
    vtkSmartPointer<vtkThreshold>::New();

selector->SetInputConnection(smoother->GetOutputPort());
selector->SetInputArrayToProcess(0, 0, 0,
    vtkDataObject::FIELD_ASSOCIATION_CELLS,
    vtkDataSetAttributes::SCALARS);

vtkSmartPointer<vtkMaskFields> scalarsOff =
    vtkSmartPointer<vtkMaskFields>::New();

// Strip the scalars from the output
scalarsOff->SetInputConnection(selector->GetOutputPort());
scalarsOff->CopyAttributeOff(vtkMaskFields::POINT_DATA,
    vtkDataSetAttributes::SCALARS);
scalarsOff->CopyAttributeOff(vtkMaskFields::CELL_DATA,
    vtkDataSetAttributes::SCALARS);

vtkSmartPointer<vtkGeometryFilter> geometry =
    vtkSmartPointer<vtkGeometryFilter>::New();

geometry->SetInputConnection(scalarsOff->GetOutputPort());
geometry->Update();

vtkSmartPointer<vtkPolyDataMapper> mapper =
    vtkSmartPointer<vtkPolyDataMapper>::New();  
mapper->SetInputConnection(geometry->GetOutputPort());  
mapper->ScalarVisibilityOff();
mapper->Update();

vtkSmartPointer<vtkRenderWindow> renderWindow =
    vtkSmartPointer<vtkRenderWindow>::New();

vtkSmartPointer<vtkRenderWindowInteractor> renderWindowInteractor =
    vtkSmartPointer<vtkRenderWindowInteractor>::New();
renderWindowInteractor->SetRenderWindow(renderWindow);

    vtkSmartPointer<vtkRenderer> renderer =
    vtkSmartPointer<vtkRenderer>::New();

renderWindow->AddRenderer(renderer);
renderer->SetBackground(.2, .3, .4);

vtkSmartPointer<vtkActor> actor =
    vtkSmartPointer<vtkActor>::New();
actor->SetMapper(mapper);
renderer->AddActor(actor);

renderer->ResetCamera();

renderWindow->Render();
renderWindowInteractor->Start();        

1 个答案:

答案 0 :(得分:0)

假设您的问题在切片之间动摇,一种改善结果的可能方法是将切片应用于切片注册。使用ImageJ应该很容易。使用切片之间的变换也可以转换标记的图像。然后通过当前管道运行转换后的标签图像。