关于c++:多视角三维模型纹理映射-01

3次阅读

共计 5934 个字符,预计需要花费 15 分钟才能阅读完成。

本片文章算是作为上一篇文章【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-cloud

unsigned nCalibratedImages; // number of valid images

unsigned 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 函数即可。

正文完
 0