當前位置:網站首頁>多視角三維模型紋理映射 02

多視角三維模型紋理映射 02

2022-01-28 04:27:50 SimpleTriangle

前言

通過前篇文章《多視角三維模型紋理映射 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》

留坑:

後續若有時間、精力,則進一步記錄自己之前對轉臺的標定實現過程,也算是這幾篇文章的前傳吧。

版權聲明
本文為[SimpleTriangle]所創,轉載請帶上原文鏈接,感謝
https://cht.chowdera.com/2022/01/202201280427494985.html

隨機推薦