乐趣区

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

前言

通过前篇文章《多视角三维模型纹理映射 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;

} // GetCamera
void 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 pose
Platform::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》

留坑:

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

退出移动版