本片文章算是作为上一篇文章【G2O与多视角点云全局配准优化】 的连续。
从上篇文章杠已知,当初目前手头曾经有了配准好的全局点云、每块点云的变换矩阵以及对应每块点云的RGB图像。接下来,自然而然的便是:对全局点云重建三维网格,并对重建后的三维网格进行纹理贴图。
已知:三维模型(三角面片,格局任意),多视角RGB图片(纹理)、变换矩阵;求:为三维模型贴上纹理图。
针对上述待求解纹理,相熟的小伙伴必定直到,其实在求解多视角立体匹配的最初一步--外表重建与纹理生成。
接上篇,为实现本人的目标,首先须要重构三角网格,从点云重建三角网格,与相机、纹理图片等没有任何关系,可间接应用pcl中的重建接口或者cgal甚至meshlab等软件,间接由三维点云重建三角网格。
在实现重建三角网格之前,还有很重要--然而能够酌情省略的一个关键步骤:多视角三维点云交融!上面持续应用本人之前所拍摄的8个点云片段为例进行示例记录。(留神,此处强调8个点云片段,并不示意8个视角的点云,后续阐明)。

多视角点云交融

将多个点云片段拼接之后,无奈防止的存在多个视角下点云互相重叠的状况。如下图所示,在重叠区域的点云疏密水平个别都大于非重叠区域点云。进行点云交融的目标一是为了点云去重,升高点云数据量;二是为了进一步平滑点云,进步精度,为后续的其余计算(如测量等)提供高质量的数据。

凑巧,本人对挪动最小二乘算法有肯定理解,在pcl中,N年之前【pcl之MLS平滑】和【pcl之MLS算法计算并对立法向量】也做过一些小测试。在我的印象中,挪动最小二乘是这样工作的:针对深刻数据,计算指标点及其邻域,这些待计算的部分数据便形成了指标点的紧支域,在紧支域内有某个函数对指标点进行运算,运算的根本规定是根据紧支域内其余点到指标点的权重不同,这里的某个函数即所谓的紧支函数紧支函数 + 紧支域 +权重函数形成了挪动最小二乘算法的根本数学概念,所谓移动性则体现在紧支域在容许的空间中“滑动”计算,直至笼罩所有数据。最小二乘个别针对全局求最优,而挪动最小二乘因为其 “移动性”(紧支)不仅能针对全局优化求解,而且也具备局部优化性,更进一步的,针对三维点云,其可能提取等值面,联合MC(挪动立方体)算法,实现外表三角网格的重建。

pcl中,对于MLS算法的例子很多很多,本人之前的两篇水文也算也有个根本的利用介绍,此处不在过多记录。在这里,间接应用如下代码:

pcl::PointCloud<pcl::PointXYZRGB> mls_points; //存后果pcl::search::KdTree<pcl::PointXYZRGB>::Ptr tree(new pcl::search::KdTree<pcl::PointXYZRGB>);pcl::MovingLeastSquares<pcl::PointXYZRGB, pcl::PointXYZRGB> mls;mls.setComputeNormals(false);mls.setInputCloud(res);mls.setPolynomialOrder(2); //MLS拟合的阶数,mls.setSearchMethod(tree);mls.setSearchRadius(3);mls.setUpsamplingMethod(pcl::MovingLeastSquares<pcl::PointXYZRGB, pcl::PointXYZRGB>::UpsamplingMethod::VOXEL_GRID_DILATION);mls.setDilationIterations(0);  //设置VOXEL_GRID_DILATION办法的迭代次数mls.process(mls_points);

留神,上述计算过程中,应用了pcl::MovingLeastSquares::VOXEL_GRID_DILATION办法,依照pcl官网解释,该办法不仅可能修复大量点云空洞,而且可能对点云坐标进行局部优化,输入为全局疏密水平一样的点云,通过设置不同的迭代次数,该办法不仅可能降采样,还能上采样(次要通过迭代次数管制).

将本人的数据放入上述计算过程,点云交融部分成果如下:

肉眼可见点云更加平均、平滑,同同时数据量从100w+ 降到 10w+.

上述操作,根本满足了本人下一步的要求。

进一步的,鉴于外表重建并非重点,针对上述后果,这里间接应用MeshLab软件中的泊松重建,后果记录如下:

多视角纹理映射

在这里,正式引入本人对所谓“多视角”的了解。

首先,多视角多角度,通常状况下,这两个概念根本都是在说同一件事件,即从不同方位角拍摄三维模型。然而这其中又隐含两种不同的 [操作形式],其一如手持式三维扫描仪、SLAM中的状态预计、甚至搭载摄像机/雷达的主动驾驶汽车等,这些场景下根本都是相机在动,指标不动,即相机绕指标静止;其二 如转台式三维扫描仪等,这些场景根本都是相机不动,指标动,即指标自身具备静止属性。所以这里,有必要对多视角多角度做进一步的辨别,多视角指的是相机的多视角(对呀,相机才具备实在的物理视角、位姿、拍摄角度 巴拉巴拉...),多角度指的是指标物体的不同角度(对呀,一个物体能够从不同的角度被察看)。

其次,外参,外参是很重要的一个概念(废话,还用你说!),可真的引起其余使用者足够的器重吗?未必! 咱们个别常说的外参,其实是带有主语的,只是咱们太司空见惯从而把主语省略了。外参---个别指相机的外参,即形容相机的变动。在多视角三维点云配准中,每个点云都有一个变动矩阵,该变动矩阵能够称其为点云的外参,即形容点云的变动。至此,至多呈现了两个外参,且这两个外参所代表的物理意义齐全不一样,然而又有千头万绪的分割。咱们都晓得,宏观下静止是互相的,则必然点云的外参和对应相机的外参互逆。

注: 之所以这里对上述概念做严格辨别,次要还是因为本人之前对上述所提到的概念没有真正深刻了解,尤其是对外参的了解,导致前期算法计算呈现谬误;其二还是后续库、框架等对上述有严格辨别。

OpenMVS与纹理映射

终于到了OpenMVS。。。。

已知OpenMVS能够用来浓密重建(点云)、Mesh重建(点云-->网格)、Mesh优化(非风行解决、补洞等)、网格纹理映射,正好对应源码自带的几个APP。

此处,目标只是单纯的须要OpenMVS的网格纹理映射性能!浏览国内外无关OpenMVS的应用办法,尤其国内,根本都是”一条龙“服务,总结应用流程就是 colmap/OpenMVG/visualSFM...+ OpenMVS,根本都是应用可执行文件的傻瓜式形式(网上一查一大堆),而且根本都是翻来覆去的互相”援用“(剽窃),显然不合乎本人要求与目标。吐个槽。。。

为了应用Openmvs的纹理映射模块,输出数据必须是 .mvs格式文件,额。。。.mvs是个什么鬼,我没有啊,怎么办?!

好吧,进入OpenMVS源码吧,用本人的数据去填充OpenMVS所需的数据接口。(Window10 + VS2017+OpenMVS编译省略,次要是本人过后编译的时候没做记录,不过CMake工程个别不太简单)。

场景Scene

查看OpenMVS自带的几个例子,发现其必须要填充Scene类,针对本人所面对的问题,Scene类的次要构造如下:

class MVS_API Scene{public:    PlatformArr platforms; //相机参数和位姿 // camera platforms, each containing the mounted cameras and all known poses    ImageArr images; //纹理图,和相机对应 // images, each referencing a platform's camera pose    PointCloud pointcloud; //点云 // point-cloud (sparse or dense), each containing the point position and the views seeing it    Mesh mesh; //网格 // mesh, represented as vertices and triangles, constructed from the input point-cloudunsigned nCalibratedImages; // number of valid imagesunsigned nMaxThreads; // maximum number of threads used to distribute the work load    ... //省略代码    bool TextureMesh(unsigned nResolutionLevel, unsigned nMinResolution, float fOutlierThreshold=0.f, float fRatioDataSmoothness=0.3f, bool bGlobalSeamLeveling=true, bool bLocalSeamLeveling=true, unsigned nTextureSizeMultiple=0, unsigned nRectPackingHeuristic=3, Pixel8U colEmpty=Pixel8U(255,127,39));    ... //省略代码    }

本人所须要的次要函数为bool TextureMesh(),可见其自带了很多参数,参数意义前期应用时再探讨。

Platforms

首先来看platforms,这个货色是Platform的数组。在OpenMVS中,Platform定义如下:

class MVS_API Platform{...public:    String name; // platform's name    CameraArr cameras; // cameras mounted on the platform    PoseArr poses; ... }

对咱们而言,必须须要填充CameraArrPoseArr两个数组。

CameraArrCameraIntern类型的数组。CameraIntern是最根本的相机父类,一提到相机,则必须蕴含两个矩阵:内参和外参,CameraIntern也不例外,它须要应用如下三个参数填充,其中K为归一化的3X3相机内参,可用Eigen中的矩阵填充,所谓归一化,其实就是该内参矩阵的每个元素除以纹理图片的最大宽度或高度;R顾名思义,示意相机的旋转C示意相机的平移,R和C独特形成了相机的外参

    KMatrix K; //相机内参(归一化后的) the intrinsic camera parameters (3x3)    RMatrix R; //外参:相机旋转 rotation (3x3) and    CMatrix C;

PoseArrPose类型的数组。Pose是类Platform中定义的一个构造体:

struct Pose {        RMatrix R; // platform's rotation matrix        CMatrix C; // platform's translation vector in the global coordinate system        #ifdef _USE_BOOST        template <class Archive>        void serialize(Archive& ar, const unsigned int /*version*/) {            ar & R;            ar & C;        }        #endif    };    typedef CLISTDEF0IDX(Pose,uint32_t) PoseArr;

从上述Pose中能够看出,其也蕴含了两个矩阵,然而这里的Pose中的矩阵所示意的物理意义和CameraIntern中外参的物理意义齐全不同,简略来说,<font color=red>CameraIntern中示意的是相机自身固有或自带的属性,而Pose则示意整个Platform平台(蕴含相机的)在世界坐标系中位姿矩阵,针对每一张纹理图(上面介绍),这两个属性参数独特形成了对应相机的实在外参</font>(前期通过源代码解释)【问题1】

Images

imagesImageArr类型的数组,如同源码中解释,每个Image对应于每个相机位姿。Image的构造如下:

class MVS_API Image{public:    uint32_t platformID;//plateform绝对应的ID // ID of the associated platform    uint32_t cameraID; // camer对应的ID //ID of the associated camera on the associated platform    uint32_t poseID; // 位姿ID  //ID of the pose of the associated platform    uint32_t ID; // global ID of the image    String name; // 该imgage的门路 // image file name (relative path)    Camera camera; // view's pose    uint32_t width, height; // image size    Image8U3 image; //load的时候曾经解决. image color pixels    ViewScoreArr neighbors; // scored neighbor images    float scale; // image scale relative to the original size    float avgDepth;    ....}

如上述本人增加的中文正文,其中<font color =red>platformID、cameraID和poseID三个参数肯定要与Platform中的成员属性严格对应</font>,对本身而言这是最重要的三个参数,Platform中的成员变量自身并不携带ID属性,只是依据增加程序默认排序,ID索引从0开始递增。

Image构造中,还有不得不提的camera成员,如源码中所正文,它示意视角(Camera)的位姿,也就是哪个相机对应于该图片。乍一看该camera成员也必须要进行填充,可事实并非如此,起因前期解释【问题2】

该构造中其余成员,如图像宽度高度、图像的name等,在调用Image::LoadImage(img_path);函数的的时候会主动填充;neighbors成员示意图像于3D点的邻接关系,在纹理贴图中非必要选项,而且不影响纹理贴图成果。

PointCloud

该成员示意点云,个别作为OpenMVS的稠密重建或浓密重建的后果,对于网格不具备约束性,间接舍弃不填充。

Mesh

OpenMVS中的三角网格存储构造,我只须要晓得其有Load函数即可。