很高兴在雪易的CSDN遇见你
VTK技术爱好者 QQ:870202403 公众号:VTK忠粉
前言
本文分享基于VTK的扩展ACVD实现网格重新划分网格Remeshing,希望对各位小伙伴有所帮助!
感谢各位小伙伴的点赞+关注,小易会继续努力分享,一起进步!
你的点赞就是我的动力(^U^)ノ~YO
目录
1. 重新划分网格Remeshing的效果
在3D图形处理时,通常需要进行网格的重新划分。医学图形处理时通常使用VTK作为图形处理和渲染显示的库。
分享过关于CGAL实现Remeshing的方法,详情见:
【CGAL系列】Remesh—1 Isotropic_remeshing_example_cgal remeshing-CSDN博客
这里,我们分享基于VTK如何进行网格重新划分Remeshing。
2. 基于VTK实现的ACVD
网站链接:ACVD | Sébastien Valette (insa-lyon.fr)
源码:ACVD
ACVD库基于VTK进行扩展,为三角形网格提供一种快速高效的网格简化/重采样方法。可以应用于非常复杂的网格(数百万个顶点)。这种方法基于变分框架内的输入网格单元的聚类。这种聚类方法类似于质心的分区Voronoi区域,其中每个区域具有相同的大小,因此表面的采样非常均匀,并且对生成的三角进行测量具有良好的纵横比。
这种方法对于重新划分网格程序也很有用。
基于斯坦福兔子的聚类方法
3. Remeshing源码分享
.h文件
#pragma once
#include <qobject.h>
#include <vtkPolyData.h>
class zxExampleRemeshing :
public QObject
{
public:
zxExampleRemeshing(QWidget* parent = nullptr);
bool ValidateSurface(vtkPolyData* surface);
vtkPolyData* Remesh(vtkPolyData* surface,
int numVertices,
double gradation,
int subsampling,
double edgeSplitting,
int optimizationLevel,
bool forceManifold,
bool boundaryFixing);
void Remeshing();
};
.cpp文件
#include "zxExampleRemeshing.h"
#include "vtkSTLReader.h"
#include "vtkSTLWriter.h"
#include "vtkTriangleFilter.h"
#include "vtkCellData.h"
#include "vtkPointData.h"
#include <vtkIdList.h>
#include <vtkIntArray.h>
#include <vtkPolyDataNormals.h>
#include <array>
#include <vector>
#include "vtkSurface.h"
#include "vtkIsotropicDiscreteRemeshing.h"
#include "vtkIsotropicMetricForClustering.h"
#include "vtkQuadricTools.h"
struct ClustersQuadrics final
{
explicit ClustersQuadrics(size_t size)
: Elements(size),
Size(size)
{
for (auto& array : Elements)
array.fill(0.0);
}
~ClustersQuadrics() = default;
ClustersQuadrics(const ClustersQuadrics&) = delete;
ClustersQuadrics& operator=(const ClustersQuadrics&) = delete;
std::vector<std::array<double, 9>> Elements;
size_t Size;
};
zxExampleRemeshing::zxExampleRemeshing(QWidget* parent)
{
}
bool zxExampleRemeshing::ValidateSurface(vtkPolyData* surface)
{
if (nullptr == surface) return false;
if (surface->GetNumberOfPoints() < 1) return false;
vtkTriangleFilter* tri = vtkTriangleFilter::New();
tri->SetInputData(surface);
tri->Update();
surface->DeepCopy(tri->GetOutput());
if (surface->GetNumberOfPolys() < 1) return false;
return true;
}
vtkPolyData* zxExampleRemeshing::Remesh(vtkPolyData* surface,
int numVertices,
double gradation,
int subsampling,
double edgeSplitting,
int optimizationLevel,
bool forceManifold,
bool boundaryFixing)
{
std::cout << "input data: " << surface << std::endl;
//if (!ValidateSurface(surface)) return nullptr;
std::cout << "input data validate: " << surface << std::endl;
std::cout << "Start remeshing...";
vtkCellArray* VerticesCells = surface->GetVerts();
vtkSurface* mesh = vtkSurface::New();
mesh->CreateFromPolyData(surface);
mesh->GetCellData()->Initialize();
mesh->GetPointData()->Initialize();
mesh->DisplayMeshProperties();
if (numVertices == 0)
{
numVertices = surface->GetNumberOfPoints();
}
if (edgeSplitting != 0.0)
mesh->SplitLongEdges(edgeSplitting);
std::cout << " **************************1111111111111*****************************************" << std::endl;
vtkIsotropicDiscreteRemeshing* remesher = vtkIsotropicDiscreteRemeshing::New();
remesher->GetMetric()->SetGradation(gradation);
remesher->SetBoundaryFixing(boundaryFixing);
remesher->SetConsoleOutput(1);
remesher->SetForceManifold(forceManifold);
remesher->SetInput(mesh);
remesher->SetNumberOfClusters(numVertices);
remesher->SetSubsamplingThreshold(subsampling);
remesher->Remesh();
std::cout << " **************************222222222222*****************************************" << std::endl;
// Optimization: Minimize distance between input surface and remeshed surface
if (optimizationLevel != 0)
{
ClustersQuadrics clustersQuadrics(numVertices);
auto faceList = vtkSmartPointer<vtkIdList>::New();
vtkSmartPointer<vtkIntArray> clustering = remesher->GetClustering();
vtkSmartPointer<vtkSurface> remesherInput = remesher->GetInput();
int clusteringType = remesher->GetClusteringType();
int numItems = remesher->GetNumberOfItems();
int numMisclassifiedItems = 0;
for (int i = 0; i < numItems; ++i)
{
int cluster = clustering->GetValue(i);
if (cluster >= 0 && cluster < numVertices)
{
if (clusteringType != 0)
{
remesherInput->GetVertexNeighbourFaces(i, faceList);
int numIds = static_cast<int>(faceList->GetNumberOfIds());
for (int j = 0; j < numIds; ++j)
vtkQuadricTools::AddTriangleQuadric(clustersQuadrics.Elements[cluster].data(), remesherInput, faceList->GetId(j), false);
}
else
{
vtkQuadricTools::AddTriangleQuadric(clustersQuadrics.Elements[cluster].data(), remesherInput, i, false);
}
}
else
{
++numMisclassifiedItems;
}
}
std::cout << " **************************333333333333333*****************************************" << std::endl;
if (numMisclassifiedItems != 0)
std::cout << numMisclassifiedItems << " items with wrong cluster association" << std::endl;
vtkSmartPointer<vtkSurface> remesherOutput = remesher->GetOutput();
double point[3];
for (int i = 0; i < numVertices; ++i)
{
remesherOutput->GetPoint(i, point);
vtkQuadricTools::ComputeRepresentativePoint(clustersQuadrics.Elements[i].data(), point, optimizationLevel);
remesherOutput->SetPointCoordinates(i, point);
}
std::cout << "After quadrics post-processing:" << std::endl;
remesherOutput->DisplayMeshProperties();
}
std::cout << " **************************444444444444*****************************************" << std::endl;
auto normals = vtkSmartPointer<vtkPolyDataNormals>::New();
normals->SetInputData(remesher->GetOutput());
normals->AutoOrientNormalsOn();
normals->ComputeCellNormalsOff();
normals->ComputePointNormalsOn();
normals->ConsistencyOff();
normals->FlipNormalsOff();
normals->NonManifoldTraversalOff();
normals->SplittingOff();
normals->Update();
auto remeshedSurface = vtkPolyData::New();
remeshedSurface->DeepCopy(normals->GetOutput());
std::cout << "Finished remeshing";
return remeshedSurface;
}
void zxExampleRemeshing::Remeshing()
{
vtkNew< vtkSTLReader > reader;
reader->SetFileName("D:\\Data\\Remeshing\\implant.stl");
reader->Update();
vtkPolyData* polyData = vtkPolyData::New();
polyData->DeepCopy(reader->GetOutput());
std::cout << "Points: " << polyData->GetNumberOfPoints() << std::endl;
/*vtkPolyData* remeshPD = Remesh(polyData, numVertices,
gradation,
subsampling,
0.0,
1.0,
false,
boundingFixing);*/
vtkSurface* mesh = vtkSurface::New();
mesh->CreateFromPolyData(polyData);
mesh->GetCellData()->Initialize();
mesh->GetPointData()->Initialize();
mesh->DisplayMeshProperties();
int density = 50;
int numVertices = std::max(100, static_cast<int>(polyData->GetNumberOfPoints() * density * 0.01));
QString remeshingType = "Adaptive"; // Adaptive or Regular
double gradation = remeshingType == QStringLiteral("Adaptive") ? 1.0 : 0.0;
const QString quality = "Medium (fast)";
int subsampling;
if (QStringLiteral("High (slow)") == quality)
{
subsampling = 50;
}
else if (QStringLiteral("Maximum (very slow)") == quality)
{
subsampling = 500;
}
else // The default is "Medium (fast)".
{
subsampling = 10;
}
bool boundingFixing = true;
double edgeSplitting = 0.;
double optimizationLevel = 1.0;
if (numVertices == 0)
{
numVertices = polyData->GetNumberOfPoints();
}
if (edgeSplitting != 0.0)
mesh->SplitLongEdges(edgeSplitting);
std::cout << " **************************1111111111111*****************************************" << std::endl;
vtkIsotropicDiscreteRemeshing* remesher = vtkIsotropicDiscreteRemeshing::New();
remesher->GetMetric()->SetGradation(gradation);
remesher->SetBoundaryFixing(boundingFixing);
remesher->SetConsoleOutput(1);
remesher->SetForceManifold(false);
remesher->SetInput(mesh);
remesher->SetNumberOfClusters(numVertices);
remesher->SetSubsamplingThreshold(subsampling);
remesher->Remesh();
std::cout << " **************************222222222222*****************************************" << std::endl;
// Optimization: Minimize distance between input surface and remeshed surface
if (optimizationLevel != 0)
{
ClustersQuadrics clustersQuadrics(numVertices);
auto faceList = vtkSmartPointer<vtkIdList>::New();
vtkSmartPointer<vtkIntArray> clustering = remesher->GetClustering();
vtkSmartPointer<vtkSurface> remesherInput = remesher->GetInput();
int clusteringType = remesher->GetClusteringType();
int numItems = remesher->GetNumberOfItems();
int numMisclassifiedItems = 0;
for (int i = 0; i < numItems; ++i)
{
int cluster = clustering->GetValue(i);
if (cluster >= 0 && cluster < numVertices)
{
if (clusteringType != 0)
{
remesherInput->GetVertexNeighbourFaces(i, faceList);
int numIds = static_cast<int>(faceList->GetNumberOfIds());
for (int j = 0; j < numIds; ++j)
vtkQuadricTools::AddTriangleQuadric(clustersQuadrics.Elements[cluster].data(), remesherInput, faceList->GetId(j), false);
}
else
{
vtkQuadricTools::AddTriangleQuadric(clustersQuadrics.Elements[cluster].data(), remesherInput, i, false);
}
}
else
{
++numMisclassifiedItems;
}
}
std::cout << " **************************333333333333333*****************************************" << std::endl;
if (numMisclassifiedItems != 0)
std::cout << numMisclassifiedItems << " items with wrong cluster association" << std::endl;
vtkSmartPointer<vtkSurface> remesherOutput = remesher->GetOutput();
double point[3];
for (int i = 0; i < numVertices; ++i)
{
remesherOutput->GetPoint(i, point);
vtkQuadricTools::ComputeRepresentativePoint(clustersQuadrics.Elements[i].data(), point, optimizationLevel);
remesherOutput->SetPointCoordinates(i, point);
}
std::cout << "After quadrics post-processing:" << std::endl;
remesherOutput->DisplayMeshProperties();
}
std::cout << " **************************444444444444*****************************************" << std::endl;
auto normals = vtkSmartPointer<vtkPolyDataNormals>::New();
normals->SetInputData(remesher->GetOutput());
normals->AutoOrientNormalsOn();
normals->ComputeCellNormalsOff();
normals->ComputePointNormalsOn();
normals->ConsistencyOff();
normals->FlipNormalsOff();
normals->NonManifoldTraversalOff();
normals->SplittingOff();
normals->Update();
auto remeshedSurface = vtkPolyData::New();
remeshedSurface->DeepCopy(normals->GetOutput());
std::cout << "Finished remeshing";
std::cout << "remeshPD: " << remeshedSurface <<std::endl;
vtkNew< vtkSTLWriter > writer;
writer->SetFileName("D:/Data/Remeshing/implantRemeshCode2.stl");
writer->SetInputData(remeshedSurface);
writer->Update();
}
结论:
感谢各位小伙伴的点赞+关注,小易会继续努力分享,一起进步!