VTK 数据类型:vtkPolyData
VTK 数据类型:vtkPolyData
vtkPolyData数据是一种广泛使用的 VTK 数据结构,可以用来表示很多常用的数据结构,如点云数据、面片模型等。本文章先分析vtkPolyData数据的基本组成,创建方法和显示管线,结尾介绍了一些基本操作,如距离、面积、包围盒、法向量以及符号化等。这些都是高级图像图像处理,此外还着中分析了图形平滑、封闭性检测、连通性分析、多分辨率处理、表面重建、点云配准、纹理映射等内容。掌握这些内容便可以解决许多实际性的工程问题。
vtkPolyData 数据生成与显示
vtkPolyData数据由几何结构数据、拓扑结构数据和属性数据组成,用于表达多边形数据(它是 VTK 最终渲染支持的两种类型之一,另一种是 Image)。几何结构数据主要是组成模型的点集,拓扑结构数据是点按一定关系组成的单元数据,属性数据与几何结构数据和拓扑结构数据想关联,可以标量、向量、张量,可以用来间接表示图像的颜色。
数据的结构由顶点(Point)和单元(Cell)表示。单元(Cell)分为4种:点、线、三角带(triangle strip)、多边形。
GetNumberOfPoints()和GetNumberOfCells()可以分别获取图形的点数和单元数目:
vtkSmartPointer<vtkConeSource> coneSource = vtkSmartPointer<vtkConeSource>::New();
coneSource->Updata;
vtkSmartPointer<vtkPolyData> cone = coneSource->GetOutput();
int npoints = cone->GetNumberOfPoints();
int nCells = cone->GetNumberOfCells();
vtkPolyData数据显示时需要定义vtkPolyDataMapper对象,用来接受vtkPolyData数据以实现图形数据到渲染图元的转换。
常见的 vtkPolyData 数据源类
VTK 自带很多的成熟的多边形数据,包括圆柱,球体,锥体等。我们仅需设置几个属性就可以使用现成的数据创建模型并显示。
vtkPolyData 数据的创建
需要先定义一个点集和一个单元集合,点集定义了 vtkPolyData 的几何结构,而单元集合定义了 vtkPolyData 的拓扑结构。
每个单元由点的索引而非坐标来定义,这样能够减少数据的存储空间。单元的类型可以是点、三角形、矩形、多边形等基本图形。只有定义了单元数据才能显示该图形数据。
vtkPolyData 属性数据
图形的颜色与vtkPolyData属性数据息息相关,可为点数据和单元数据分别指定属性数据。
点和单元属性数据分别存储在vtkPointData和vtkCellData中,可以通过调用GetCellData()函数获取一个vtkCellData类型单元数据指针,在通过SetScalars()函数设置颜色数据。
由于可以同时设置点和单元设置属性,那么怎么用点和单元来控制颜色呢?这就需要使用vtkPolyDataMapper类的方法:
- SetScalarModeToDefault():默认设置,该设置下首先使用点的标量数据控制颜色,若点标量数据不可用时,则使用单元数据。
- SetScalarModeToUsePointData():使用点的标量数据控制颜色,若点标量数据不可用也不会使用其他数据。
- SetScalarModeToUseCellData():使用单元的标量数据控制颜色,若单元标量数据不可用也不会使用其他数据。
- SetScalarModeToUsePointFieldData()/SetScalarModeToUseCellFieldData():点数据和标量数据都不会用来着色,而是使用属性数据中场数据数组。可以通过名字来指定进行颜色渲染的数据。
在某些情况下,需要对点属性数据和单元属性数据进行转换,这需要用到两个类vtkCellDataToPointData和vtkPointDataToCellData。转换原理是:当由带属性向单元属性转换时,每个单元属性数据为组成该单元的点对应的属性的平均值;当单元属性数据向点属性数据转换时,点属性为使用该点的单元的属性平均值。
基本图形操作
VTK提供了多种基本图形操作:
- vtkLine提供了点与线、线与线间的距离计算;
- vtkTriangle提供了面积、外接圆、法向量的计算,点与三角形位置关系的判断;
- vtkPolygen提供了法向量、重心、面积的计算、点与多边形位置的判断、点与多边形、多边形与多边形相交判断;
- vtkTetra实现了四面体体积计算、重心计算;
- vtkMassProperties实现三角网格的表面积和体积计算,但要求网格必须时封闭的三角网格;
- vtkTriangleFilter可以实现多边形网格向三角网格转换。
测地距离:三维模型上两个点的测地距离是指沿着模型表面两者之间的最短距离,通常采用Dijkstra算法近似求解,VTK中的vtkDijkstraGraphGeodesicPath类可实现测地距离求解。通过GetGeodesicLenght()函数可以获取当前计算的两点测地距离的数值。
包围盒:能够包围模型的最小立方体,常用于模型的碰撞检测中。vtkOutlineFilter提供一个方便的方法来生成包围盒。
法向量计算
三维平面的法向量是指垂直该平面的向量。某点的法向量为垂直该点切平面的法向量。
在计算网格法向量时,单元法向量可以通过组成每个单元的任意两条边的叉乘向量归并化来表示;二点的法向量则是由使用该点的单元单元法向量的平均值表示。
VTK中计算法向量的类为vtkPolyDataNormals,可以通过SetComputeCellNormals()和SetComputePointNormals()来设置需要计算的法向量类型,默认计算点法向量。
计算单元法向量时,要保持单元法向量一致才能得到合理的法向量。SetConsistency()可以自动调整单元点的顺序;SetAutoOrientNormals()可以自动调整法向量方向。
类vtkPolyDataNormals自动开启对锐边缘处理,如果检测到锐边缘,会将其分裂,使图形更加平滑,可通过SetSplitting()函数关闭该功能。
符号化(Glyphing)
通过符号化(Glyphing)技术将法向量图形化显示。
VTK中使用vtkGlyph3D类实现该功能,并支持图形缩放、着色、设置空间姿态等,需要接受两个输入:几何数据点集合、Glyph图形数据(vtkPolyData数据)。几何数据点集合来自求完法向量的图像,Glyph图形数据用于在点集合处显示法向量。
曲率计算
曲率是曲面弯曲程度的一种度量,是几何体的一种重要局部特征。计算曲面M点曲率,考虑经过M的法线的一个平面与曲面相交,可得到一条二维曲线,经过M的法线的平面可以有很多,与曲面相交时可得到多条曲线,取曲率最大和最小的曲线,若其对应曲率为k1和k2,称为主曲率,而该点的高斯曲率为k1*k2,平均曲率为:(k1+k2)/2。
VTK中vtkCurvatures类实现了4种网格模型曲率计算方法:
- SetCurvatureTypeToMaximum():计算最大主曲率;
- SetCurvatureTypeToMinimum():计算最小主曲率;
- SetCurvatureTypeToGaussian():计算高斯曲率;
- SetCurvatureTypeToMean():计算平均曲率。
网格平滑
vtkSmoothPolyDataFilter类,实现了拉普拉斯平滑算法(一种网格平滑算法,将每个点用其邻域点的中心来代替,通过不断迭代,得到较为光滑的网格),用SetNumberOfIterations()控制平滑次数,次数越大,平滑越厉害。
vtkSmoothPolyDataFilter类通过拉普拉斯不断迭代,模型会不断向网格中心收缩。该类中还有很多变量来控制平滑过程:
- BoundarySmoothing控制是否对边界点平滑;
- FeatureEdgeSmoothing控制是否对特征边上的点平滑,通过调用SetFeatureAngle()函数设置特征角阈值。
vtkWindowedSincPolyDataFilter类,使用窗口函数Sinc实现网格平滑,能够最下程度避免收缩,使用方法与vtkSmoothPolyDataFilter类相同。
封闭性检测与填补漏洞
如果一条边只被一个多边形包含,那这条边就是边界边,是否存在边界边是检测网格模型是否封闭的重要特征。
vtkFeatureEdges类能够提取多边形网格模型中4种类型的边:
- 边界边;
- 非流形边;
- 特征边;
- 流形边;
可以通过判断边界边的数目来网格是否封闭。检测出网格是否封闭之后可以通过类vtkFillHoleFilter将漏洞填补起来。
连通区域分析
vtkAppendPolyData类可以实现vtkPolyData的合并,使用该类可以方便地构造含有多个连通区域的数据。
vtkPolyDataConnectivityFilter类用于实现连通区域分析,它有以下典型函数:
- SetExtractionModeToLargestRegion():用于提取具有最多点的连通区域;
- SetExtractionModeToAllRegion():用于连通区域标记,配合函数ColorRegionsOn()一起使用;
- SetExtractionModeToSpecifiedRegion():用于提取一个或多个连通区域,需要通过AddSpecifiedRegion()来添加需要提取的边界号。
实例1:8 个点表示 vtkPolyData
#include "VTKPolyData.h"
#include <vtkPoints.h>
#include <vtkCellArray.h>
#include <vtkPolyDataMapper.h>
#include <vtkActor.h>
#include <vtkRenderer.h>
#include <vtkRenderWindow.h>
#include <vtkShrinkPolyData.h>
VTKPolyData::VTKPolyData(QWidget *parent)
: QMainWindow(parent)
{
ui.setupUi(this);
_pVTKWidget = new QVTKOpenGLNativeWidget();
this->setCentralWidget(_pVTKWidget);
// this->showMaximized();
// 1. generate data
// vtkSmartPointer<vtkConeSource> cone = vtkSmartPointer<vtkConeSource>::New();
vtkNew<vtkPoints> points; // 节点
points->InsertPoint(0, 0, 0, 0);
points->InsertPoint(1, 0, 1, 0);
points->InsertPoint(2, 1, 0, 0);
points->InsertPoint(3, 1, 1, 0);
points->InsertPoint(4, 2, 0, 0);
points->InsertPoint(5, 2, 1, 0);
points->InsertPoint(6, 3, 0, 0);
points->InsertPoint(7, 3, 1, 0);
vtkNew<vtkCellArray> strips; // 单元
strips->InsertNextCell(8);
strips->InsertCellPoint(0);
strips->InsertCellPoint(1);
strips->InsertCellPoint(2);
strips->InsertCellPoint(3);
strips->InsertCellPoint(4);
strips->InsertCellPoint(5);
strips->InsertCellPoint(6);
strips->InsertCellPoint(7);
vtkNew<vtkPolyData> poly;
poly->SetVerts(strips);
// poly->SetLines(strips);
// poly->SetStrips(strips);
// 2. filter
// 3. mapper
vtkSmartPointer<vtkPolyDataMapper> mapper = vtkSmartPointer<vtkPolyDataMapper>::New();
// 4. actor
vtkSmartPointer<vtkActor> actor = vtkSmartPointer<vtkActor>::New();
// 5. renderer
vtkSmartPointer<vtkRenderer> renderer = vtkSmartPointer<vtkRenderer>::New();
renderer->SetBackground(0.3, 0.6, 0.3);
// 6. connect
mapper->SetInputData(poly);
actor->SetMapper(mapper);
renderer->AddActor(actor);
this->_pVTKWidget->renderWindow()->AddRenderer(renderer);
this->_pVTKWidget->renderWindow()->Render();
}
VTKPolyData::~VTKPolyData()
{}
poly->SetVerts(strips);
设置单元类型为点,效果如下所示:
poly->SetLines(strips);
设置单元类型为线,效果如下所示:
poly->SetStrips(strips);
设置单元类型为三角形带,效果如下所示:
按下W进入线框模式,可以看出三角形带的边如何构成:
实例2:立方体
本程序中,数组 pts 表示立方体8个顶点的坐标,数组 ordering 表示顶点和面的对应关系。
程序先把点的坐标输入points数组,同时设置了scalars作为颜色;再把ordering输入polys单元数组,cube->SetPoints(points);
设置cube的点数据,cube->SetPolys(polys);
设置cube的多面体数据,cube->GetPointData()->SetScalars(scalars);
设置了cube的颜色,最后把cube交给mapper,渲染显示。
#include "VTKPolyData.h"
#include <vtkPoints.h>
#include <vtkPointData.h>
#include <vtkCellArray.h>
#include <vtkFloatArray.h>
#include <vtkPolyDataMapper.h>
#include <vtkActor.h>
#include <vtkRenderer.h>
#include <vtkRenderWindow.h>
#include <vtkShrinkPolyData.h>
VTKPolyData::VTKPolyData(QWidget* parent)
: QMainWindow(parent)
{
ui.setupUi(this);
_pVTKWidget = new QVTKOpenGLNativeWidget();
this->setCentralWidget(_pVTKWidget);
// this->showMaximized();
// 1. generate data
std::array<std::array<double, 3>, 8> pts = {
{
{{0,0,0}},
{{1,0,0}},
{{1,1,0}},
{{0,1,0}},
{{0,0,1}},
{{1,0,1}},
{{1,1,1}},
{{0,1,1}}
}
};
std::array<std::array<vtkIdType, 4>, 6> ordering = {
{
{{0,1,2,3}},
{{4,5,6,7}},
{{0,1,5,4}},
{{1,2,6,5}},
{{2,3,7,6}},
{{3,0,4,7}}
}
};
vtkNew<vtkPoints> points;
vtkNew<vtkCellArray> polys;
vtkNew<vtkFloatArray> scalars;
vtkNew<vtkPolyData> cube;
for (size_t i = 0; i < pts.size(); i++)
{
points->InsertPoint(i, pts[i].data());
scalars->InsertTuple1(i, i);
}
for (auto&& i : ordering)
polys->InsertNextCell(vtkIdType(i.size()), i.data());
cube->SetPoints(points);
cube->SetPolys(polys);
cube->GetPointData()->SetScalars(scalars);
// 2. filter
// 3. mapper
vtkSmartPointer<vtkPolyDataMapper> mapper = vtkSmartPointer<vtkPolyDataMapper>::New();
// 4. actor
vtkSmartPointer<vtkActor> actor = vtkSmartPointer<vtkActor>::New();
// 5. renderer
vtkSmartPointer<vtkRenderer> renderer = vtkSmartPointer<vtkRenderer>::New();
renderer->SetBackground(0.3, 0.6, 0.3);
// 6. connect
mapper->SetInputData(cube);
mapper->SetScalarRange(cube->GetScalarRange());
actor->SetMapper(mapper);
renderer->AddActor(actor);
this->_pVTKWidget->renderWindow()->AddRenderer(renderer);
this->_pVTKWidget->renderWindow()->Render();
}
VTKPolyData::~VTKPolyData()
{}
通过
mapper->SetScalarRange(cube->GetScalarRange());
设置mapper的ScalarRange与cube保持一致,可以让显示效果更好。
运行结果:
参考
- https://zhuanlan.zhihu.com/p/336743251
- https://blog.csdn.net/Littlehero_121/article/details/128630253
- https://blog.csdn.net/qq_35769071/article/details/122671756