前言

通过前篇文章《多视角三维模型纹理映射 01》,根本对OpenMVS框架和应用办法有了一个简略的了解,这里持续基于上一篇文章进行本人的探索,也对上篇文章中留下的问题进行解释。

已知:

1、手头有从8个角度拍摄的点云数据,且点云曾经通过配准、交融、网格化造成了Mesh;

2、8个点云的外参(确认正确无误);

3、8个角度的图片;

原始点云是这样的:

点云配准后如上篇文章《G2O与多视角点云全局配准优化》图片所示。

实现

基于OpenMVS,次要实现代码片段如下:

void texture() {    int number_of_thread = 1;    Scene scene(number_of_thread);        Eigen::Matrix4d temp;    std::string t_inPath = "./test/scene/1_in.txt";    loadMat4(t_inPath, temp);    Eigen::Matrix3d _k = temp.block<3, 3>(0, 0);  //相机内参    //归一化    _k(0, 0) = _k(0, 0) / 1600;    _k(1, 1) = _k(1, 1) / 1600;    _k(0, 2) = _k(0, 2) / 1600;    _k(1, 2) = _k(1, 2) / 1600;        { //填充platform        Platform &plat = scene.platforms.AddEmpty();        //1、name        plat.name = "platform";            CameraIntern &cam = plat.cameras.AddEmpty();            cam.R = RMatrix::IDENTITY;        cam.C = Point3(0, 0, 0);        cam.K = _k;            //已知 有8个相机位姿        std::string matrix_path = "./test/scene/";        for (int i = 1; i <= vieNum; ++i)        {            std::string _path = matrix_path + std::to_string(i) + "_ex.txt";            Eigen::Matrix4d temp;            loadMat4(_path, temp);                        Platform::Pose &pose = plat.poses.AddEmpty();            pose.C = temp.block<3, 1>(0, 3);            pose.R = temp.block<3, 3>(0, 0);                    }    }        {//填充images        std::string imag_path = "test/image/";        std::string matrix_path = "test/scene/";        //ImageArr imgarr = scene.images;        for (int i = 1; i <= vieNum; ++i) {            std::string t_img = imag_path + std::to_string(i) + ".jpg";                        String _imgP(t_img);                Image &_img = scene.images.AddEmpty();            _img.ID = i-1;            _img.platformID = 0;            _img.cameraID = 0;            _img.poseID = i-1;             _img.LoadImage(_imgP);            scene.images.push_back(_img);        }        }        scene.mesh.Load("test/sm_mesh.ply");     unsigned nResolutionLevel = 0;    unsigned nMinResolution = 1280;    float fOutlierThreshold = 0.f;    float fRatioDataSmoothness = 0.0f;    bool bGlobalSeamLeveling = true;    bool bLocalSeamLeveling = true;    unsigned nTextureSizeMultiple = 0;    unsigned nRectPackingHeuristic = 0;    bool res = scene.TextureMesh(nResolutionLevel, nMinResolution, fOutlierThreshold,        fRatioDataSmoothness, bGlobalSeamLeveling, bLocalSeamLeveling,        nTextureSizeMultiple, nRectPackingHeuristic);        std::cout << "texture res:" << res << std::endl;        //scene.Save("./test/res_tex.mvs",ARCHIVE_TEXT);    scene.mesh.Save("test/res_tex.ply");}

依照上篇文章《多视角三维模型纹理映射 01》中所述,须要填充Scene中的相干数据成员。在这里我增加了一个Platform以及一个camera,而后增加了8个相机的Pose,这是合乎本人的理论应用状况的,我有一个相机,拍摄了指标的8个角度的图像,所以我只有一个平台,一个相机,然而复原了8个相机的位姿。

而后就是填充了8个Image,每一个ImageposeID须要和Platform中的Pose严格对应。

最初填充Mesh,间接应用了Load()函数。

释疑

针对上篇文章中的两个问题,以及本人为什么在这里以上述形式填充Platform,次要起因在于:

进入scene.TextureMesh()代码实现局部,在其视图抉择模块中有

        imageData.UpdateCamera(scene.platforms);

一块代码,显然这是更新相机参数,更确切的,这是更新Image类中成员camera的;进一步的进入该代码:

// compute the camera extrinsics from the platform pose and the relative camera pose to the platform//从platform计算相机外参Camera Image::GetCamera(const PlatformArr& platforms, const Image8U::Size& resolution) const{    ASSERT(platformID != NO_ID);    ASSERT(cameraID != NO_ID);    ASSERT(poseID != NO_ID);    // compute the normalized absolute camera pose    //依据platformid提取该image对应的platform信息    const Platform& platform = platforms[platformID];    Camera camera(platform.GetCamera(cameraID, poseID));        // compute the unnormalized camera    //计算原始相机内参(归一化前的,实在相机内参)    camera.K = camera.GetK<REAL>(resolution.width, resolution.height);    //将相机内外惨整合为3*4的仿射矩阵(P=KR[I|-C])    camera.ComposeP();        return camera;} // GetCameravoid Image::UpdateCamera(const PlatformArr& platforms){    camera = GetCamera(platforms, Image8U::Size(width, height));} // UpdateCamera

从上述代码块可见,Image类中成员camera实质是从Platform中依据对应的ID提取计算的,也就是说Image::camera是依赖Platform的!最初进入platform.GetCamera(cameraID, poseID)代码实现局部:

// return the normalized absolute camera posePlatform::Camera Platform::GetCamera(uint32_t cameraID, uint32_t poseID) const{    const Camera& camera = cameras[cameraID];    const Pose& pose = poses[poseID];    // add the relative camera pose to the platform    Camera cam;    cam.K = camera.K;    cam.R = camera.R*pose.R;    cam.C = pose.R.t()*camera.C+pose.C;    return cam;} // GetCamera

从上述代码可见,实在参加纹理映射的是Imagecamera,该camera的外参由Platformcamera和对应pose独特决定。

至此上述代码曾经解释了:

  1. 上篇【问题1】:每张纹理图片对应的相机位姿其实是由Platform中的相机和Platform中的位姿决定的
  2. 上篇【问题2】:没有必要在 ”外” 代码中填充实现Image中的camera,无论怎么样填充该数据,它都会被Platform中的属性所笼罩。当然,前提是你曾经正确填充了Paltform.
  3. 上述源代码也解释了为什么在本人代码中,我只是发明了一个PlatformCamera,并且所发明的Camera旋转矩阵为单位矩阵,平移矩阵为0的起因---PlatformCameraPose会同时参加Imagecamera的计算,此时本人所填充的Platformpose曾经是正确且实在对应Image的位姿矩阵,所以没必要也无奈再去填充Platfromcamera

试验

复原的相机位姿与全局配准点云的关系如下:

Mesh后果如图:

Mesh对应的合成纹理如下:

反思、总结

上述本人对于OpenMVS的了解,也不尽然完全正确,还有待进一步的晋升,只是目前临时达到了本人初步料想后果。

再来说一说外参。点云的外参示意点云的静止,绝对的,点云外参的逆则示意所对应相机的静止,将所有点云做全局配准对立到世界坐标系下之后,每块点云外参的逆则代表了其所对应的相机在世界坐标系下的位姿矩阵。

另外,本人所应用的相机为RGB--D相机,即红外相机负责生成点云,RGB相机负责为点云提供纹理图,也就是说初始点云的坐标系是在红外相机下的,而纹理图则属于RGB相机,所以该RGB—D相机模组之间还有红外相机和RGB黑白相机的之间的标定,这里的标定矩阵,实质是点云的外参---将点云变换到RGB相机下能力贴图嘛!也正是因为应用的是RGB-D类型的相机,所以OpenMVS所需的矩阵才须要本人手动去填充(集体了解:OpenMVS是间接从RGB图像中复原三维模型,而后贴图,不存在额定的点云到RGB的外参)。

再扯远一点,TexRecon也是专门用来解决网格纹理映射,它的输出也是mesh +camera+纹理图,与OpenMVS中不同,TexRecon中的camera其实是用点云的外参来填充!!然而TexRecon在进行纹理映射时候会对原始网格进行删减,可能会导致本来润滑的网格产生额定的孔洞,另外TexRecon依赖第三方(MVE等)比拟多,这也是本人没有更加深刻源码探讨的起因。

(TexRecon成果不是很好,不贴图了,或是本人应用并不是完全正确)

《Let There Be Color! Large-Scale Texturing of 3D Reconstructions》

留坑:

后续若有工夫、精力,则进一步记录本人之前对转台的标定实现过程,也算是这几篇文章的前传吧。