vtk内两种PolyData转voxel的方法
【VTK】vtkPolyData数据组成及属性修改
vtkPolyData数据处理
vtkPolyData
是VTK(Visualization Toolkit)
中一个用于表示几何形状和拓扑结构的非常强大和灵活的类。它是一种用于存储点、线、多边形等几何数据的数据结构,特别适用于表示复杂的3D模型和进行科学计算可视化。vtkPolyData
通过组合不同类型的数据集合(如点、线、面)来构建更复杂的3D图形和表面。此外,它还可以存储与这些几何元素相关联的属性数据,这些属性数据对于数据分析和可视化非常重要。
vtkPolyData主要属性
标量(Scalars): 标量数据通常用于表示每个几何元素或点的单个值,如温度、压力等。在可视化过程中,标量数据经常用于根据值的不同对模型进行着色。
向量(Vectors): 向量数据为每个元素提供了一个方向和大小,常用于表示流场中的速度或力的方向和强度。
法线(Normals): 法线是与几何表面垂直的向量,用于光照计算和平滑着色,以增强视觉效果。
纹理坐标(Texture Coordinates): 纹理坐标用于将2D纹理映射到3D模型上。每个点的纹理坐标决定了纹理图像上的对应位置。
颜色(Colors): 颜色数据可以为每个点或单元指定颜色,用于直接控制模型的可视化颜色。
GetNumberOfPoints()
和GetNumberOfCells()
可以分别获取图形的点数和单元数目;vtkPolyData
数据显示时需要定义vtkPolyDataMapper
对象,用来接受vtkPolyData
数据以实现图形数据到渲染图元的转换。图形的颜色与
vtkPolyData
属性数据息息相关,可为点数据和单元数据分别指定属性数据。点和单元属性数据分别存储咋
vtkPointData
和vtkCellData
中,可以通过调用GetCellData()
函数获取一个vtkCellData
类型单元数据指针,在通过SetScalars()函数设置颜色数据。由于可以同时设置点和单元设置属性,我们也可以使用
vtkPolyDataMapper
类的方法来修改颜色SetScalarModeToDefault()
,默认设置,该设置下首先使用点的标量数据控制颜色,若点标量数据不可用时,则使用单元数据。SetScalarModeToUsePointData()
,使用点的标量数据控制颜色,若点标量数据不可用也不会使用其他数据。SetScalarModeToUseCellData()
,使用单元的标量数据控制颜色,若单元标量数据不可用也不会使用其他数据。SetScalarModeToUsePointFieldData()
/SetScalarModeToUseCellFieldData()
,点数据和标量数据都不会用来着色,而是使用属性数据中场数据数组。可以通过名字来指定进行颜色渲染的数据。
在某些情况下,需要对点属性数据和单元属性数据进行转换,这需要用到两个类
vtkCellDataToPointData
和vtkPointDataToCellData
。
转换原理是:当由带属性向单元属性转换时,每个单元属性数据为组成该单元的点对应的属性的平均值;当单元属性数据向点属性数据转换时,点属性为使用该点的单元的属性平均值。
var renderer = renderWindowControl.RenderWindow.GetRenderers().GetFirstRenderer();
// 创建vtkPolyData对象
vtkPolyData polyData = vtkPolyData.New();
// 添加点
var points = vtkPoints.New();
points.InsertNextPoint(0.0, 1.0, 0.0); // 点1
points.InsertNextPoint(0.87, 0.5, 0.0); // 点2
points.InsertNextPoint(0.87, -0.5, 0.0); // 点3
points.InsertNextPoint(0.0, -1.0, 0.0); // 点4
points.InsertNextPoint(-0.87, -0.5, 0.0); // 点5
points.InsertNextPoint(-0.87, 0.5, 0.0); // 点6
var hexagon = vtkCellArray.New();
hexagon.InsertNextCell(6); // 六个顶点的六边形
for (int i = 0; i < 6; i++)
{
hexagon.InsertCellPoint(i);
}
polyData.SetPoints(points);
// 将多边形添加到polyData
polyData.SetPolys(hexagon);
var colors = new vtkUnsignedCharArray();
colors.SetNumberOfComponents(3); // RGB
colors.SetName("Colors");
colors.InsertNextTuple3(255, 0, 0); // 为点设置红色
colors.InsertNextTuple3(0, 255, 0); // 为点设置红色
colors.InsertNextTuple3(0, 0, 255); // 为点设置红色
polyData.GetPointData().SetScalars(colors); // 添加颜色到点数据
当你为
vtkPolyData
中的点指定颜色,并且发现创建出来的三角形颜色是渐变的,这是因为VTK在渲染时对颜色进行了插值。在这种情况下,每个顶点的颜色被指定了(比如红、绿、蓝
),而在三角形的内部,VTK会根据这些顶点的颜色进行线性插值来计算内部像素的颜色。
基本图形操作
vtkLine
提供了点与线、线与线间的距离计算;vtkTriangle
提供了面积、外接圆、法向量的计算,点与三角形位置关系的判断;vtkPolygen
提供了法向量、重心、面积的计算、点与多边形位置的判断、点与多边形、多边形与多边形相交判断;vtkTetra
实现了四面体体积计算、重心计算;vtkMassProperties
实现三角网格的表面积和体积计算,但要求网格必须时封闭的三角网格;vtkTriangleFilter
可以实现多边形网格向三角网格转换。- 测地距离: 三维模型上两个点的测地距离是指沿着模型表面两者之间的最短距离,通常采用
Dijkstra
算法近似求解,VTK中的vtkDijkstraGraphGeodesicPath类可实现测地距离求解。通过GetGeodesicLenght()
函数可以获取当前计算的两点测地距离的数值。 - 包围盒: 是指能够包围模型的最小立方体,常用于模型的碰撞检测中。
vtkOutlineFilter
提供一个方便的方法来生成包围盒。
GetGeodesicLength
方法的示例:
// 两点之间测距
var sphereSource = new vtkSphereSource();
sphereSource.SetRadius(5.0); // 设置球体的半径
sphereSource.SetThetaResolution(50); // 设置球体的分辨率
sphereSource.SetPhiResolution(50);
sphereSource.Update();
var dijkstra = new vtkDijkstraGraphGeodesicPath();
dijkstra.SetInputConnection(sphereSource.GetOutputPort());
dijkstra.SetStartVertex(0); // 假设的起点ID
dijkstra.SetEndVertex(10); // 假设的终点ID
dijkstra.Update();
var pathPolyData = dijkstra.GetOutput();
var pathPoints = pathPolyData.GetPoints();
double length = 0.0;
for (int i = 0; i < pathPoints.GetNumberOfPoints() - 1; i++)
{
double[] p1 = pathPoints.GetPoint(i);
double[] p2 = pathPoints.GetPoint(i + 1);
double distance = Math.Sqrt(Math.Pow(p2[0] - p1[0], 2) + Math.Pow(p2[1] - p1[1], 2) + Math.Pow(p2[2] - p1[2], 2));
length += distance;
}
Debug.WriteLine($"Geodesic length: {length}");
法向量计算
- 三维平面的法向量是指垂直该平面的向量。某点的法向量为垂直该点切平面的法向量。
- 在计算网格法向量时,单元法向量可以通过组成每个单元的任意两条边的叉乘向量归并化来表示;二点的法向量则是由使用该点的单元单元法向量的平均值表示。
- VTK 中计算法向量的类为
vtkPolyDataNormals
,可以通过SetComputeCellNormals()
和SetComputePointNormals()
来设置需要计算的法向量类型,默认计算点法向量。 - 计算单元法向量时,要保持单元法向量一致才能得到合理的法向量。
SetConsistency()
可以自动调整单元点的顺序;SetAutoOrientNormals()
可以自动调整法向量方向。 - 类
vtkPolyDataNormals
自动开启对锐边缘处理,如果检测到锐边缘,会将其分裂,使图形更加平滑,可通过SetSplitting()
函数关闭该功能。
曲率计算
曲率时曲面弯曲程度的一种度量,是几何体的一种重要局部特征。计算曲面M点曲率,考虑经过M的法线的一个平面与曲面相交,可得到一条二维曲线,经过M的法线的平面可以有很多,与曲面相交时可得到多条曲线,取曲率最大和最小的曲线,若其对应曲率为k1
和k2
,称为主曲率,而该点的高斯曲率为k1*k2
,平均曲率为:(k1+k2)/2
。
VTK中vtkCurvatures类实现了4种网格模型曲率计算方法:
SetCurvatureTypeToMaximum()
:计算最大主曲率;SetCurvatureTypeToMinimum()
:计算最小主曲率;SetCurvatureTypeToGaussian()
:计算高斯曲率;SetCurvatureTypeToMean()
:计算平均曲率。
网格平滑
拉普拉斯平滑是一种网格平滑算法,将每个点用其邻域点的中心来代替,通过不断迭代,得到较为光滑的网格。vtkSmoothPolyDataFilter
类,实现了拉普拉斯平滑算法,用SetNumberOfIterations()
控制平滑次数,次数越大,平滑越厉害。
vtkSmoothPolyDataFilter
类,实现了拉普拉斯平滑算法,用SetNumberOfIterations()
控制平滑次数,次数越大,平滑越厉害。BoundarySmoothing
控制是否对边界点平滑;FeatureEdgeSmoothing
控制是否对特征边上的点平滑,通过调用SetFeatureAngle()
函数设置特征角阈值。
vtkSmoothPolyDataFilter
类通过拉普拉斯不断迭代,模型会不断向网格中心收缩。vtkWindowedSincPolyDataFilter
类,使用窗口函数Sinc实现网格平滑,能够最下程度避免收缩,使用方法与vtkSmoothPolyDataFilter
类相同。
var smoother = vtkSmoothPolyDataFilter.New();
smoother.SetInputData(polyData);
smoother.SetNumberOfIterations(100); // 平滑迭代次数
smoother.FeatureEdgeSmoothingOff(); // 关闭特征边缘平滑
smoother.BoundarySmoothingOn(); // 开启边界平滑
smoother.SetRelaxationFactor(0.1); // 调整松弛因子
smoother.Update();
// 使用平滑后的数据
var smoothPolyData = smoother.GetOutput();
// 创建映射器并设置polyData
vtkPolyDataMapper mapper = vtkPolyDataMapper.New();
mapper.SetInputData(smoothPolyData);
连通区域分析
vtkAppendPolyData
类可以实现vtkPolyData
的合并,使用该类可以方便地构造含有多个连通区域的数据。
vtkPolyDataConnectivityFilter
类用于实现连通区域分析:
SetExtractionModeToLargestRegion()
:用于提取具有最多点的连通区域;SetExtractionModeToAllRegion()
:用于连通区域标记,配合函数ColorRegionsOn()
一起使用;SetExtractionModeToSpecifiedRegion()
:用于提取一个或多个连通区域,需要通过AddSpecifiedRegion()
来添加需要提取的边界号;
表面重建
利用表面重建技术将点云数据重建为面模型。
三角剖分
三角剖分将一些散乱的点云数据剖分为一系列三角网格,最常用的三角剖分技术是Delaunay三角剖分,主要用于二维三角剖分,三维数据通常使用点云重建。
vtkDelaunay2D
类实现了2维三角剖分。vtkDelaunay3D
类实现了3维三角剖分。得到的结果并非三角网格,而是四面体网格,因此,其输出数据类型为vtkUnstructuredGrid
.
等值面提取
类似与面绘制,通过等值面提取提取技术,仅提取感兴趣的一个或者几个组织轮廓,并生成网格模型以供后续处理和显示。
等值面提取算法多基于Marching Cubes
算法实现:
vtkImageMarchingCubes
主要处理三维图像数据;vtkMarchingCubes
主要针对规则体数据生成等值面;vtkMarchingSquares
针对二维规则网格数据生成等值线。
通常需要搭配图形平滑、抽取等操作来对等值面数据进行后处理。
// vtkImageData 数据结构加载
vtkImageData imageData = reader.GetOutput();
vtkContourFilter contourFilter = vtkContourFilter.New();
contourFilter.SetInputData(imageData);
contourFilter.SetValue(0, -250); // 设置等值面标量值
contourFilter.Update();
// 这俩作用一样
// var marchingCubes = vtkImageMarchingCubes.New();
// marchingCubes.SetInputData(imageData); // vtkImageData是您的体积数据
// marchingCubes.SetValue(0, -250); // isoValue是您选择的等值面值
// marchingCubes.Update();
// 创建映射器并设置其输入为contourFilter的输出
var mapper = vtkPolyDataMapper.New();
mapper.SetInputConnection(contourFilter.GetOutputPort());
// 创建演员并将映射器连接到它
var actor = vtkActor.New();
actor.SetMapper(mapper);
actor.GetProperty().SetColor(1.0, 0.0, 0.0);
renderer.SetBackground(0.1, 0.2, 0.1);
// 将actor添加到渲染器
renderer.AddActor(actor);
vtkImageMarchingCubes
专门设计用于处理vtkImageData
类型的数据, vtkContourFilter
是一种更通用的工具,它可以处理各种类型的数据集,包括vtkImageData
、vtkPolyData
、vtkStructuredGrid
, 用哪个都一样,我感觉我这个电脑配置用哪个不跑异步都卡。
点云配准
点云配准即是对一组原点云数据应用一个空间变换,使得变换后的数据与目标点云数据能够一一映射,使两组数据之间的平均距离误差最小。
vtkLandmarkTransform
类实现了标记点配准算法,使得两个点集在配准后平均距离误差最小。通过SetSourceLandmarks()
和SetTargetLandmarks()
函数分别设置源标记点集和目标标记点集。
SetModeToRigidBody()
:函数用于设置其配准变换的类型为刚体变换;SetModeToAffine()
:设置其配准变换的类型为放射变换;SetModeToSimilarity()
:设置相似变换,即平移、旋转和放缩变换。
vtkVertexGlyphFilter
类显示点集;
vtkTransformPolyDataFilter
用来对面源标记点进行变换来显示配准后的点集。
点云数据配准最经典的方法是迭代最近点算法
(ICP)
:每次迭代中对于源数据点P找到目标点集Q中的最近点,然后基于最小二乘原理求解当前的变换T。通过不断迭代直至收敛,即完成了点集的配准。
vtkIterativeClosesPointTransform
类中内部定义了一个vtkLandmarkTransform
指针,用于就算ICP
迭代中最佳匹配点集,可以通过GetLandmarkTransform()
函数获取,并通过vtkLandmarkTransform
指针设置相应的变换类型。
SetMaximumNumberOfIterations()
函数用于设置ICP
算法迭代次数,StartByMatchingCentroidsOn()
函数则用于设置配准之前先计算两个点集重心,并平移源点集使得两者重心重合。配置完毕可以通过GetMatrix()
函数来获取相应的变换矩阵。
var renderer = renderWindowControl.RenderWindow.GetRenderers().GetFirstRenderer();
// 创建第一个点云(源点云)
var sourcePoints = vtkPoints.New();
sourcePoints.InsertNextPoint(0.0, 0.0, 0.0);
sourcePoints.InsertNextPoint(1.0, 0.0, 0.0);
sourcePoints.InsertNextPoint(0.0, 1.0, 0.0);
var sourcePolyData = vtkPolyData.New();
sourcePolyData.SetPoints(sourcePoints);
// 创建第二个点云(目标点云),这里假设它是第一个点云经过某种变换的结果
var targetPoints = vtkPoints.New();
targetPoints.InsertNextPoint(0.5, 0.0, 0.5);
targetPoints.InsertNextPoint(1.5, 0.0, 0.5);
targetPoints.InsertNextPoint(0.5, 1.0, 0.5);
var targetPolyData = vtkPolyData.New();
targetPolyData.SetPoints(targetPoints);
// 应用ICP算法进行配准
var icp = vtkIterativeClosestPointTransform.New();
icp.SetSource(sourcePolyData);
icp.SetTarget(targetPolyData);
icp.GetLandmarkTransform().SetModeToRigidBody(); // 对于仅旋转和平移的变换
icp.SetMaximumNumberOfIterations(20); // 设置迭代次数
icp.StartByMatchingCentroidsOn(); // 通过匹配中心开始
icp.Modified();
icp.Update();
// 将变换应用到源点云
var transformFilter = vtkTransformPolyDataFilter.New();
transformFilter.SetInputData(sourcePolyData); // sourcePolyData是源点云
transformFilter.SetTransform(icp); // icpTransform是ICP变换结果
transformFilter.Update();
// 获取变换后的点云
var registeredPolyData = transformFilter.GetOutput();
// 可视化源点云、目标点云和配准后的点云
var sourceActor = VisualizePointCloud(sourcePolyData, 1.0, 0.0, 0.0); // 红色
var targetActor = VisualizePointCloud(targetPolyData, 0.0, 1.0, 0.0); // 绿色
var registeredActor = VisualizePointCloud(registeredPolyData, 0.0, 0.0, 1.0); // 蓝色
// 可视化(源点云、目标点云和配准后的点云)
var sourceMapper = vtkPolyDataMapper.New();
sourceMapper.SetInputData(sourcePolyData);
renderer.AddActor(sourceActor);
renderer.AddActor(targetActor);
renderer.AddActor(registeredActor);
renderer.SetBackground(0.1, 0.2, 0.4); // 设置背景颜色