0. Background
很久很久以前记录了一下使用WPF进行三维重建的一些探索,后来了解到了VTK这个开发包,
觉得功能很强大,因此后续都在基于VTK进行三维重建,在前文中对于VTK的一些相关网站进行了介绍
http://www.cnblogs.com/dawnWind/archive/2013/01/14/3D_04.html
这里就不再累赘了,感兴趣的Google一下即可。
对于VTK感兴趣的还可能会认识到与之相关的几个开发包。
其一是ITK:http://www.itk.org/
ITK始于著名的(VHP)Visible Human Project
http://www.nlm.nih.gov/research/visible/visible_gallery.html
简要地说VHP就是使用现代技术使用医学扫描等方式(如X光、CT、CMR等)获得人体二维图片,并根据这些图片构建出三维模型,
当然这里说要求的精度以及细度都非常高。人体的三维模型在我们头脑中可能立马会闪现很多电影里面有的场景,
一个组织脉络都清晰可见的人体呈现在我们面前。可以说在那个时代美国提出的这一科技项目不仅是高屋建瓴实际上
也给后续相关技术的发展带来了巨大的推动。
不过ITK说关注的更多是三维数据的测量、切割等,如果要进行三维呈现通常可以和VTK进行协作,这两者之间的数据
互通也很方便。不过对于ITK的了解我也就到此为止,并没有安装并使用过。
国内对于ITK、VTK开发包类似的研究主要由田捷带领开发的MITK,它整合ITK、VTK,为了达到一致简洁的开发,主要针对的领域是医学处理。
背景知识就这么多,接下来说一下使用VTK进行三维重建的基本步骤。
1. 3D Reconstruction with VTK
先上码。
// 读取文件夹下图片,将图像进行轮廓提取后再进行三维重建 int build3DViewFull() { vtkSmartPointer<vtkRenderer> aRenderer = vtkSmartPointer<vtkRenderer>::New(); vtkSmartPointer<vtkRenderWindow> renWin = vtkSmartPointer<vtkRenderWindow>::New(); renWin->AddRenderer(aRenderer); vtkSmartPointer<vtkRenderWindowInteractor> iren = vtkSmartPointer<vtkRenderWindowInteractor>::New(); iren->SetRenderWindow(renWin); // 新建文件读取对象,常见的有vtkBMPReader、vtkDICOMImageReader、vtkJPEGReader等 vtkSmartPointer<vtkJPEGReader> jpegReader = vtkSmartPointer<vtkJPEGReader>::New(); // 不同的reader需要设置的参数是不同的 因此本例仅适合jpegreader jpegReader->SetFilePrefix("C:/Users/DawnWind/Desktop/000/"); // 要打开的路径 jpegReader->SetFilePattern("%s%d.jpg"); // 图片文件名格式,此处为 0.jpg 1.jpg ... jpegReader->SetDataByteOrderToLittleEndian(); jpegReader->SetDataSpacing(1, 1, 1.4); // 设置图片中像素比,我理解得不清楚,具体请百度之 jpegReader->SetFileNameSliceSpacing(1); jpegReader->SetDataExtent(0, 209, 0, 209, 0, 29); // 这里因为在000文件夹里面有0.jpg ~ 29.jpg,所以设置为 0,29 // 每张图片的长宽为210 * 210 因此设置为0,209 jpegReader->Update(); // update这里要注意一下,对于VTK在默认情况下是在最后操作时候才一次性刷新 // 也就是说如果没有自动刷新的话,在一些中间过程中是无法获得到数据的,因为没update进去 vtkSmartPointer<vtkContourFilter> skinExtractor = vtkSmartPointer<vtkContourFilter>::New(); skinExtractor->SetInputConnection(jpegReader->GetOutputPort()); skinExtractor->SetValue(200, 100); //值越大,保留的部分越少。 //重新计算法向量 vtkSmartPointer<vtkPolyDataNormals> skinNormals = vtkSmartPointer<vtkPolyDataNormals>::New(); skinNormals->SetInputConnection(skinExtractor->GetOutputPort()); skinNormals->SetFeatureAngle(60.0); //Specify the angle that defines a sharp edge. //If the difference in angle across neighboring polygons is greater than this value, //the shared edge is considered "sharp". //create triangle strips and/or poly-lines 为了更快的显示速度 vtkSmartPointer<vtkStripper> skinStripper = vtkSmartPointer<vtkStripper>::New(); skinStripper->SetInputConnection(skinNormals->GetOutputPort()); vtkSmartPointer<vtkPolyDataMapper> skinMapper = vtkSmartPointer<vtkPolyDataMapper>::New(); skinMapper->SetInputConnection(skinStripper->GetOutputPort()); skinMapper->ScalarVisibilityOff(); //这样不会带颜色 vtkSmartPointer<vtkActor> skin = vtkSmartPointer<vtkActor>::New(); skin->SetMapper(skinMapper); // An outline provides context around the data. // 一个围绕在物体的立体框,可以先忽略 /* vtkSmartPointer<vtkOutlineFilter> outlineData = vtkSmartPointer<vtkOutlineFilter>::New(); outlineData->SetInputConnection(dicomReader->GetOutputPort()); vtkSmartPointer<vtkPolyDataMapper> mapOutline = vtkSmartPointer<vtkPolyDataMapper>::New(); mapOutline->SetInputConnection(outlineData->GetOutputPort()); vtkSmartPointer<vtkActor> outline = vtkSmartPointer<vtkActor>::New(); outline->SetMapper(mapOutline); outline->GetProperty()->SetColor(0,0,0); aRenderer->AddActor(outline); */ // It is convenient to create an initial view of the data. The FocalPoint // and Position form a vector direction. Later on (ResetCamera() method) // this vector is used to position the camera to look at the data in // this direction. vtkSmartPointer<vtkCamera> aCamera = vtkSmartPointer<vtkCamera>::New(); aCamera->SetViewUp (0, 0, -1); aCamera->SetPosition (0, 1, 0); aCamera->SetFocalPoint (0, 0, 0); aCamera->ComputeViewPlaneNormal(); aCamera->Azimuth(30.0); aCamera->Elevation(30.0); // Actors are added to the renderer. An initial camera view is created. // The Dolly() method moves the camera towards the FocalPoint, // thereby enlarging the image. aRenderer->AddActor(skin); aRenderer->SetActiveCamera(aCamera); aRenderer->ResetCamera (); aCamera->Dolly(1.5); // Set a background color for the renderer and set the size of the // render window (expressed in pixels). aRenderer->SetBackground(.2, .3, .4); renWin->SetSize(640, 480); // Note that when camera movement occurs (as it does in the Dolly() // method), the clipping planes often need adjusting. Clipping planes // consist of two planes: near and far along the view direction. The // near plane clips out objects in front of the plane; the far plane // clips out objects behind the plane. This way only what is drawn // between the planes is actually rendered. aRenderer->ResetCameraClippingRange (); // Initialize the event loop and then start it. iren->Initialize(); iren->Start(); return 0; }
通过上面代码的可以看出,对于一个三维重建是有一些必须步骤要走的。
一是要有输入源(上文中是reader读入的数据)通过处理构成的模型actor、二是要有相机(camera)、三要有用于展示的窗口(window)
其他的就是将其中的脉络理清了,可以仿照上面代码所述进行理解,具体理论可以去查找一下OpenGL的三维模型结构,其实与我前面说的
WPF三维模型相关理论是相通的。
可以获得类似下图结果:
号外:关于VTK的设置之类就没有多说了,假设大家已经可以运行VTK的示例