MFC中嵌入VTK显示点云数据

最近折腾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);

这是博主目前一个软件的显示效果图:

好了,最后还是非常感谢开头链接给出的参考代码。

《MFC中嵌入VTK显示点云数据》有4条评论

  1. 您好,用您这个方法显示的时候出现了一个很奇怪的问题,单个的txt点云数据显示没有问题,但是我把两个Txt文件内的数据按顺序存放到数组里面,最后显示出来的点云图像会分成独立的两块,请问这可能是什么原因造成的呢?

    回复
    • 如果只是单个点,直接粘贴应该没有问题,如果带有其它信息就不一样了。
      比如点云pcd文件是有格式的,有些情况点云的顺序表征了结构,又比如有的点云按照三角化时的顺序排列。

      回复
      • 感谢您的回信,我提出的问题,txt文件中就只有点云坐标, 而且使用matlab显示出来也很正常,所以这个问题很奇怪。 现在是这样,我分多次扫描出来的几个点云坐标数据存放到txt文件中,例如point0.txt到point5.txt, 那我想要只显示其中的几个点云, 例如要同时在同一个picture control控件中显示point2.txt和point4.txt中的点云信息, 有什么方法可以做到呢? 现在看起来txt直接拼接存在一点问题,只是还没有搞清楚原因。

        回复
  2. 向您请教一个问题, 为什么我开了线程后,程序跑到m_RenderWindow->Render后会出错呢?wglMakeCurrent failed in MakeCurrent error, 请求资源在使用中。 出错0xC0000005

    回复

发表评论