應(yīng)用于三維重建的TSDF(一)原理與代碼解析

一些背景知識(shí)

TSDF的主要作用是進(jìn)行三維場(chǎng)景在計(jì)算機(jī)中的重建。目前的那些中文博客與成熟的TSDF應(yīng)用其實(shí)還有差距滑沧,故寫(xiě)此文。
視覺(jué)SLAM應(yīng)用的一個(gè)分支為Dense SLAM巍实。簡(jiǎn)單來(lái)說(shuō)就是在定位機(jī)器人的同時(shí)對(duì)周?chē)沫h(huán)境進(jìn)行(近乎)實(shí)時(shí)的3維重建滓技,比如下圖[1]

tmp_elastic.jpg

比較出名的有開(kāi)源代碼的代表作為15年之前的Kinect Fusion, Dense Visual SLAM for RGB-D Cameras[2],2015年之后有比較優(yōu)秀的代表作為Elastic Fusion, Infinitam V3, 2019年的BAD SLAM 以及2020年的基于Voxblox的kimera VINS+Kimera Semantics等棚潦。
一般的Dense SLAM主要面臨的局限性是場(chǎng)景重建地很精密令漂,對(duì)硬件的要求高,比如上面的Elastic Fusion的重建場(chǎng)景可能就包含數(shù)百萬(wàn)個(gè)點(diǎn)丸边,一般需要較強(qiáng)的GPU輔助叠必。而且比較難應(yīng)用大場(chǎng)景的三維重建。Elastic Fusion, BAD SLAM都只能用于室內(nèi)小范圍的3D重建妹窖。 Infinitam V3號(hào)稱(chēng)可以進(jìn)行大范圍的3D重建纬朝,但是論文最后的limitation說(shuō)到
Firstly, the number of points that a surfel scene can contain is currently limited to 5 million
5百萬(wàn)點(diǎn)并不適用于大型的場(chǎng)景,當(dāng)然這個(gè)值可以改變骄呼,至于具體的效果我并沒(méi)有試過(guò)共苛。讀者可自行去嘗試。
基于CPU的dense SLAM一般來(lái)說(shuō)重建的效果會(huì)差一些蜓萄,但是方便應(yīng)用于大型室外場(chǎng)景隅茎。比如我們?cè)诒疚幕蛘呓酉聛?lái)的文章中會(huì)詳細(xì)介紹的voxblox(又開(kāi)始畫(huà)餅,畢竟SLAM introdution都沒(méi)寫(xiě)了绕德。患膛。。哎主要是簡(jiǎn)書(shū)老吞我辛辛苦苦l(xiāng)atex碼出來(lái)的公式耻蛇。。胞此。就不想寫(xiě)了= = )
voxblox嚴(yán)格來(lái)說(shuō)并不算是SLAM系統(tǒng)臣咖,因?yàn)檫@個(gè)系統(tǒng)并不帶有定位的功能,它只負(fù)責(zé)進(jìn)行三維重建漱牵,即只有mapping沒(méi)有l(wèi)ocalization夺蛇。它需要一個(gè)具有定位功能的SLAM系統(tǒng)輸入當(dāng)前機(jī)器人的位姿估計(jì)。這既是缺點(diǎn)也是優(yōu)點(diǎn)酣胀。因?yàn)槲覀兛梢越o這個(gè)系統(tǒng)搭配任意一個(gè)SLAM系統(tǒng)刁赦。像上文提到的Elastic Fusion, BAD SLAM的另一個(gè)問(wèn)題就是因?yàn)樗麄兪腔诩円曈X(jué)的系統(tǒng)娶聘,當(dāng)你的相機(jī)有比較大的抖動(dòng)或者轉(zhuǎn)彎等情況時(shí),追蹤就容易丟失甚脉。而對(duì)于voxblox丸升,我們可以給它提供一個(gè)魯棒性很強(qiáng)的SLAM系統(tǒng)提供位姿估計(jì),一般來(lái)說(shuō)就是包含IMU的比如港科大的VINS牺氨,Kimera-VIO 或者ORBSLAM 3(不是2是3哦狡耻,在2020 年7月左右開(kāi)源,增加了對(duì)IMU等各種支持)猴凹。下圖就是我利用港科大的VINS+Voxblox夷狰,硬件上利用了Realsense相機(jī)+IMU,在室外進(jìn)行的CPU-Based的3維場(chǎng)景重建郊霎。
image.png

(場(chǎng)景拉大了看得不是很清楚沼头,有興趣的同學(xué)我可以截取一段視頻發(fā)送)

我們最容易直接想到的在計(jì)算機(jī)中進(jìn)行3維重建的就是點(diǎn)。一個(gè)場(chǎng)景可以由無(wú)數(shù)個(gè)點(diǎn)構(gòu)成點(diǎn)云书劝,參考2的Dense Visual SLAM即是如此瘫证。但是這種方法效率并不高。目前的Dense系統(tǒng)最常用的基本單元就是TSDF和Surfel(面片)庄撮。
Elastic Fusion背捌,Infinitam v3等是基于surfel的。我不會(huì)介紹surfel的相關(guān)內(nèi)容洞斯,感興趣的同學(xué)可以取查看Elastic Fusion等論文毡庆。voxblox就是基于TSDF的3維場(chǎng)景重建。
剛才我們提到直接利用點(diǎn)云進(jìn)行3維重建效率并不高烙如,原因的話(huà)舉個(gè)簡(jiǎn)單的栗子么抗。假設(shè)我們要重構(gòu)一個(gè)平面,平面上有10萬(wàn)個(gè)點(diǎn)亚铁,我們要儲(chǔ)存些點(diǎn)不僅需要相當(dāng)?shù)膬?nèi)存蝇刀,繪制的時(shí)候還很費(fèi)事,畢竟點(diǎn)這么多徘溢。然而對(duì)于這個(gè)平面吞琐,其實(shí)我們只需要知道四個(gè)頂點(diǎn)就可以了。省時(shí)又省事然爆。無(wú)論TSDF還是surfel站粟,都是以能觀測(cè)到的表面為基本進(jìn)行重建,而不是點(diǎn)云曾雕∨樱可能你說(shuō)又不是每個(gè)物體表面都是平的,是否忘了曲線(xiàn)的小段都可線(xiàn)性近似(手動(dòng)滑稽)。
這篇文章的后續(xù)切诀,會(huì)以github上最簡(jiǎn)單的開(kāi)源TSDF代碼為例講解其重構(gòu)的過(guò)程揩环,相對(duì)簡(jiǎn)單,在下篇文章幅虑,我們會(huì)追溯voxblox的代碼丰滑,來(lái)了解tsdf在比較成熟系統(tǒng)中的實(shí)際應(yīng)用。

基本的TSDF

我們把一個(gè)有限的三維空間想象成由M個(gè)小方塊堆疊而成翘单,假設(shè)每個(gè)小方塊長(zhǎng)寬高都為c cm吨枉,我們稱(chēng)他們?yōu)関oxel(體素)。這個(gè)三維空間在X,Y,Z維度上分別有w,h,l個(gè)小方塊哄芜。三維空間的體素類(lèi)比二維空間照片的像素貌亭。

三維空間的體素

(圖片來(lái)源于網(wǎng)絡(luò),侵刪)
我們不僅用直觀的x,y,z坐標(biāo)來(lái)表示一個(gè)體素认臊,我們用另外兩個(gè)值來(lái)表示體素
1:體素到最近表面的距離圃庭。如果體素在表面之外(更靠近相機(jī)一側(cè)),則為正值失晴,體素在表面之內(nèi)剧腻,則為負(fù)值。這也是SDF(signed distance function)名字的由來(lái)涂屁。下圖[3]更好地體現(xiàn)了這點(diǎn)
sdf.png

上圖是3維世界的2維俯視圖书在。可以看到很多小格子即體素拆又。其中\mathbf{x}是其中之一儒旬,淺綠色的折線(xiàn)為距離體素最近的一個(gè)表面,而\mathbf{p}點(diǎn)是距離\mathbf{x}最近的表面上的點(diǎn)帖族,兩點(diǎn)之間的距離|\mathbf{x} \mathbf{p}|即是體素\mathbf{x}的sdf栈源。我們的主題是tsdf(truncated sdf),這個(gè)'truncated'體現(xiàn)在哪里呢竖般?體現(xiàn)在我們會(huì)把具體最近表現(xiàn)太遠(yuǎn)或者距離相機(jī)太近的體素點(diǎn)的sdf給強(qiáng)行賦值為一個(gè)確定值甚垦。數(shù)學(xué)表達(dá)為,我們?nèi)藶槎x一個(gè)截?cái)嗑嚯xd
if(sdf > d) \ \ sdf = 1 \\ else \ \ if(sdf<-d ) \ \ sdf = -1
參考下圖
tsdf

同樣是俯視圖涣雕。我們可以看到艰亮,具體表面(紅線(xiàn)部分)遠(yuǎn)的值被賦值sdf如果為正,則被賦值為了1胞谭,如果為負(fù)則為-1.
2:sdf更新時(shí)的權(quán)重也被儲(chǔ)存在體素內(nèi)垃杖。

計(jì)算并更新sdf的方法目前主要分兩大類(lèi)[4],本文先介紹第一類(lèi)丈屹。
把位于全局坐標(biāo)系的體素根據(jù)變換矩陣轉(zhuǎn)換到當(dāng)前相機(jī)所在的坐標(biāo)系,再投影到2d的當(dāng)前圖像,獲得與之匹配的像素點(diǎn)旺垒。比較體素的深度z_v與像素在3維坐標(biāo)下的深度點(diǎn)z_p彩库。像素的深度值即是相機(jī)中心到該像素點(diǎn)對(duì)應(yīng)的表面的距離,而體素轉(zhuǎn)換到相機(jī)坐標(biāo)系后的深度值是體素到相機(jī)中心的距離先蒋,所以z_p - z_v就可以得到體素與像素對(duì)應(yīng)的3d點(diǎn)到相機(jī)中心的距離差骇钦,即sdf.png里的|\mathbf{p} \mathbf{x}|了。
我們記某個(gè)體素的tsdf為 D (\mathbf{x} )竞漾,其權(quán)重為W(\mathbf{x})眯搭,計(jì)算它的tsdf與權(quán)重的方法其實(shí)too simple (sometimes naive)。如下[5]
\begin{aligned} &D_i (\mathbf{x}) = \frac{\Sigma\omega_i( \mathbf{x}) d_i(\mathbf{x})}{\Sigma \omega_i(\mathbf{x})} \\ & W_i(\mathbf{x}) = \Sigma \omega_i(\mathbf{x}) \end{aligned} \tag{1}
上面的式子中i表示相機(jī)的幀數(shù)业岁,\omega_i表示當(dāng)前幀數(shù)的該體素的權(quán)重鳞仙,而d_i表示的是當(dāng)前幀數(shù)的tsdf”适保可以看到體素的權(quán)重僅僅是把之前所有幀得到的權(quán)重加起來(lái)棍好,而體素的tsdf只是把所有幀的權(quán)重乘以對(duì)應(yīng)幀得到的tsdf再除以權(quán)重和做平均而已。
實(shí)際操作中允耿,我們不可能等到相機(jī)運(yùn)行了100幀之后再做計(jì)算借笙,而是第二幀的體素由第一幀得到,第三幀的由第二幀得到较锡,如此增量計(jì)算业稼。所以實(shí)際運(yùn)用中的體素更新公式為(為了簡(jiǎn)便我省去了(\mathbf{x}),大家知道是針對(duì)單個(gè)體素即可)
\begin{aligned} & D_{i+1} = \frac{ W_{i}D_{i} + \omega_{i+1}d_{i+1}}{W_i + \omega_{i+1} } \\ & W_{i+1}(\mathbf{x}) = W_i + \omega_{i+1} \end{aligned} \tag{2}
式2只是寫(xiě)成了了式1的增量形式而已蚂蕴,實(shí)際是一個(gè)意思低散。其中每一幀的當(dāng)前權(quán)重很粗暴的直接設(shè)置成了1。即\omega_0 = ... = \omega_i = 1掂墓。相關(guān)論文也提到了雖然\omega_i的選取很重要谦纱,但是大多數(shù)時(shí)候另它等于1都能得到不錯(cuò)的效果,在一些著名的應(yīng)用中比如Kinect Fusion就是如此設(shè)置的(在voxblox中進(jìn)行了一些改進(jìn)君编,我們?cè)诜治鰒oxblox代碼時(shí)會(huì)講到)跨嘉。
TSDF的基本更新方法就是如此。
相機(jī)的每一幀到來(lái)時(shí)吃嘿,都會(huì)嘗試更新每個(gè)體素的值祠乃,每個(gè)體素都可以獨(dú)立更新,所以理所當(dāng)然地應(yīng)該應(yīng)用于GPU上兑燥。
下面我們參考一段tsdf重構(gòu)的基本代碼[6]tsdf代碼亮瓷。代碼是應(yīng)用于GPU上的CUDA代碼,不了解CUDA也沒(méi)有關(guān)系降瞳,我們會(huì)稍加解釋嘱支。主代碼只有一個(gè)文件蚓胸,實(shí)現(xiàn)tsdf基本功能,非常簡(jiǎn)單除师。
在代碼的demo.cu的主函數(shù)中沛膳,首先定義了輸入文件的位置

  // Location of camera intrinsic file
  std::string cam_K_file = "data/camera-intrinsics.txt";

  // Location of folder containing RGB-D frames and camera pose files
  std::string data_path = "data/rgbd-frames";
  int base_frame_idx = 150;
  int first_frame_idx = 150;
  float num_frames = 50;

  float cam_K[3 * 3];
  float base2world[4 * 4];
  float cam2base[4 * 4];
  float cam2world[4 * 4];
  int im_width = 640;
  int im_height = 480;
  float depth_im[im_height * im_width];

輸入有:每一幀2D圖像以及對(duì)應(yīng)的深度圖像,相機(jī)內(nèi)參汛聚,每一幀的位姿(TSDF的應(yīng)用比如Kinect Fusion一般會(huì)根據(jù)圖片的像素的匹配來(lái)估計(jì)位姿锹安,實(shí)現(xiàn)SLAM的功能,但這里的應(yīng)用我們只需要了解到tsdf本身怎樣使用就可以了倚舀,所以就使用已知位姿了)叹哭。
接下來(lái)設(shè)置我們一共有多少體素點(diǎn),以及每個(gè)體素大小是多少痕貌。

  // Voxel grid parameters (change these to change voxel grid resolution, etc.)
  float voxel_grid_origin_x = -1.5f; // Location of voxel grid origin in base frame camera coordinates
  float voxel_grid_origin_y = -1.5f;
  float voxel_grid_origin_z = 0.5f;
  float voxel_size = 0.006f;
  float trunc_margin = voxel_size * 5;
  int voxel_grid_dim_x = 500;
  int voxel_grid_dim_y = 500;
  int voxel_grid_dim_z = 500;

可以看到體素起點(diǎn)為(-1.5,-1.5,0.5)风罩,每個(gè)體素的大小為0.006(立方米),體素總數(shù)為長(zhǎng)×寬x高(500x500x500)芯侥。
看到這里我很自然地想到這個(gè)程序的tsdf應(yīng)用的一些限制泊交,
體素所能表示的環(huán)境的大小看來(lái)已經(jīng)被限制,一個(gè)體素一個(gè)維度的長(zhǎng)度為0.006m柱查,一共有500x500x500個(gè)體素廓俭,也就是我們能map的3D環(huán)境大小不能超過(guò)(0.006^3)X500X500X500 = 27立方米,每個(gè)維度不能超過(guò)0.006X500 = 3米唉工。這對(duì)于稍微大一點(diǎn)的室內(nèi)環(huán)境都不夠研乒,更別說(shuō)室外大場(chǎng)景了。
我們可以增大voxel_size淋硝,但這會(huì)意味著每一個(gè)體素所表示的空間體積增大雹熬,精度降低。我們可以增大體素的個(gè)數(shù)比如500改為1000甚至更大,這對(duì)于室內(nèi)場(chǎng)景可行谣膳。但是我們需要把這些體素拷貝到GPU中竿报,小的GPU存量幾個(gè)G,大的十幾個(gè)G继谚,體素過(guò)多烈菌,對(duì)于室外場(chǎng)景,如果還保持高精度花履,GPU的是難以?xún)?chǔ)存并計(jì)算的芽世。
另外一個(gè)維度的長(zhǎng)度被固定地死死的,超一丟丟都不行诡壁,能不能讓這個(gè)尺寸能動(dòng)態(tài)變化呢?
這些問(wèn)題放到第二講討論济瓢。這一講我們只走完這個(gè)基本程序。

定義完體素的長(zhǎng)寬高后妹卿,把所有體素的tsdf,weight都初始化為0并拷貝到GPU里旺矾。

// Initialize voxel grid
...
 memset(voxel_grid_weight, 0, sizeof(float) * voxel_grid_dim_x * voxel_grid_dim_y * voxel_grid_dim_z);
...
  cudaMalloc(&gpu_depth_im, im_height * im_width * sizeof(float));
  checkCUDA(__LINE__, cudaGetLastError());

對(duì)于不太了解CUDA的朋友可以省去看這段蔑鹦。簡(jiǎn)要理解為,CPU和GPU作為兩個(gè)設(shè)備宠漩,我們要使用GPU計(jì)算就需要把CPU的東西拷貝到GPU里面去举反。
之后進(jìn)入for循環(huán)懊直,讀取每一幀的rgbd圖像以及位姿扒吁,更新tsdf

// Loop through each depth frame and integrate TSDF voxel grid
  for (int frame_idx = first_frame_idx; frame_idx < first_frame_idx + (int)num_frames; ++frame_idx){
...
}

for循環(huán)里會(huì)調(diào)用函數(shù)

Integrate <<< voxel_grid_dim_z, voxel_grid_dim_y >>>...

這個(gè)函數(shù)會(huì)在GPU里執(zhí)行對(duì)應(yīng)代碼開(kāi)頭的Integrate函數(shù),用來(lái)對(duì)tsdf累積更新

__global__
void Integrate(...){
...}

下面我們繼續(xù)對(duì)integrate函數(shù)進(jìn)行解讀室囊,函數(shù)的前兩行

  int pt_grid_z = blockIdx.x;
  int pt_grid_y = threadIdx.x;

把block和thread id賦值給了pt_grid_z和pt_grid_y雕崩。接著可以看到for循環(huán)針對(duì)x維度進(jìn)行循環(huán)

for (int pt_grid_x = 0; pt_grid_x < voxel_grid_dim_x; ++pt_grid_x)

這表示作者想用GPU里的每一個(gè)線(xiàn)程,去處理y,z固定融撞,僅僅有x變化的一條線(xiàn)上的體素盼铁,即500個(gè)體素。進(jìn)入for循環(huán)后尝偎,可以看到針對(duì)一條線(xiàn)上的每個(gè)體素

    // Convert voxel center from grid coordinates to base frame camera coordinates
    float pt_base_x = voxel_grid_origin_x + pt_grid_x * voxel_size;
    float pt_base_y = voxel_grid_origin_y + pt_grid_y * voxel_size;
    float pt_base_z = voxel_grid_origin_z + pt_grid_z * voxel_size;
    // Convert from base frame camera coordinates to current frame camera coordinates
       float tmp_pt[3] = {0};
    tmp_pt[0] = pt_base_x - cam2base[0 * 4 + 3];
    tmp_pt[1] = pt_base_y - cam2base[1 * 4 + 3];
    tmp_pt[2] = pt_base_z - cam2base[2 * 4 + 3];
    float pt_cam_x = cam2base[0 * 4 + 0] * tmp_pt[0] + cam2base[1 * 4 + 0] * tmp_pt[1] + cam2base[2 * 4 + 0] * tmp_pt[2];
    float pt_cam_y = cam2base[0 * 4 + 1] * tmp_pt[0] + cam2base[1 * 4 + 1] * tmp_pt[1] + cam2base[2 * 4 + 1] * tmp_pt[2];
    float pt_cam_z = cam2base[0 * 4 + 2] * tmp_pt[0] + cam2base[1 * 4 + 2] * tmp_pt[1] + cam2base[2 * 4 + 2] * tmp_pt[2];

會(huì)先把它投影到base frame饶火,這個(gè)base frame是相機(jī)的初始坐標(biāo)。再根據(jù)相機(jī)的當(dāng)前位姿致扯,投影到相機(jī)的當(dāng)前坐標(biāo)肤寝。此時(shí),就可以得到該體素到相機(jī)的距離pt_cam_z了抖僵。
之后會(huì)再將體素投影到當(dāng)前相機(jī)的二維平面鲤看,找到與之對(duì)應(yīng)的pixel,獲取那個(gè)pixel的深度值depth_val

    int pt_pix_x = roundf(cam_K[0 * 3 + 0] * (pt_cam_x / pt_cam_z) + cam_K[0 * 3 + 2]);
    int pt_pix_y = roundf(cam_K[1 * 3 + 1] * (pt_cam_y / pt_cam_z) + cam_K[1 * 3 + 2]);
    if (pt_pix_x < 0 || pt_pix_x >= im_width || pt_pix_y < 0 || pt_pix_y >= im_height)
      continue;

    float depth_val = depth_im[pt_pix_y * im_width + pt_pix_x];

depth_val和pt_cam_z只差即是我們之前提到的sdf |\mathbf{p}\mathbf{x}|了耍群。小于某個(gè)值則 忽略它(這和之前直接賦值為+-1有些不一樣义桂,但是)

    if (diff <= -trunc_margin)
      continue;

之后對(duì)sdf進(jìn)行截?cái)?/p>

float dist = fmin(1.0f, diff / trunc_margin);

diff呢就是咱們的TSDF了。下面幾行代碼就妥妥的對(duì)應(yīng)式2了

    float weight_old = voxel_grid_weight[volume_idx];
    float weight_new = weight_old + 1.0f;
    voxel_grid_weight[volume_idx] = weight_new;
    voxel_grid_TSDF[volume_idx] = (voxel_grid_TSDF[volume_idx] * weight_old + dist) / weight_new;

利用上一幀得到的tsdf的 weight和tsdf的值更新當(dāng)前tsdf的值蹈垢。
我們可以看到慷吊,每一幀圖像進(jìn)來(lái)時(shí),我們是對(duì)每一個(gè)體素都進(jìn)行了更新的曹抬,計(jì)算量很大溉瓶。如果場(chǎng)景小還好,場(chǎng)景如果很大沐祷,可能很多體素是不需要再進(jìn)行更新的嚷闭,因?yàn)樗麄兘?jīng)過(guò)投影不會(huì)投影到當(dāng)前的相機(jī)平面的。

至此基本的tsdf的更新就講解完畢赖临。那么有了這些tsdf胞锰,我們是如何把3d場(chǎng)景再還原出來(lái)的呢?
在主函數(shù)的for循環(huán)結(jié)束后兢榨,我們會(huì)進(jìn)入下面幾行程序

  // Load TSDF voxel grid from GPU to CPU memory
  cudaMemcpy(voxel_grid_TSDF, gpu_voxel_grid_TSDF, voxel_grid_dim_x * voxel_grid_dim_y * voxel_grid_dim_z * sizeof(float), cudaMemcpyDeviceToHost);
  cudaMemcpy(voxel_grid_weight, gpu_voxel_grid_weight, voxel_grid_dim_x * voxel_grid_dim_y * voxel_grid_dim_z * sizeof(float), cudaMemcpyDeviceToHost);
  checkCUDA(__LINE__, cudaGetLastError());

把我們所有體素的tsdf從GPU拷貝會(huì)CPU嗅榕。之后根據(jù)體素重建表面

  SaveVoxelGrid2SurfacePointCloud("tsdf.ply", voxel_grid_dim_x, voxel_grid_dim_y, voxel_grid_dim_z, 
                                  voxel_size, voxel_grid_origin_x, voxel_grid_origin_y, voxel_grid_origin_z,
                                  voxel_grid_TSDF, voxel_grid_weight, 0.2f, 0.0f);

這個(gè)函數(shù)在utils.hpp里顺饮,下面我們進(jìn)入這個(gè)函數(shù)

// Compute surface points from TSDF voxel grid and save points to point cloud file
void SaveVoxelGrid2SurfacePointCloud(const std::string &file_name, int voxel_grid_dim_x, int voxel_grid_dim_y, int voxel_grid_dim_z,
                                     float voxel_size, float voxel_grid_origin_x, float voxel_grid_origin_y, float voxel_grid_origin_z,
                                     float * voxel_grid_TSDF, float * voxel_grid_weight,
                                     float tsdf_thresh, float weight_thresh) {

...
  // Create point cloud content for ply file
  for (int i = 0; i < voxel_grid_dim_x * voxel_grid_dim_y * voxel_grid_dim_z; i++) {

    // If TSDF value of voxel is less than some threshold, add voxel coordinates to point cloud
    if (std::abs(voxel_grid_TSDF[i]) < tsdf_thresh && voxel_grid_weight[i] > weight_thresh) {
}
...

}

其實(shí)截出來(lái)的兩行代碼說(shuō)明了一切。
從tsdf再重構(gòu)表面的方法是
1:遍歷所有的體素凌那。
2:如果每個(gè)體素的tsdf小于某個(gè)數(shù)值(代表它足夠接近一個(gè)表面)并且它的權(quán)重大于某個(gè)數(shù)值(體素的權(quán)重每次更新時(shí)加1兼雄,大于某個(gè)數(shù)值代表更新次數(shù)足夠)
那么我們會(huì)把該體素對(duì)應(yīng)的3d位置轉(zhuǎn)換為3d點(diǎn)云,最后把點(diǎn)云畫(huà)出即能得到我們重構(gòu)的表面帽蝶。

總體來(lái)說(shuō)赦肋,最基本的tsdf代碼還是非常straigtforward的,但也因此暴露出來(lái)了一些的問(wèn)題励稳。根據(jù)前面的講解我們?cè)倏偨Y(jié)一下
1:體素的大小需要預(yù)先設(shè)定并固定佃乘。這意味著場(chǎng)景超出體素涵蓋的范圍時(shí),將不能重建驹尼。
2:我們利用體素重構(gòu)表面時(shí)趣避,理論上需要選tsdf為0的點(diǎn),這些點(diǎn)才在表面上新翎。但這幾乎是不能滿(mǎn)足的程帕,所以參考代碼退而求其次,選取tsdf小于某個(gè)數(shù)值的體素地啰。這種做法比較粗糙也會(huì)損失不少的精度愁拭。成熟的tsdf based 3d重建實(shí)際不會(huì)直接用點(diǎn)云常見(jiàn)。
這兩個(gè)問(wèn)題我們會(huì)在介紹voxblox里說(shuō)到髓绽。

但其實(shí)只要你想重建的環(huán)境足夠小敛苇,上面的問(wèn)題都不是問(wèn)題。因?yàn)槟愕膙oxel_size只要夠小顺呕,tsdf也會(huì)足夠精確枫攀,重建也會(huì)足夠精確。但是我最近一些應(yīng)用希望把tsdf應(yīng)用到室內(nèi)/室外較大的場(chǎng)景株茶,就需要額外考慮很多東西来涨。

參考(參考鏈接可能需要科學(xué)上網(wǎng)):
(1) Elastic Fusion
(2) Dense Visual SLAM
(3) Truncated Signed Distance Function: Experiments on Voxel Size
(4) voxblox
(5) A Volumetric Method for Building Complex Models from Range Images
(6) https://github.com/andyzeng/tsdf-fusion

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市启盛,隨后出現(xiàn)的幾起案子蹦掐,更是在濱河造成了極大的恐慌,老刑警劉巖僵闯,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件卧抗,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡鳖粟,警方通過(guò)查閱死者的電腦和手機(jī)社裆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)向图,“玉大人泳秀,你說(shuō)我怎么就攤上這事标沪。” “怎么了嗜傅?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵金句,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我吕嘀,道長(zhǎng)违寞,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任币他,我火速辦了婚禮坞靶,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘蝴悉。我一直安慰自己,他們只是感情好瘾敢,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布拍冠。 她就那樣靜靜地躺著,像睡著了一般簇抵。 火紅的嫁衣襯著肌膚如雪庆杜。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,125評(píng)論 1 297
  • 那天碟摆,我揣著相機(jī)與錄音晃财,去河邊找鬼。 笑死典蜕,一個(gè)胖子當(dāng)著我的面吹牛断盛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播愉舔,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼钢猛,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了轩缤?” 一聲冷哼從身側(cè)響起命迈,我...
    開(kāi)封第一講書(shū)人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎火的,沒(méi)想到半個(gè)月后壶愤,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡馏鹤,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年征椒,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片假瞬。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡陕靠,死狀恐怖迂尝,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情剪芥,我是刑警寧澤垄开,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站税肪,受9級(jí)特大地震影響溉躲,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜益兄,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一锻梳、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧净捅,春花似錦疑枯、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至国章,卻和暖如春具钥,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背液兽。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工骂删, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人四啰。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓宁玫,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親拟逮。 傳聞我的和親對(duì)象是個(gè)殘疾皇子撬统,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容

  • 在tsdf的第一講里我們講解了最基本的TSDF重構(gòu)算法。也提出了算法的一些不足之處敦迄。1:體素的大小需要預(yù)先設(shè)定并固...
    陳瓜瓜_ARPG閱讀 2,584評(píng)論 5 7
  • voxblox結(jié)構(gòu)圖 上一講我們說(shuō)過(guò) 函數(shù)負(fù)責(zé) 中的TSDF Integrator部分恋追,而 函數(shù)負(fù)責(zé) 部分。這一講...
    陳瓜瓜_ARPG閱讀 1,436評(píng)論 0 3
  • 題目:?jiǎn)文恳曈X(jué)-慣性融合自動(dòng)導(dǎo)航 摘要 自主微型飛行器(MAV)具有成本和機(jī)動(dòng)性?xún)?yōu)勢(shì)罚屋,使其成為航空攝影苦囱,監(jiān)視和搜索...
    陌上塵離閱讀 2,816評(píng)論 0 1
  • 三維重建技術(shù)的綜述 三維重建技術(shù)通過(guò)深度數(shù)據(jù)獲取、預(yù)處理脾猛、點(diǎn)云配準(zhǔn)與融合撕彤、生成表面等過(guò)程,把真實(shí)場(chǎng)景刻畫(huà)成符合計(jì)算...
    劉優(yōu)秀_cfc6閱讀 2,460評(píng)論 0 3
  • 建設(shè)中,記錄日常學(xué)習(xí)到的碎片羹铅,最后整理 什么是三維重建蚀狰? 這里指的三維重建是基于對(duì)環(huán)境或者物體的一系列不同角度的照...
    啊呀喲嘿閱讀 22,950評(píng)論 3 1