VTK随笔十:VTK图形处理(封闭性检测、联通区域分析、多分辨率处理)

一、封闭性检测

如果一条边只被一个多边形包含,那么这条边就是边界边。

是否存在边界边是检测一个网格模型是否封闭的重要特征。

vtkFeatureEdges是一个非常重要的类,该类能够提取多边形网格模型中四种类型的边。

1)边界边。即只被一个多边形或者一条边包含的边。

2)非流形边。被3个或者3个以上的多边形包含的边即为非流形边。

3)特征边。上节中也提到,需要设置一个特征角的阈值,当包含同一条边的两个三角形的法向量的夹角大于该阈值时,即为一个特征边。

4)流形边。只被两个多边形包含的边即为流形边。

可以使用该类来检测是否存在边界边,并依此来判断网格是否封闭。检测出网格是否封闭之后可以通过类vtkFillHoleFilter将漏洞填补起来。

示例代码:

#include <QApplication>
#include <vtkSmartPointer.h>
#include <vtkRenderer.h>
#include <vtkGenericOpenGLRenderWindow.h>
#include <QVTKOpenGLNativeWidget.h>
#include <vtkSphereSource.h>
#include <vtkSelectionNode.h>
#include <vtkPolyDataMapper.h>
#include <vtkProperty.h>
#include <vtkInformation.h>
#include <vtkExtractSelection.h>
#include <vtkPolyDataMapper.h>
#include <vtkDataSetSurfaceFilter.h>
#include <vtkFeatureEdges.h>
#include <vtkFillHolesFilter.h>
#include <vtkPolyDataNormals.h>
#include <vtkCamera.h>
#include <QDebug>

void GenerateData(vtkSmartPointer<vtkPolyData> input)
{
    vtkSmartPointer<vtkSphereSource> sphereSource = vtkSmartPointer<vtkSphereSource>::New();
    sphereSource->Update();

    vtkSmartPointer<vtkIdTypeArray> ids = vtkSmartPointer<vtkIdTypeArray>::New();
    ids->SetNumberOfComponents(1);
    ids->InsertNextValue(2);
    ids->InsertNextValue(10);

    vtkSmartPointer<vtkSelectionNode> selectionNode = vtkSmartPointer<vtkSelectionNode>::New();
    selectionNode->SetFieldType(vtkSelectionNode::CELL);
    selectionNode->SetContentType(vtkSelectionNode::INDICES);
    selectionNode->SetSelectionList(ids);
    selectionNode->GetProperties()->Set(vtkSelectionNode::INVERSE(), 1);

    vtkSmartPointer<vtkSelection> selection = vtkSmartPointer<vtkSelection>::New();
    selection->AddNode(selectionNode);

    vtkSmartPointer<vtkExtractSelection> extractSelection = vtkSmartPointer<vtkExtractSelection>::New();
    extractSelection->SetInputData(0, sphereSource->GetOutput());
    extractSelection->SetInputData(1, selection);
    extractSelection->Update();

    vtkSmartPointer<vtkDataSetSurfaceFilter> surfaceFilter = vtkSmartPointer<vtkDataSetSurfaceFilter>::New();
    surfaceFilter->SetInputConnection(extractSelection->GetOutputPort());
    surfaceFilter->Update();

    input->ShallowCopy(surfaceFilter->GetOutput());
}

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    vtkSmartPointer<vtkPolyData> input = vtkSmartPointer<vtkPolyData>::New();
    GenerateData(input);

    vtkSmartPointer<vtkFeatureEdges> featureEdges = vtkSmartPointer<vtkFeatureEdges>::New();
    featureEdges->SetInputData(input);
    featureEdges->BoundaryEdgesOn();
    featureEdges->FeatureEdgesOff();
    featureEdges->ManifoldEdgesOff();
    featureEdges->NonManifoldEdgesOff();
    featureEdges->Update();

    int numberOfOpenEdges = featureEdges->GetOutput()->GetNumberOfCells();
    if(numberOfOpenEdges)
    {
        qDebug()<<"该网格模型不是封闭的...";
    }
    else
    {
        qDebug()<<"该网格模型是封闭的...";
        return 0;
    }

    vtkSmartPointer<vtkFillHolesFilter> fillHolesFilter = vtkSmartPointer<vtkFillHolesFilter>::New();
    fillHolesFilter->SetInputData(input);
    fillHolesFilter->Update();

    vtkSmartPointer<vtkPolyDataNormals> normals = vtkSmartPointer<vtkPolyDataNormals>::New();
    normals->SetInputConnection(fillHolesFilter->GetOutputPort());
    normals->ConsistencyOn();
    normals->SplittingOff();
    normals->Update();

    double leftViewport[4] = {0.0, 0.0, 0.5, 1.0};
    double rightViewport[4] = {0.5, 0.0, 1.0, 1.0};

    vtkSmartPointer<vtkPolyDataMapper> originalMapper = vtkSmartPointer<vtkPolyDataMapper>::New();
    originalMapper->SetInputData(input);

    vtkSmartPointer<vtkProperty> backfaceProp = vtkSmartPointer<vtkProperty>::New();
    backfaceProp->SetDiffuseColor(0.89,0.81,0.34);

    vtkSmartPointer<vtkActor> originalActor = vtkSmartPointer<vtkActor>::New();
    originalActor->SetMapper(originalMapper);
    originalActor->SetBackfaceProperty(backfaceProp);
    originalActor->GetProperty()->SetDiffuseColor(1.0, 0.3882, 0.2784);

    vtkSmartPointer<vtkPolyDataMapper> edgeMapper = vtkSmartPointer<vtkPolyDataMapper>::New();
    edgeMapper->SetInputData(featureEdges->GetOutput());
    vtkSmartPointer<vtkActor> edgeActor =
        vtkSmartPointer<vtkActor>::New();
    edgeActor->SetMapper(edgeMapper);
    edgeActor->GetProperty()->SetEdgeColor(0.,0.,1.0);
    edgeActor->GetProperty()->SetEdgeVisibility(1);
    edgeActor->GetProperty()->SetLineWidth(5);

    vtkSmartPointer<vtkPolyDataMapper> filledMapper = vtkSmartPointer<vtkPolyDataMapper>::New();
    filledMapper->SetInputData(normals->GetOutput());

    vtkSmartPointer<vtkActor> filledActor = vtkSmartPointer<vtkActor>::New();
    filledActor->SetMapper(filledMapper);
    filledActor->GetProperty()->SetDiffuseColor(1.0, 0.3882, 0.2784);

    vtkSmartPointer<vtkRenderer> leftRenderer = vtkSmartPointer<vtkRenderer>::New();
    leftRenderer->SetViewport(leftViewport);
    leftRenderer->AddActor(originalActor);
    leftRenderer->AddActor(edgeActor);
    leftRenderer->SetBackground(1.0, 1.0, 1.0);

    vtkSmartPointer<vtkRenderer> rightRenderer = vtkSmartPointer<vtkRenderer>::New();
    rightRenderer->SetViewport(rightViewport);
    rightRenderer->AddActor(filledActor);
    rightRenderer->SetBackground(1.0, 1.0, 1.0);

    vtkSmartPointer<vtkGenericOpenGLRenderWindow> renderWindow = vtkSmartPointer<vtkGenericOpenGLRenderWindow>::New();
    renderWindow->AddRenderer(leftRenderer);
    renderWindow->AddRenderer(rightRenderer);

    QVTKOpenGLNativeWidget w;
    w.resize(640, 320);
    w.setRenderWindow(renderWindow);
    w.setWindowTitle("PolyDataClosed");
    w.show();

    leftRenderer->GetActiveCamera()->SetPosition(0, -1, 0);
    leftRenderer->GetActiveCamera()->SetFocalPoint(0, 0, 0);
    leftRenderer->GetActiveCamera()->SetViewUp(0, 0, 1);
    leftRenderer->GetActiveCamera()->Azimuth(30);
    leftRenderer->GetActiveCamera()->Elevation(30);
    leftRenderer->ResetCamera();
    rightRenderer->SetActiveCamera(leftRenderer->GetActiveCamera());

    return a.exec();
}

运行效果:

二、联通区域分析

许多图形数据中,并非只包含一个对象(连通区域)。而在处理这些图形数据时,有时需要对每一个对象单独处理或者让其单独显示。比如利用MarchingCube 方法提取三维图像中的等值面,得到的结果往往是存在多个连通的对象区域,这时就需要对图形数据做连通区域分析,提取每个连通区域并计算其属性信息,以此来得到需要的连通区域。

示例代码:

#include <QApplication>
#include <vtkSmartPointer.h>
#include <vtkRenderer.h>
#include <vtkGenericOpenGLRenderWindow.h>
#include <QVTKOpenGLNativeWidget.h>
#include <vtkSphereSource.h>
#include <vtkConeSource.h>
#include <vtkPolyDataMapper.h>
#include <vtkProperty.h>
#include <vtkAppendPolyData.h>
#include <vtkPolyDataConnectivityFilter.h>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    vtkSmartPointer<vtkSphereSource> sphereSource = vtkSmartPointer<vtkSphereSource>::New();
    sphereSource->SetRadius(10);
    sphereSource->SetThetaResolution(10);
    sphereSource->SetPhiResolution(10);
    sphereSource->Update();

    vtkSmartPointer<vtkConeSource> coneSource = vtkSmartPointer<vtkConeSource>::New();
    coneSource->SetRadius(5);
    coneSource->SetHeight(10);
    coneSource->SetCenter(25,0,0);
    coneSource->Update();

    vtkSmartPointer<vtkAppendPolyData> appendFilter = vtkSmartPointer<vtkAppendPolyData>::New();
    appendFilter->AddInputData(sphereSource->GetOutput());
    appendFilter->AddInputData(coneSource->GetOutput());
    appendFilter->Update();

    vtkSmartPointer<vtkPolyDataConnectivityFilter> connectivityFilter = vtkSmartPointer<vtkPolyDataConnectivityFilter>::New();
    connectivityFilter->SetInputData(appendFilter->GetOutput());
    connectivityFilter->SetExtractionModeToCellSeededRegions();
    connectivityFilter->AddSeed(100);
    connectivityFilter->Update();

    vtkSmartPointer<vtkPolyDataMapper> originalMapper = vtkSmartPointer<vtkPolyDataMapper>::New();
    originalMapper->SetInputConnection(appendFilter->GetOutputPort());
    originalMapper->Update();

    vtkSmartPointer<vtkActor> originalActor = vtkSmartPointer<vtkActor>::New();
    originalActor->SetMapper(originalMapper);

    vtkSmartPointer<vtkPolyDataMapper> extractedMapper = vtkSmartPointer<vtkPolyDataMapper>::New();
    extractedMapper->SetInputConnection(connectivityFilter->GetOutputPort());
    extractedMapper->Update();

    vtkSmartPointer<vtkActor> extractedActor = vtkSmartPointer<vtkActor>::New();
    extractedActor->SetMapper(extractedMapper);

    double leftViewport[4] = {0.0, 0.0, 0.5, 1.0};
    double rightViewport[4] = {0.5, 0.0, 1.0, 1.0};

    vtkSmartPointer<vtkRenderer> leftRenderer = vtkSmartPointer<vtkRenderer>::New();
    leftRenderer->SetViewport(leftViewport);
    leftRenderer->AddActor(originalActor);
    leftRenderer->SetBackground(0.8, 0.8, 0.8);

    vtkSmartPointer<vtkRenderer> rightRenderer = vtkSmartPointer<vtkRenderer>::New();
    rightRenderer->SetViewport(rightViewport);
    rightRenderer->AddActor(extractedActor);
    rightRenderer->SetBackground(1.0, 1.0, 1.0);

    vtkSmartPointer<vtkGenericOpenGLRenderWindow> renderWindow = vtkSmartPointer<vtkGenericOpenGLRenderWindow>::New();
    renderWindow->AddRenderer(leftRenderer);
    renderWindow->AddRenderer(rightRenderer);

    QVTKOpenGLNativeWidget w;
    w.resize(640, 320);
    w.setRenderWindow(renderWindow);
    w.setWindowTitle("PolyDataConnectedCompExtract");
    w.show();

    return a.exec();
}

运行效果:

vtkPolyDataConnectivityFilter类用于实现连通区域分析,它有以下典型函数:

1、SetExtractionModeToLargestRegion():用于提取具有最多点的连通区域。

2、SetExtractionModeToAllRegion():用于连通区域标记,配合函数ColorRegionsOn()一起使用。

3、SetExtractionModeToSpecifiedRegion():用于提取一个或多个连通区域,需要通过AddSpecifiedRegion()来添加需要提取的边界号。

4、SetExtractionModeToClosestPointRegion():该模式下需要使用 SetClosestPoint()函数设置一个空间点坐标,其执行结果为离该点最近的连通区域。

5、SetExtractionModeToPointSeededRegions():该模式下需要使用 AddSeed()函数添加种子点,提取种子点所在的区域。

6、SetExtractionModeToCellSeededRegions():该模式下需要使用 AddSeed()函数添加种子单元,提取种子单元所在的区域。

三、多分辨率处理

模型抽取(Decimation)和细化(Subdivision)是两个相反的操作,是三角形网格模型多分辨率处理中的两个重要操作。使用这两个操作可以在保持模型拓扑结构的同时,得到不同分辨率的网格模型。模型抽取的作用是减少模型数据中的点数据和单元数据,便于模型的后续处理与交互渲染,这类似于图像数据的降采样。而网格细化则是利用一定的细化规则,在给定的初始网格中插入新的点,从而不断细化出新的网格单元,在极限细化情况下,该网格能够收敛于一个光滑曲面。

1、网格抽取

VTK 中主要有三种网格抽取类:vtkDecimatePro、vtkQuadricDecimation 和 vtkQuadricClustering。vtkDecimatePro 是最常用的,是用一种边塌陷的方法来删除点和单元,处理速度比较快,而且可以方便地控制网格抽取的幅度,得到不同级别的模型数据。

示例代码:

#include <QApplication>
#include <vtkSmartPointer.h>
#include <vtkRenderer.h>
#include <vtkGenericOpenGLRenderWindow.h>
#include <QVTKOpenGLNativeWidget.h>
#include <vtkPolyDataReader.h>
#include <vtkDecimatePro.h>
#include <vtkPolyDataMapper.h>
#include <vtkProperty.h>
#include <vtkCamera.h>
#include <QDebug>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    vtkSmartPointer<vtkPolyDataReader> reader = vtkSmartPointer<vtkPolyDataReader>::New();
    reader->SetFileName("D:/data/fran_cut.vtk");
    reader->Update();
    vtkSmartPointer<vtkPolyData> original  =  reader->GetOutput();

    qDebug() << "抽取前:------------" ;
    qDebug() << "模型点数为: " << original->GetNumberOfPoints();
    qDebug() << "模型面数为: " << original->GetNumberOfPolys();

    vtkSmartPointer<vtkDecimatePro> decimate = vtkSmartPointer<vtkDecimatePro>::New();
    decimate->SetInputData(original);
    decimate->SetTargetReduction(.80);
    decimate->Update();

    vtkSmartPointer<vtkPolyData> decimated = decimate->GetOutput();
    qDebug() << "抽取后:" << "------------";
    qDebug() << "模型点数为:" << decimated->GetNumberOfPoints();
    qDebug() << "模型面数为:" << decimated->GetNumberOfPolys();

    vtkSmartPointer<vtkPolyDataMapper> origianlMapper = vtkSmartPointer<vtkPolyDataMapper>::New();
    origianlMapper->SetInputData(original);

    vtkSmartPointer<vtkActor> origianlActor = vtkSmartPointer<vtkActor>::New();
    origianlActor->SetMapper(origianlMapper);

    vtkSmartPointer<vtkPolyDataMapper> decimatedMapper = vtkSmartPointer<vtkPolyDataMapper>::New();
    decimatedMapper->SetInputData(decimated);

    vtkSmartPointer<vtkActor> decimatedActor = vtkSmartPointer<vtkActor>::New();
    decimatedActor->SetMapper(decimatedMapper);

    double leftViewport[4] = {0.0, 0.0, 0.5, 1.0};
    double rightViewport[4] = {0.5, 0.0, 1.0, 1.0};

    vtkSmartPointer<vtkRenderer> leftRenderer = vtkSmartPointer<vtkRenderer>::New();
    leftRenderer->SetViewport(leftViewport);
    leftRenderer->AddActor(origianlActor);
    leftRenderer->SetBackground(1.0, 1.0, 1.0);

    vtkSmartPointer<vtkRenderer> rightRenderer = vtkSmartPointer<vtkRenderer>::New();
    rightRenderer->SetViewport(rightViewport);
    rightRenderer->AddActor(decimatedActor);
    rightRenderer->SetBackground(1.0, 1.0, 1.0);

    leftRenderer->GetActiveCamera()->SetPosition(0, -1, 0);
    leftRenderer->GetActiveCamera()->SetFocalPoint(0, 0, 0);
    leftRenderer->GetActiveCamera()->SetViewUp(0, 0, 1);
    leftRenderer->GetActiveCamera()->Azimuth(30);
    leftRenderer->GetActiveCamera()->Elevation(30);
    leftRenderer->ResetCamera();
    rightRenderer->SetActiveCamera(leftRenderer->GetActiveCamera());

    vtkSmartPointer<vtkGenericOpenGLRenderWindow> renderWindow = vtkSmartPointer<vtkGenericOpenGLRenderWindow>::New();
    renderWindow->AddRenderer(leftRenderer);
    renderWindow->AddRenderer(rightRenderer);

    QVTKOpenGLNativeWidget w;
    w.resize(640, 320);
    w.setRenderWindow(renderWindow);
    w.setWindowTitle("PolyDataDecimation");
    w.show();

    return a.exec();
}

运行效果:

vtkOuadricDecimation也可以实现三角网格的简化,并能较好地逼近原模型。该类虽然提供了 SetTargetReduction()函数用于设置模型简化程度,但是最终简化率并非严格等于程序中设置的简化率。可以通过GetActualReduction()函数来获取最终模型简化率。

vtkQuadricClustering 是三种模型抽取类中最快的一种,能够处理大数据模型。通过 StartAppend()、Append()和 EndAppend()函数可以将整个模型分为多个网格片处理,从而避免一次性处理整个模型,减少内存开支,提高处理效率。

三个网格抽取类都接受的是vtkPolyData的三角网格数据:如果vkPolyData 数据为多边形网格数据,需要先通过 vtkTriangleFilter 将多边形网络数据转换为三角网格数据。

2、网格细化

VTK中实现网格细化的类有 vtkLinearSubdivisionFilter、vtkLoopSubdivisionFilter 和vtkButerflySubdivisionFilter。

vtkInterpolatingSubdivisionFilter 内部提供了 SetNumberOfSubdivisions()函数来设置细化的次数,其中每次细化后模型的三角面片的个数将是细化前的4倍。因此,在对网格模型进行n次细分后,该模型的面片个数将成为原始模型面片数目的4n倍。vtkLinearSubdivisionFilter 实现了一种线性细分算法,每次细分将每个三角片面生成4个新的面片,该算法比较简单,速度快,但是细分后不能产生光滑的模型。vtkLoopSubdivisionFilter 实现了Loop 细分算法,每次细分会将一个三角面片生成4个三角面片。该方法能够生成光滑的连续曲面,应用比较广泛。vtkButterflySubdivisionFilter实现了蝶形细分算法。

示例代码:

#include <QApplication>
#include <vtkSmartPointer.h>
#include <vtkRenderer.h>
#include <vtkGenericOpenGLRenderWindow.h>
#include <QVTKOpenGLNativeWidget.h>
#include <vtkSphereSource.h>
#include <vtkPolyDataAlgorithm.h>
#include <vtkPolyDataMapper.h>
#include <vtkProperty.h>
#include <vtkLinearSubdivisionFilter.h>
#include <vtkLoopSubdivisionFilter.h>
#include <vtkButterflySubdivisionFilter.h>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    vtkSmartPointer<vtkPolyData> originalMesh;

    vtkSmartPointer<vtkSphereSource> sphereSource = vtkSmartPointer<vtkSphereSource>::New();
    sphereSource->Update();
    originalMesh = sphereSource->GetOutput();

    double numberOfViewports = 3;
    int numberOfSubdivisions = 2;

    vtkSmartPointer<vtkGenericOpenGLRenderWindow> renderWindow = vtkSmartPointer<vtkGenericOpenGLRenderWindow>::New();

    for(unsigned i = 0; i < numberOfViewports; i++)
    {
        vtkSmartPointer<vtkPolyDataAlgorithm> subdivisionFilter;
        switch(i)
        {
        case 0:
            subdivisionFilter = vtkSmartPointer<vtkLinearSubdivisionFilter>::New();
            dynamic_cast<vtkLinearSubdivisionFilter *> (subdivisionFilter.GetPointer())->SetNumberOfSubdivisions(numberOfSubdivisions);
            break;
        case 1:
            subdivisionFilter =  vtkSmartPointer<vtkLoopSubdivisionFilter>::New();
            dynamic_cast<vtkLoopSubdivisionFilter *> (subdivisionFilter.GetPointer())->SetNumberOfSubdivisions(numberOfSubdivisions);
            break;
        case 2:
            subdivisionFilter = vtkSmartPointer<vtkButterflySubdivisionFilter>::New();
            dynamic_cast<vtkButterflySubdivisionFilter *> (subdivisionFilter.GetPointer())->SetNumberOfSubdivisions(numberOfSubdivisions);
            break;
        default:
            break;
        }

        subdivisionFilter->SetInputData(originalMesh);
        subdivisionFilter->Update();

        vtkSmartPointer<vtkRenderer> renderer = vtkSmartPointer<vtkRenderer>::New();

        renderWindow->AddRenderer(renderer);
        renderer->SetViewport(static_cast<double>(i)/numberOfViewports,0,static_cast<double>(i+1)/numberOfViewports,1);
        renderer->SetBackground(1.0, 1.0, 1.0);

        vtkSmartPointer<vtkPolyDataMapper> mapper = vtkSmartPointer<vtkPolyDataMapper>::New();
        mapper->SetInputConnection(subdivisionFilter->GetOutputPort());
        vtkSmartPointer<vtkActor> actor = vtkSmartPointer<vtkActor>::New();
        actor->SetMapper(mapper);
        renderer->AddActor(actor);
        renderer->ResetCamera();
    }

    QVTKOpenGLNativeWidget w;
    w.resize(640, 320);
    w.setRenderWindow(renderWindow);
    w.setWindowTitle("PolyDataSubdivision");
    w.show();

    return a.exec();
}

运行效果:

模型细化算子仅对三角网格数据有效,因此在处理多边形数据时,需要通过vtkTriangleFilter将多边形数据转换为三角网格数据。

相关推荐
Mr.Q23 分钟前
Qt多边形填充/不填充绘制
qt
霁月风30 分钟前
设计模式——适配器模式
c++·适配器模式
jrrz08281 小时前
LeetCode 热题100(七)【链表】(1)
数据结构·c++·算法·leetcode·链表
可峰科技1 小时前
斗破QT编程入门系列之二:认识Qt:编写一个HelloWorld程序(四星斗师)
开发语言·qt
咖啡里的茶i1 小时前
Vehicle友元Date多态Sedan和Truck
c++
海绵波波1071 小时前
Webserver(4.9)本地套接字的通信
c++
@小博的博客1 小时前
C++初阶学习第十弹——深入讲解vector的迭代器失效
数据结构·c++·学习
爱吃喵的鲤鱼2 小时前
linux进程的状态之环境变量
linux·运维·服务器·开发语言·c++
7年老菜鸡3 小时前
策略模式(C++)三分钟读懂
c++·qt·策略模式
Ni-Guvara3 小时前
函数对象笔记
c++·算法