最近折腾MFC中的PCL点云显示,折腾了很久,尝试过OpenGL方案、VTK方案等,最终整理出一套最优的方案,决定使用VTK来显示点云。博主将显示整理成一个CVtkViewer类,该类继承自CStatic,直接修改Picture控件变量的类型就可以实现显示,对点云和图像接口进行封装,使用较为方便和安全。并且使用VTK显示的类封装可以在多个窗口分别显示,如左图所示可以在两个以上的窗口分别显示不同的点云,如果使用OpenGL来实现会相当麻烦。还有就是VTK这种上层接口封装非常全面,OpenGL太偏底层了,比如VTK会自动设置合适的相机位置,而OpenGL实现同样的效果就需要自己解算位置,这也是博主放弃OpenGL的重要原因。
在这篇博客之前,你需要对VTK开发有基本了解,博主使用的是VTK7.0,PCL1.8.0,博客中对博主遇到的编译错误、运行错误等都做了详细解释,希望能有帮助。
博主参考了大量资料,前排放一些有价值的参考链接:
http://blog.csdn.net/wishchin/article/details/45887561
http://blog.csdn.net/wishchin/article/details/45951543
http://blog.sina.com.cn/s/blog_e7783fcf0102xqip.html
http://tieba.baidu.com/p/5131946762
创建CVtkViewer类
下面提供了CVtkViewer类的源码,这里对源码做简单的解释:
1.该类继承自CStatic,支持CStatic的几乎所有操作,但是MoveWindow会改变位置,不会改变大小,所以类中有对MoveWindow的重载
2.显示接口有两个,一个是自定义的RGBXYZ点云裸数据接口,ReadPointCloud,另一个是vtkImageData接口,vtkImageData不仅支持二维图像,也支持三维图像,更多消息请移步百度
3.注意头文件中这样一段代码#include <vtkAutoInit.h> VTK_MODULE_INIT(vtkRenderingOpenGL) VTK_MODULE_INIT(vtkInteractionStyle) VTK_MODULE_INIT(vtkRenderingFreeType),你可以先去掉这一段代码,在Debug模式下看看VTK是否有错误消息,如果没有错误消息就不要加这几行代码,这是由于使用本地编译器编译VTK时有些宏没有定义,会导致一些错误。vtk还有很多奇葩错误,参见我前一篇博客。
4.点云显示的接口,有一段注释掉了,如果你只想显示单色点云,那么可以使用被注释掉的那段代码,如果想显示彩色点云,就不需要修改
5.头文件最后还注释掉了一个类,如果你想多串口同步移动相机,可以通过这个类同时向多个窗口发送命令,详细参见开头给出的链接,虽仍然博主想到其它更简单的方法,但是没有博主的项目没有需求,就没有加进来。
6.在Debug模式下,关闭窗口时会有VTK Error 1208,应该和OpenGL或者Render有关,目前还没有查出来,如果你解决了这个问题,非常希望你能留言告知,这是这个类中(目前发现的)唯一的瑕疵,博主将非常感激。
好了,粘代码吧,首先是头文件:
#pragma once #include "afxwin.h" #include <vtkResliceCursor.h> #include <vtkResliceCursorWidget.h> #include <vtkPlane.h> #include <vtkPlaneSource.h> #include <vtkPlaneWidget.h> #include <vtkImagePlaneWidget.h> #include <vtkResliceCursorThickLineRepresentation.h> #include <vtkResliceCursor.h> #include <vtkCommand.h> #include <vtkViewport.h> #include <vtkViewDependentErrorMetric.h> #include <vtkSmartPointer.h> #include <vtkRenderer.h> #include <vtkRendererSource.h> #include <vtkRenderingOpenGLModule.h> #include <vtkRenderWindow.h> #include <vtkWin32OpenGLRenderWindow.h> #include <vtkWin32RenderWindowInteractor.h> #include <vtkPolyVertex.h> #include <vtkUnstructuredGrid.h> #include <vtkDataSetMapper.h> #include <vtkActor.h> #include <vtkProperty.h> #include <vtkLookupTable.h> #include <vtkFloatArray.h> #include <vtkPointData.h> #include <vtkAutoInit.h> VTK_MODULE_INIT(vtkRenderingOpenGL) VTK_MODULE_INIT(vtkInteractionStyle) VTK_MODULE_INIT(vtkRenderingFreeType) using Pointf = struct _Pointf{ float x; float y; float z; float r; float g; float b; }; class CVtkViewer : public CStatic { DECLARE_DYNAMIC(CVtkViewer) public: CVtkViewer(); virtual ~CVtkViewer(); vtkSmartPointer<vtkActor> actor; void ReadPointCloud(std::vector<Pointf>&); public: //3.2 重载CvtkView类PreSubclassWindow()函数和OnPaint()函数 //PreSubclassWindow函数负责创建VTK可视化管线,OnPaint()函数负责客户区内场景渲染。 //vtkAcor,vtkRenderer,vtkRenderWindow,vtkRenderWindowInteractor四个部分。当然根据需要还可以设置vtkRenderWindowInteractorStyle,以及光照,材质,颜色等。 //在CvtkView类头文件中定义相关对象,并在PreSubclassWindow函数中实例化和构建可视化管线 void PreSubclassWindow(); void SetImageData(vtkSmartPointer<vtkImageData> ImageData); void SetupReslice(); void MoveWindow(CRect); private: vtkSmartPointer< vtkImagePlaneWidget > m_ImagePlaneWidget; vtkSmartPointer< vtkResliceCursorWidget> m_ResliceCursorWidget; vtkSmartPointer< vtkResliceCursor > m_ResliceCursor; vtkSmartPointer< vtkResliceCursorThickLineRepresentation > m_ResliceCursorRep; vtkSmartPointer<vtkRenderer> m_Renderer; vtkSmartPointer<vtkRenderWindow> m_RenderWindow; vtkSmartPointer<vtkImageData> m_ImageData; //m_Direction为方向标志,取值分别为0,1和2,分别代表X轴,Y轴和Z轴方向, int m_Direction; protected: DECLARE_MESSAGE_MAP() }; //当用户改变图像切分的坐标轴时(旋转坐标轴或者平移坐标系),图像切分平面会产生相应的改变, //如果将新的切分平面更新到二维视图的vtkImagePlaneWidget对象中,即可实现三维视图的同步更新操作。 ///基于以上设计,实现一个vtkCommand子类,来监听vtkResliceCursorWidget::ResliceAxesChangedEvent消息,并实现相应的更新操作。 //class vtkResliceCursorCallback : public vtkCommand //{ //public: // static vtkResliceCursorCallback *New() // { // return new vtkResliceCursorCallback; // } // // CVtkViewer* view[4]; // //public: // void Execute(vtkObject *caller, unsigned long /*ev*/, // void *callData) // { // vtkResliceCursorWidget *rcw = dynamic_cast<vtkResliceCursorWidget *>(caller); // if (rcw) // { // for (int i = 0; i < 3; i++) // { // vtkPlaneSource *ps = // static_cast<vtkPlaneSource *>(view[i]->GetImagePlaneWidget()->GetPolyDataAlgorithm()); // // ps->SetOrigin(view[i]->GetResliceCursorWidget()-> // GetResliceCursorRepresentation()->GetPlaneSource()->GetOrigin()); // ps->SetPoint1(view[i]->GetResliceCursorWidget()-> // GetResliceCursorRepresentation()->GetPlaneSource()->GetPoint1()); // ps->SetPoint2(view[i]->GetResliceCursorWidget()-> // GetResliceCursorRepresentation()->GetPlaneSource()->GetPoint2()); // // view[i]->GetImagePlaneWidget()->UpdatePlacement(); // view[i]->Render(); // } // view[3]->Render(); // } // // } // // vtkResliceCursorCallback() {} //};
然后是cpp文件:
#include "stdafx.h" #include "VtkViewer.h" IMPLEMENT_DYNAMIC(CVtkViewer, CStatic) CVtkViewer::CVtkViewer() { //在实例化时需要注意,该视图类在默认情况下渲染的是vtkResliceCursorWidget对象的输出, //因此需要为vtkResliceCursorWidget对象指定相应的vtkRenderer对象, //m_ResliceCursorWidget->SetInteractor(m_RenderWindow->GetInteractor()); //m_ResliceCursorWidget->SetDefaultRenderer(m_Renderer); } CVtkViewer::~CVtkViewer() { } void CVtkViewer::ReadPointCloud(std::vector<Pointf>& pointcloud){ int n = pointcloud.size(); vtkSmartPointer<vtkPoints> points = vtkSmartPointer<vtkPoints>::New(); points->SetNumberOfPoints(n); vtkSmartPointer<vtkLookupTable> lut = vtkSmartPointer<vtkLookupTable>::New(); lut->SetNumberOfTableValues(n); lut->Build(); for (int i = 0; i < n; ++i){ points->InsertPoint(i, pointcloud[i].x, pointcloud[i].y, pointcloud[i].z); lut->SetTableValue(i, pointcloud[i].r, pointcloud[i].g, pointcloud[i].b); } vtkSmartPointer<vtkPolyVertex> polyVertex = vtkSmartPointer<vtkPolyVertex>::New(); polyVertex->GetPointIds()->SetNumberOfIds(n); vtkSmartPointer<vtkFloatArray> pointsScalars = vtkSmartPointer<vtkFloatArray>::New(); pointsScalars->SetNumberOfTuples(n); for (int i = 0; i < n; ++i){ polyVertex->GetPointIds()->SetId(i, i); pointsScalars->InsertValue(i, i); } vtkSmartPointer<vtkUnstructuredGrid> grid = vtkSmartPointer<vtkUnstructuredGrid>::New(); grid->Allocate(1, 1); grid->SetPoints(points); grid->GetPointData()->SetScalars(pointsScalars); grid->InsertNextCell(polyVertex->GetCellType(), polyVertex->GetPointIds()); vtkSmartPointer<vtkDataSetMapper> mapper = vtkSmartPointer<vtkDataSetMapper>::New(); mapper->SetInputData(grid); mapper->ScalarVisibilityOn(); mapper->SetScalarRange(0, n-1); mapper->SetLookupTable(lut); m_Renderer->RemoveActor(actor); actor = vtkSmartPointer<vtkActor>::New(); actor->SetMapper(mapper); actor->GetProperty()->SetRepresentationToPoints(); actor->GetProperty()->SetPointSize(2); m_Renderer->AddActor(actor); m_Renderer->SetBackground(0, 0, 0); m_Renderer->ResetCamera(); m_RenderWindow->Render(); // 点云全部显示同样的颜色 /* vtkPoints * points = vtkPoints::New(); int n = pointcloud.size(); int idx = 0; for (int i = 0; i < n; ++i){ points->InsertPoint(i, pointcloud[i].x, pointcloud[i].y, pointcloud[i].z); } vtkPolyVertex * polyvertex = vtkPolyVertex::New(); polyvertex->GetPointIds()->SetNumberOfIds(n); for (int i = 0; i < n; ++i){ polyvertex->GetPointIds()->SetId(i, i); } vtkUnstructuredGrid * grid = vtkUnstructuredGrid::New(); grid->SetPoints(points); grid->InsertNextCell(polyvertex->GetCellType(), polyvertex->GetPointIds()); vtkDataSetMapper * map = vtkDataSetMapper::New(); map->SetInputData(grid); vtkActor * actor = vtkActor::New(); actor->SetMapper(map); actor->GetProperty()->SetColor(0.194, 0.562, 0.75); m_Renderer->AddActor(actor); m_Renderer->SetBackground(0, 0, 0); m_Renderer->ResetCamera(); m_RenderWindow->Render(); map->Delete(); grid->Delete(); actor->Delete(); points->Delete(); polyvertex->Delete();*/ } void CVtkViewer::PreSubclassWindow() { // TODO: Add your specialized code here and/or call the base class CRect rect; GetClientRect(rect); m_Renderer = vtkSmartPointer<vtkRenderer>::New(); m_RenderWindow = vtkSmartPointer<vtkRenderWindow>::New(); m_RenderWindow->SetParentId(this->m_hWnd); m_RenderWindow->SetSize(rect.Width(), rect.Height()); m_RenderWindow->AddRenderer(m_Renderer); if (m_RenderWindow->GetInteractor() == NULL) { vtkSmartPointer<vtkRenderWindowInteractor> RenderWindowInteractor = vtkSmartPointer<vtkRenderWindowInteractor>::New(); RenderWindowInteractor->SetRenderWindow(m_RenderWindow); RenderWindowInteractor->Initialize(); } m_RenderWindow->Start(); CStatic::PreSubclassWindow(); } void CVtkViewer::SetImageData(vtkSmartPointer<vtkImageData> ImageData) { if (ImageData == NULL) return; m_ImageData = ImageData; SetupReslice(); } void CVtkViewer::SetupReslice() { if (m_ImageData == NULL) return; int dims[3]; m_ImageData->GetDimensions(dims); ////////////////////////////////////////////////////////////////////////// m_ImagePlaneWidget->SetInputData(m_ImageData); m_ImagePlaneWidget->SetPlaneOrientation(m_Direction); m_ImagePlaneWidget->SetSliceIndex(dims[m_Direction] / 2); m_ImagePlaneWidget->On(); m_ImagePlaneWidget->InteractionOn(); ////////////////////////////////////////////////////////////////////////// m_ResliceCursor->SetCenter(m_ImageData->GetCenter()); m_ResliceCursor->SetImage(m_ImageData); m_ResliceCursor->SetThickMode(0); //m_ResliceCursorRep->GetResliceCursorActor()->GetCursorAlgorithm()->SetResliceCursor(m_ResliceCursor); //m_ResliceCursorRep->GetResliceCursorActor()->GetCursorAlgorithm()->SetReslicePlaneNormal(m_Direction); m_ResliceCursorWidget->SetEnabled(1); m_Renderer->ResetCamera(); ////////////////////////////////////////////////////////////////////////// double range[2]; m_ImageData->GetScalarRange(range); m_ResliceCursorWidget->GetResliceCursorRepresentation()-> SetWindowLevel(range[1] - range[0], (range[0] + range[1]) / 2.0); m_ImagePlaneWidget->SetWindowLevel(range[1] - range[0], (range[0] + range[1]) / 2.0); } void CVtkViewer::MoveWindow(CRect rect){ m_RenderWindow->SetSize(rect.Width(), rect.Height()); CStatic::MoveWindow(rect); } BEGIN_MESSAGE_MAP(CVtkViewer, CStatic) END_MESSAGE_MAP()
在对话框类中使用
首先拖几个Picture Control进来,然后修改控件ID为IDC_VTK1,这里小心IDC_STATIC是不能接收消息的,必须修改成其它的,然后右键添加环境变量为CStatic,然后将其改为CVtkViewer类型
CVtkViewer m_vtk1; CVtkViewer m_vtk2; CVtkViewer m_vtk3; CVtkViewer m_vtk4;
这时候你可以编译下,会发现这些Picture Control直接变成了黑色,这就说明VTK窗口工作了。
然后就是添加电源数据显示了,在对话框窗口的OnPaint函数中进行绘制,使用方法非常简单,就像下面这样
m_vtk2.ReadPointCloud(pointclouds[1]); m_vtk2.MoveWindow(rect); m_vtk2.ShowWindow(SW_SHOW);
这是博主目前一个软件的显示效果图:
好了,最后还是非常感谢开头链接给出的参考代码。
您好,用您这个方法显示的时候出现了一个很奇怪的问题,单个的txt点云数据显示没有问题,但是我把两个Txt文件内的数据按顺序存放到数组里面,最后显示出来的点云图像会分成独立的两块,请问这可能是什么原因造成的呢?
如果只是单个点,直接粘贴应该没有问题,如果带有其它信息就不一样了。
比如点云pcd文件是有格式的,有些情况点云的顺序表征了结构,又比如有的点云按照三角化时的顺序排列。
感谢您的回信,我提出的问题,txt文件中就只有点云坐标, 而且使用matlab显示出来也很正常,所以这个问题很奇怪。 现在是这样,我分多次扫描出来的几个点云坐标数据存放到txt文件中,例如point0.txt到point5.txt, 那我想要只显示其中的几个点云, 例如要同时在同一个picture control控件中显示point2.txt和point4.txt中的点云信息, 有什么方法可以做到呢? 现在看起来txt直接拼接存在一点问题,只是还没有搞清楚原因。
向您请教一个问题, 为什么我开了线程后,程序跑到m_RenderWindow->Render后会出错呢?wglMakeCurrent failed in MakeCurrent error, 请求资源在使用中。 出错0xC0000005