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.****.net/wishchin/article/details/45887561
http://blog.****.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有关,目前还没有查出来,如果你解决了这个问题,非常希望你能留言告知,这是这个类中(目前发现的)唯一的瑕疵,博主将非常感激。
好了,粘代码吧,首先是头文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
|
#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文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
|
#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类型
1
2
3
4
|
CVtkViewer m_vtk1;
CVtkViewer m_vtk2;
CVtkViewer m_vtk3;
CVtkViewer m_vtk4;
|
这时候你可以编译下,会发现这些Picture Control直接变成了黑色,这就说明VTK窗口工作了。
然后就是添加电源数据显示了,在对话框窗口的OnPaint函数中进行绘制,使用方法非常简单,就像下面这样
1
2
3
|
m_vtk2.ReadPointCloud(pointclouds[1]);
m_vtk2.MoveWindow(rect);
m_vtk2.ShowWindow(SW_SHOW);
|
这是博主目前一个软件的显示效果图:
好了,最后还是非常感谢开头链接给出的参考代码。