VTK知识学习(45)- 基本的图形操作(四)

发布于:2025-03-07 ⋅ 阅读:(12) ⋅ 点赞:(0)

1、点云配准

1)概述

        在计算机逆向工程中,通过三维扫描等实物数字化技术可以获取各种点云数据。但是受测量环境和设备的影响,在一次测量的情况下,难以获取实物整体的点云数据,因此需要多次从不同角度进行测量。但不同的测量数据之间可能会存在平移错误和旋转错位等问题。这就需要使用点云配准技术来对测量点云数据进行局部配准和整合以得到完整的模型数据。另外,在外科手术导航技术中,图像标记点数据与人体表面标记点数据的配准是一个关键步骤,对于手术定位的精度有着重要的影响。这通常需要使用基于标记点的配准技术(也属于点云配准的一种)。因此,点云配准即是对一组源点云数据应用一个空间变换,使得变换后的数据与目标点云数据能够一一映射,使两组数据之间的平均距离误差最小。

        vtkLandmarkTransform 实现了标记点配准算法,使得两个点集在配准后的平均距离最小通过 SetSourceLandmarks()和 SetTargetLandmarks()函数分别设置源标记点集和目标标记点集需要注意的是,源标记点集和目标标记点集序号要对应,如1号源点要映射到1号目标点。

2)代码

        定义了两组点集并使用基于标记点的配准算法进行配准。

 private void TestDataLandmarkReg()
        {
            vtkPoints sourcePoints = vtkPoints.New();
            sourcePoints.InsertNextPoint(0.5, 0, 0);
            sourcePoints.InsertNextPoint(0, 0.5, 0);
            sourcePoints.InsertNextPoint(0, 0, 0.5);

            vtkPoints targetPoints = vtkPoints.New();
            targetPoints.InsertNextPoint(0, 0, 0.55);
            targetPoints.InsertNextPoint(0, 0.55, 0);
            targetPoints.InsertNextPoint(-0.55, 0, 0);

            vtkLandmarkTransform landmarkTransform = vtkLandmarkTransform.New();
            landmarkTransform.SetSourceLandmarks(sourcePoints);
            landmarkTransform.SetTargetLandmarks(targetPoints);
            landmarkTransform.SetModeToRigidBody(); //设置其配准变换的类型为刚体变换,即只有平移和放置
            landmarkTransform.Update();

            vtkPolyData source = vtkPolyData.New();
            source.SetPoints(sourcePoints);

            vtkPolyData target = vtkPolyData.New();
            target.SetPoints(targetPoints);

            //显示点集
            vtkVertexGlyphFilter sourceGlyphFilter = vtkVertexGlyphFilter.New();
            sourceGlyphFilter.SetInputData(source);
            sourceGlyphFilter.Update();

            vtkVertexGlyphFilter targetGlyphFilter = vtkVertexGlyphFilter.New();
            targetGlyphFilter.SetInputData(target);
            targetGlyphFilter.Update();

            vtkTransformPolyDataFilter transformFilter = vtkTransformPolyDataFilter.New();
            transformFilter.SetInputData(sourceGlyphFilter.GetOutput());
            transformFilter.SetTransform(landmarkTransform);
            transformFilter.Update();

            vtkPolyDataMapper sourceMapper = vtkPolyDataMapper.New();
            sourceMapper.SetInputConnection(sourceGlyphFilter.GetOutputPort());

            vtkActor sourceActor = vtkActor.New();
            sourceActor.SetMapper(sourceMapper);
            sourceActor.GetProperty().SetColor(0, 1, 0);
            sourceActor.GetProperty().SetPointSize(5);

            vtkPolyDataMapper targetMapper = vtkPolyDataMapper.New();
            targetMapper.SetInputConnection(targetGlyphFilter.GetOutputPort());

            vtkActor targetActor = vtkActor.New();
            targetActor.SetMapper(targetMapper);
            targetActor.GetProperty().SetColor(1, 0, 0);
            targetActor.GetProperty().SetPointSize(5);


            vtkPolyDataMapper solutionMapper = vtkPolyDataMapper.New();
            solutionMapper.SetInputConnection(transformFilter.GetOutputPort());

            vtkActor solutionActor = vtkActor.New();
            solutionActor.SetMapper(solutionMapper);
            solutionActor.GetProperty().SetColor(0, 0, 1);
            solutionActor.GetProperty().SetPointSize(5);

            vtkAxesActor axes = vtkAxesActor.New();
            axes.SetScale(30);

            vtkRenderer renderer = vtkRenderer.New();
            renderer.AddActor(sourceActor);
            renderer.AddActor(targetActor);
            renderer.AddActor(solutionActor);
            renderer.AddActor(axes);
            renderer.ResetCamera();
            renderer.SetBackground(1, 1, 1);

            vtkRenderWindow renderWindow = renderWindowControl.RenderWindow;
            renderWindow.AddRenderer(renderer);

            renderWindow.Render();
        }
3)效果

绿色的三个点为源标记点,红色点为目标标记点,而蓝色的点为配准后的点集。

4)说明

        vtkLandmarkTransform 的使用比较简单,只需设置源标记点和目标标记点。

        SetModeToRigidBody()函数,用于设置其配准变换的类型为刚体变换,即只有平移和旋转:        

        SetModeToSimilarity()设置相似变换,即平移、旋转和放缩变换;

        SetModeToAffne()函数用于设置放射变换。默认情况下使用相似变换。

         vtkVertexGlyphFilter 类显示点集,vtkTransformPolyDataFilter 用来对源标记点进行变换来显示配准后的点集,SetTransform()直接设置为vtkLandmarkTransform 的变换结果。从结果来看,配准后的点集与目标标记点集是非常接近的。

2、ICP点云配准

1)概述

        点云数据配准最经典的方法是迭代最近点算法(Iterative Closest Points,ICP)。ICP 算法是一个迭代的过程,每次迭代中对于源数据点P找到目标点集Q中的最近点,然后基于最小乘原理求解当前的变换T。通过不断迭代直至收敛,即完成了点集的配准。

2)代码

        演示如何对点集做ICP配准

private void TestDataLandICP()
        {
            //读取一个人脸模型
            vtkPolyDataReader reader = vtkPolyDataReader.New();
            reader.SetFileName("F:\\code\\VTK\\TestActiViz\\data\\fran_cut.vtk");
            reader.Update();

            vtkPolyData original = reader.GetOutput();

            //对原始模型做 平移和旋转变换
            vtkTransform translation = vtkTransform.New();
            translation.Translate(0.2, 0, 0);  //X方向平移量为0.2
            translation.RotateX(30);           //绕X轴旋转30度  

            //对vtkPolyData的空间变换 
            vtkTransformPolyDataFilter transformFilter1 = vtkTransformPolyDataFilter.New();
            transformFilter1.SetInputData(reader.GetOutput());
            transformFilter1.SetTransform(translation);
            transformFilter1.Update();

            vtkPolyData source = vtkPolyData.New();
            source.SetPoints(original.GetPoints());

            vtkPolyData target = vtkPolyData.New();
            target.SetPoints(transformFilter1.GetOutput().GetPoints());

            vtkVertexGlyphFilter sourceGlyphFilter = vtkVertexGlyphFilter.New();
            sourceGlyphFilter.SetInputData(source);
            sourceGlyphFilter.Update();

            vtkVertexGlyphFilter targetGlyphFilter = vtkVertexGlyphFilter.New();
            targetGlyphFilter.SetInputData(target);
            targetGlyphFilter.Update();

            vtkIterativeClosestPointTransform icpTransform = vtkIterativeClosestPointTransform.New();
            icpTransform.SetSource(sourceGlyphFilter.GetOutput());     //源点集
            icpTransform.SetTarget(targetGlyphFilter.GetOutput());     //目标点集
            icpTransform.GetLandmarkTransform().SetModeToRigidBody();
            icpTransform.SetMaximumNumberOfIterations(20);             //设置ICP算法迭代的次数
            icpTransform.StartByMatchingCentroidsOn();                 // 设置配准之前先计算两个点集重心,并平移源点集使得两者重心重合 
            icpTransform.Modified();
            icpTransform.Update();

            vtkTransformPolyDataFilter transformFilter2 = vtkTransformPolyDataFilter.New();
            transformFilter2.SetInputData(sourceGlyphFilter.GetOutput());
            transformFilter2.SetTransform(icpTransform);
            transformFilter2.Update();

            vtkPolyDataMapper sourceMapper = vtkPolyDataMapper.New();
            sourceMapper.SetInputConnection(sourceGlyphFilter.GetOutputPort());

            vtkActor sourceActor = vtkActor.New();
            sourceActor.SetMapper(sourceMapper);
            sourceActor.GetProperty().SetColor(0, 1, 0);
            sourceActor.GetProperty().SetPointSize(3);

            vtkPolyDataMapper targetMapper = vtkPolyDataMapper.New();
            targetMapper.SetInputConnection(targetGlyphFilter.GetOutputPort());

            vtkActor targetActor = vtkActor.New();
            targetActor.SetMapper(targetMapper);
            targetActor.GetProperty().SetColor(1, 0, 0);
            targetActor.GetProperty().SetPointSize(3);

            //显示配准后的点集
            vtkPolyDataMapper solutionMapper = vtkPolyDataMapper.New();
            solutionMapper.SetInputConnection(transformFilter2.GetOutputPort());

            vtkActor solutionActor = vtkActor.New();
            solutionActor.SetMapper(solutionMapper);
            solutionActor.GetProperty().SetColor(0, 0, 1);
            solutionActor.GetProperty().SetPointSize(5);


            vtkRenderer renderer = vtkRenderer.New();
            renderer.AddActor(sourceActor);
            renderer.AddActor(targetActor);
            renderer.AddActor(solutionActor);
            renderer.SetBackground(1, 1, 1);

            vtkRenderWindow renderWindow = renderWindowControl.RenderWindow;
            renderWindow.AddRenderer(renderer);

            renderWindow.Render();
        }
3)效果

        绿色点为源点集,红色点为目标点集,蓝色点为配准后的点集(由于配准后的点集与目标点集是重合的,因此肉眼观看不是很清晰)。

4)说明

        读取了一个人脸模型(fancut.vtk)。为了方便测试效果,这里对原始模型做了一个平移和旋转变换。vtkTransformPolyDataFilter 类,可以实现 vtkPolyData 的空间变换,其输入为一个 vkTransfom,即变换矩阵。这里设置在X方向平移量为0.2,旋转量为绕X轴旋转 30°。vkIterativeClosestPointTransfomm 类中设置源点集和目标点集的函数为SetSource()和 SetTarget(),其输入数据的类型为 vtkDataSet,而 vtkLandmarkTransfomm 的输入数据类型为 vtkPoints。因此,这里使用 vtkVertexGlyphFilter 将读入模型和变换后模型的点集转换为相应的 vtkPolyData 数据,并设置为 vtkIterativeClosestPointTransfomm 的源点数据和目标点数据。

        vtkIterativeClosestPointTransform 内部定义了一个 vtkLandmarkTransform指针,用于计算 ICP 迭代中的最佳匹配点集。可以通过 GetLandmarkTransfomm()函数获取,并通过 vtkLandmarkTransform 指针设置相应的变换类型。

        SetMaximumNumberOfterations()函数用于设置 ICP 算法迭代的次数

        StartByMatchingCentroidsOn()函数则用于设置配准之前先计算两个点集重心,并平移源点集使得两者重心重合。

        配置完毕,可以通过 GetMatrixO)函数来获取相应的变换矩阵。最后,再次使用 vkTransfommPolyDataFilter 利用ICP 配准变换来对源点进行空间变换。

3、纹理映射

1)概述

        纹理映射(Texture Mapping)是将纹理空间中的纹理像素映射到屏幕空间中的像素的过程。纹理生成过程实质上是将所定义的纹理映射为某种三维物体表面的属性,并参与后续的光照计算。在三维图形中,纹理映射运用得非常广泛,尤其是描述具有真实感的物体。比如绘制一面砖墙,就可以使用一幅具有真实感的图像或者照片作为纹理贴到一个矩形上,这样一面逼真的砖墙就绘制好了。

        实现纹理映射主要是建立纹理空间与模型空间、模型空间与屏幕空间之间的映射关系(见图6-28)。其中纹理空间可以定义为u-v空间,每个轴坐标范围为(0.1)。其中对于一个纹理图像,其左下角 uv 坐标为(0.0),右上角坐标为(1,1)。而对于简单的参数模型,可以方便地建立模型与纹理空间的映射关系,例如球面、圆柱面等。而根据图形学三维空间变换容易实现模型空间到屏幕空间的变换,因此最终显示在计算机屏幕上的图像即是纹理映射后的结果。

        而对于无参数化曲面的纹理映射技术,通常需要将纹理空间到模型空间的映射分解为两个简单映射。这里需要引入一个包围景物的中介三维曲面作为中介映射媒介,主要实现步骤
如下。

        先将二维纹理空问映射为一个简单的三维物体表面,例如球面、圆柱面等;然后将上述中介物体表面的纹理映射到模型表面,例如以模型表面法线与中介模型的交点作为映射点。这样即可实现由纹理空间到模型空间的映射。

        VTK中定义了多个类实现纹理空间到模型空间的映射,例如vtkTextureMapToPlane 通过一个平面建立纹理空间到模型空间的映射关系;vtkTextureMapToCylinder 通过圆柱面建立映射关系;vtkTextureMapToSphere 通过球面建立映射关系。vtkTexture 则实现加载纹理(在第2章中已经介绍过)。另外,vtkTransformTextureCoords也是一个非常有用的类,可以实现纹理坐标的平移和缩放,例如,如果要实现重复纹理,只需通过vkTransformTextureCoords::SetScale()将纹理坐标每个方向进行放大,如由[0,1]变换到[0,10]即可。

2)代码
private void TestTextureMap()
        {
            vtkBMPReader reader = vtkBMPReader.New();
            reader.SetFileName("F:\\code\\VTK\\TestActiViz\\data\\masonry.bmp");
            reader.Update();

            //vtkPNGReader reader = vtkPNGReader.New();
            //reader.SetFileName("F:\\code\\VTK\\TestActiViz\\data\\图层 21.png");
            //reader.Update();

            //vtkJPEGReader reader = vtkJPEGReader.New();
            //reader.SetFileName("F:\\code\\VTK\\TestActiViz\\data\\1.jpg");
            //reader.Update();

            vtkTexture texture = vtkTexture.New();
            texture.SetInputConnection(reader.GetOutputPort());
            

            vtkXMLPolyDataReader modelReader = vtkXMLPolyDataReader.New();
            modelReader.SetFileName("F:\\code\\VTK\\TestActiViz\\data\\cow.vtp");
            modelReader.Update();

            vtkTextureMapToCylinder texturemap = vtkTextureMapToCylinder.New();
            texturemap.SetInputConnection(modelReader.GetOutputPort());
         

            vtkPolyDataMapper mapper = vtkPolyDataMapper.New();
            mapper.SetInputConnection(texturemap.GetOutputPort());

            vtkActor actor = vtkActor.New();
            actor.SetMapper(mapper);
            actor.SetTexture(texture);

            vtkRenderer renderer = vtkRenderer.New();
            renderer.AddActor(actor);
            renderer.SetBackground(1, 1, 1);

            vtkRenderWindow renderWindow = renderWindowControl.RenderWindow;
            renderWindow.AddRenderer(renderer);

            renderWindow.Render();
        }
3)效果

4)说明

        先使用 vtkTexture 来加载纹理图像,并通过 vtkActor:SetTexture()设置纹理;然后使用 vtkTextureMapToCylinder 来建立纹理空间与模型空间的映射关系;最后通过 VTK 渲染引擎进行渲染。

4、结语

        图形处理是 VTK中的一个重要内容。

        vtkPolyData 是一种使用较为广泛的 VTK 数据结构,而且在实际应用中,用 vtkPolyData 可以表示很多常用的数据,例如点云数据、面片模型等。因此,掌握 vtkPolyData数据及其处理是学习VTK的一个重要内容。前面分析了vtkPolyData 数据的基本组成、创建方法和显示管线。掌握这些基本内容,便了解了怎样将实际中的数据转换成一个 vtkPolyData 数据,并根据不同的需求将其显示出来。接着以一些简单的示例来分析一些 vtkPolyData 的基本操作,包括距离、面积、包围盒、法向量以及符号化等。这些都是高级图形处理的基本工具。对于高级图形处理,着重分析了图形平滑、封闭性检测、连通性分析、多分辨率处理、表面重建、点云配准、纹理映射等内容。掌握了这些内容,便可解决许多实际的工程问题,此外,还可以将实现自定义的算法用于处理图形数据。