JOVANA
Explore Library Glossary Getting Started Three Levels Fields How it works Mission
Join the mission
All guides

神經 3D:學習式深度、NeRF 與高斯潑濺

踏入現代——神經網路從單張影像預測深度,並以 NeRF 與高斯潑濺渲染擬真的 3D 場景。

從單張影像學出深度

在本軌道第一篇導覽裡,我們撞上了一道硬牆:單張照片無法告訴你「公制深度」。針孔相機把一整條 3D 射線壓成一個像素,因此近處的玩具車與遠處的真車可能畫出完全相同的像素。光靠幾何就卡住了——這正是我們後來需要兩個視角(立體視覺)與多個視角(SfM)的原因。然而你只看一張普通快照,卻能立刻感覺到哪裡近、哪裡遠。怎麼辦到的?

你並不是在「量測」——你是在回憶。你的大腦倚賴學到的線索:朝消失點匯聚的線條(透視)、距離越遠紋理越細(紋理梯度)、由熟悉真實尺寸洩露距離的物體,以及近物會遮擋遠物這個事實。這些都不來自三角測量,而來自你一生看世界的經驗。深度網路用的正是同一招,我們稱之為 單張影像深度預測

具體做法是:蒐集巨量的影像,每張都配上已知的深度圖(來自深度感測器、立體相機組或 LiDAR 掃描)。我們訓練一個卷積或 transformer 網路,使它在測試時只看 RGB 影像,就能輸出每個像素的深度。網路不是一條條解幾何方程,而是吸收了一個統計先驗——「長這樣的影像,通常深度長那樣」。這整個任務家族就是 深度估計,而其中「單張影像、靠學習」的版本最像魔法。

# Monocular depth at inference: one RGB image in, one depth map out.
# No second camera, no triangulation — just a learned prior.
img = load_rgb("holiday_photo.jpg")        # H x W x 3
depth = depth_net(img)                      # H x W, predicted (often RELATIVE) depth

# Scale ambiguity: depth is correct up to an unknown factor s.
# If we know ONE true distance (e.g. a measured object), we can anchor it:
s = true_distance_to_marker / depth[marker_y, marker_x]
metric_depth = s * depth                    # now in metres (approximately)
單張影像深度只需一次前向傳遞——但要換成公尺,你得先錨定那個未知的尺度。

優點顯而易見又討喜:它能在一張隨手拍的手機照片上運作,不需相機組、不需第二視角、不需相機移動。失敗模式同樣富有教育意義。學到的先驗只與它見過的東西一樣好——給它一個訓練裡從沒有過的場景(空拍、顯微影像、鏡子、艾雪的階梯),它可能自信地幻想出錯誤深度。幾何從不說謊,卻常常答不出來;學習式深度總是給答案,卻可能自信地答錯。當我們從「預測深度」邁向「渲染整個世界」時,請把這兩個真理都記在心裡。

把場景表示成一個函數:NeRF

到目前為止,我們遇到的每一種 3D 表示都是顯式的:點雲(一袋點),或第 4 篇裡由點縫成三角形的網格。你可以指著一個頂點說「世界的那一塊就在這裡」。神經輻射場(NeRF)把這種直覺丟掉,換上一個激進的想法:把場景儲存成的不是「東西」,而是一個函數。一個小型神經網路本身就是場景。

想像房間裡瀰漫著一團會發光的魔法霧。你可以走到空間中任何一個點(無論空著還是被佔),朝任何方向看,並問兩個問題:「我在這裡看到的是什麼顏色?」以及「這個位置有多實心、多不透明?」一個 神經輻射場 就是一個函數,對每個點、每個視角方向,恰好回答這兩個問題。訓練得好,它就把整個 3D 場景記在幾 MB 的網路權重裡——相較於稠密網格做 三維重建,這是個驚人地精巧的替代方案。

NeRF 就是一個多層感知器:餵入一個 3D 點與一個視角方向,回傳顏色與密度。

示意圖:一個 3D 位置與一個方向箭頭進入一個小型多層網路,網路輸出一個 RGB 顏色與一個純量密度值。

F_{\Theta}:\;(\mathbf{x},\,\mathbf{d})\;\longmapsto\;(\mathbf{c},\,\sigma)

整個場景,被壓進這一個小函數裡。

讓我們逐個符號讀。左邊的輸入是一個 5 維查詢:\mathbf{x}=(X,Y,Z) 是空間中的一個 3D 點(你站在霧裡的位置),\mathbf{d} 是給出視角方向的單位向量(你正朝哪看)。函數 F_{\Theta} 是一個小型多層感知器,其行為由學到的權重 \Theta 決定——這些權重就是被記下的場景。輸出是 \mathbf{c}=(r,g,b),即該點朝你發出的顏色,以及 \sigma,即體積密度——粗略地說,就是 \mathbf{x} 處塞了多少不透明的物質。具體讀法:查詢 \mathbf{x}= 花瓶口、\mathbf{d}= 從正面看;輸出 \mathbf{c}= 釉面的奶白色與一個大的 \sigma(實心)。查詢半空中的一點,則 \sigma\approx 0(空的,光直接穿過)。

請注意設計裡刻意安排的不對稱:顏色 \mathbf{c} 同時依賴位置與方向,而密度 \sigma 依賴位置。這是物理,不是怪癖。某點是否有物質,不會因你從哪看而改變——牆無論你站哪個角度都是實心的。但你看到的顏色可以隨角度改變:光亮的茶壺會打出一個明亮的鏡面高光,當你移動時它會在表面上滑動。讓 \mathbf{c} 依賴方向,正是 NeRF 能捕捉到「會反光、隨視角變」外觀的原因,而這是一般上色網格做不到的。現在我們有了描述霧的函數;下一節將示範如何讓射線在霧中行進,把它變成一張真正的圖。

體積渲染:把場變成影像

我們有了一個處處都知道顏色與密度的函數,但函數不是照片。要得到單一像素,我們借用第 1 篇的相機模型並把它反過來:不是問世界中某點落在感測器哪裡,而是從相機中心穿過那個像素,朝場景射出一條射線。接著我們沿射線取許多樣本點,在每個點查詢 NeRF 的顏色與密度,再把它們合成成一個最終顏色。這個配方稱為 體積渲染,是 NeRF 的數學核心——所以我們會慢慢來。

關鍵的新觀念是透射率:光從某點回到相機的旅程中,能存活下來多少。想像你盯著一層層的霧看。深處某物發出的光,必須穿過它前面的一切,每經過一塊稠密區就被多吸收一點。所以一個雖然稠密(強烈發光)但坐落在其他稠密物後面的點,幾乎沒有貢獻——它被遮住了。一個稠密、且前方是清澈空氣的點,則貢獻很多。透射率就是這套記帳法,它自動讓近處表面正確地遮住遠處表面。

沿一條射線取樣多個點,讀出顏色與密度,再以「光的存活率」加權累積。

一條從相機射出的射線穿過體積中的取樣點;每個點以其密度、以及前方光的存活程度加權,貢獻顏色。

C(\mathbf{r})=\int_{t_n}^{t_f} T(t)\,\sigma\!\big(\mathbf{r}(t)\big)\,\mathbf{c}\!\big(\mathbf{r}(t),\mathbf{d}\big)\,dt,\qquad T(t)=\exp\!\Big(\!-\!\int_{t_n}^{t}\sigma\!\big(\mathbf{r}(s)\big)\,ds\Big)

體積渲染積分:每個點的顏色,乘上它有多實心、以及有多少光到達它。

由左讀到右。射線是 \mathbf{r}(t)=\mathbf{o}+t\,\mathbf{d}:從相機原點 \mathbf{o} 出發,朝視角方向 \mathbf{d} 走距離 t;積分從近界 t_n 到遠界 t_f。在每個深度 t,NeRF 給出密度 \sigma(\mathbf{r}(t)) 與顏色 \mathbf{c}(\mathbf{r}(t),\mathbf{d})。因子 T(t) 是透射率——光從相機到距離 t 的存活比例。它等於 e^{-(\text{至今穿過的總密度})}:若你已穿過很多 \sigma,指數就非常負、T 接近 0,於是更遠處的一切都被壓暗到沒有。把這三項相乘——T(t)(光到得了嗎?)、\sigma(這裡有物質嗎?)、\mathbf{c}(它是什麼顏色?)——沿射線積分,就得到 C(\mathbf{r}),也就是那一個像素的最終顏色。

\hat{C}(\mathbf{r})=\sum_{i=1}^{N} T_i\,\alpha_i\,\mathbf{c}_i,\qquad \alpha_i = 1-e^{-\sigma_i\delta_i},\qquad T_i=\prod_{j=1}^{i-1}\big(1-\alpha_j\big)

同樣的想法,化為沿射線 N 個取樣點的可計算總和。

電腦無法做真正的積分,於是我們把射線切成 N 個樣本,化為加權總和。對樣本 i\delta_i 是到下一個樣本的間距,\sigma_i 是它的密度,\mathbf{c}_i 是它的顏色。項 \alpha_i = 1-e^{-\sigma_i\delta_i} 是該樣本的不透明度——讀作「這一小塊板子有多實心?」若 \sigma_i\delta_i 很大,\alpha_i\to 1(完全不透明,像牆);若接近 0,\alpha_i\to 0(透明的空氣)。項 T_i=\prod_{j<i}(1-\alpha_j) 是離散透射率:把前面每塊板子的「透得過程度」(1-\alpha_j) 連乘。於是每個樣本的權重 T_i\,\alpha_i 恰好讀作「有多可見 × 有多實心」。小小範例:三個樣本,\alpha=(0.1,\,0.9,\,0.8)。第一個貢獻權重 1\times0.1=0.1;第二個 (1-0.1)\times0.9=0.81;第三個 (1-0.1)(1-0.9)\times0.8=0.072。中間那個表面主導一切,而第三個——躲在不透明的第二個後面——幾乎無感。這就是遮擋,免費算出來。

訓練 NeRF

場景從哪來?你交給系統一組從許多角度拍下、針對某物件或某房間的普通照片——而且關鍵在於,還要附上每張照片的相機姿態(它在空間中的位置與朝向)。這裡有個漂亮的呼應:這些姿態正來自第 1 篇與第 4 篇的流程——以相機校正取得內參,以「運動回復結構」(SfM)復原每張照片在哪拍的。古典幾何負責測量,神經場負責渲染。它們是夥伴,不是對手。

  1. 從訓練照片中挑一批像素。對每個像素,用已知的姿態與內參建出它的相機射線(第 1 篇)。
  2. 沿每條射線取樣多個點,並在每個點向 NeRF 查詢顏色與密度。
  3. 用可微分的體積渲染器合成,得到預測的像素顏色 \hat{C}(\mathbf{r})
  4. 用光度損失把 \hat{C} 與真實像素顏色比較,再反向傳播去微調權重 \Theta
  5. 重複數十萬次迭代,直到渲染結果在每個視角都吻合照片。
\mathcal{L}=\sum_{\mathbf{r}\in\mathcal{R}}\big\lVert\,\hat{C}(\mathbf{r})-C_{\text{gt}}(\mathbf{r})\,\big\rVert_2^{2}

光度損失:讓渲染出的像素吻合真實像素。

這個損失簡單到幾乎令人不好意思,而這正是它美的一部分。總和遍歷一批射線 \mathcal{R}——每個訓練像素對應一條射線。對每條射線,\hat{C}(\mathbf{r}) 是我們體積渲染器產生的顏色,C_{\text{gt}}(\mathbf{r}) 是那個像素在真實照片裡的標準答案顏色。雙豎線 \lVert\cdot\rVert_2^2 意思是「兩個 RGB 顏色之間的平方距離」——例如預測 (0.8,0.2,0.2) 對真實 (0.9,0.1,0.2),得 (0.1)^2+(0.1)^2+0^2=0.02。把所有取樣像素加總並最小化。因為從 \Theta\hat{C} 的每一步都可微分,梯度下降能把誤差一路傳回權重裡。令人驚嘆之處:完全沒有 3D 監督——沒有人告訴網路真實幾何。它純粹從「渲染後要像這些視角下的 2D 照片」這個要求,推斷出整個 3D 結構。

有一項配料默默決定了結果是銳利還是糊成一團:位置編碼。若你把原始座標 (X,Y,Z) 直接餵進 MLP,它會產生過度平滑、褪色的場景——網路天生偏好低頻、緩慢變化的函數,因此無法表現銳利的邊緣或精細的紋理。解法是在網路看到座標之前,先把每個座標升維成一組高頻的正弦與餘弦特徵,給它能表達快速變化的原料。

位置編碼把一個普通座標升維成許多正弦/餘弦頻率,讓網路能表達銳利的細節。

一個純量座標被展開成一個由逐漸升高頻率的正弦與餘弦值組成的向量。

三維高斯潑濺:又快又顯式

現在來認識當前的最先進技術,它把 NeRF 的核心抉擇整個翻轉過來。NeRF 是隱式且慢的:場景隱形地住在網路權重裡,而你為每個像素付出沉重的逐射線網路查詢。三維高斯潑濺 回到顯式表示——但是個聰明的顯式。場景是一團由數百萬個微小 3D 斑點組成的雲,每個斑點是一個柔軟模糊的橢球,攜帶自己的位置、形狀、顏色與不透明度。沒有任何隱式成分,渲染時也沒有 MLP 要評估。

G(\mathbf{x})=\exp\!\Big(\!-\tfrac{1}{2}\,(\mathbf{x}-\boldsymbol{\mu})^{\top}\,\Sigma^{-1}\,(\mathbf{x}-\boldsymbol{\mu})\Big)

一個 3D 高斯:中心在 μ、由協方差 Σ 塑形的柔軟橢球斑點。

這就是你以前見過的鐘形高斯,現在搬到 3D。這裡 \mathbf{x} 是任一查詢點,\boldsymbol{\mu} 是斑點的中心——它在場景中的 3D 位置——而 \Sigma 是一個 3\times3協方差矩陣,決定斑點的大小、形狀與朝向(把它想成一個被拉伸、傾斜的橢球,而非完美的球)。式中 (\mathbf{x}-\boldsymbol{\mu})^{\top}\Sigma^{-1}(\mathbf{x}-\boldsymbol{\mu}) 是離中心的平方距離,但被 \Sigma^{-1} 扭曲了,使它沿橢球長軸與短軸對距離有不同的計法。接著 \exp(-\tfrac12 \cdot) 讓斑點的影響 G(\mathbf{x}) 在中心恰為 1,並隨著遠離平滑地淡到 0——中間最濃、邊緣柔軟、沒有硬邊界。除了 \boldsymbol{\mu}\Sigma,每個高斯還儲存一個顏色(常常依視角而定,因此也能做鏡面高光)與一個不透明度。數百萬個這樣的斑點合在一起,就是場景。

為什麼這麼快?渲染是光柵化,不是射線行進。回想第 1 篇:相機把一個 3D 點投影成一個 2D 像素;這裡我們把每一整個高斯——連同 \Sigma——投影成影像上的一個 2D 橢圓。那個被投影到螢幕空間的橢圓就是「潑濺(splat)」。GPU 天生就擅長以極快速度繪製數百萬個這種 2D 基本圖元。我們把這些潑濺按深度排序,再用體積渲染裡完全相同的 alpha 合成規則由前往後混合:\hat{C}=\sum_i T_i\,\alpha_i\,\mathbf{c}_i,其中每個 \alpha_i 現在來自高斯自身的不透明度乘上它在該像素的衰減。任何地方都沒有逐像素的網路呼叫——這正是它能即時、每秒數百幀渲染的原因,而 NeRF 卻在爬行。

這些高斯怎麼找出來?用訓練 NeRF 的同一個想法:可微分渲染加上光度損失。你從一個稀疏點雲開始(很方便地,就用第 4 篇 SfM 的點),在每個點放一個高斯,渲染,與照片比較,再對每個高斯的位置、協方差、顏色與不透明度做梯度下降。巧妙之處在於自適應密度控制:訓練過程中,優化器會在重建不足、需要更多細節的區域複製並分裂高斯,並修剪那些已幾乎透明或沒用的高斯。這個集合會自己長大或縮小以貼合場景。整件事最好想成:用數不盡、柔軟、有色、半透明的模糊點來畫一個場景——鋪得夠多,圖就銳化成逼真的照片。

# Sketch of a Gaussian-splatting training step (one batch of pixels).
for step in range(num_iters):
    cam = sample_training_camera()           # known pose from SfM (Guide 4)
    splats_2d = project_gaussians(gaussians, cam)   # 3D ellipsoid -> 2D screen ellipse
    splats_2d.sort(key=depth)                # front-to-back
    pred = alpha_blend(splats_2d)            # same compositing as volume rendering
    loss = photometric(pred, cam.photo)      # ||pred - ground_truth||^2 (+ SSIM term)
    loss.backward()                          # differentiable splatting
    optimizer.step()                         # update mu, Sigma, colour, opacity
    gaussians = densify_and_prune(gaussians) # clone/split where blurry, drop the useless
損失哲學與 NeRF 相同,但用的是顯式斑點與 GPU 光柵化,而非 MLP。

三維視覺的未來

退一步,看看我們走過的整片地景。古典幾何(第 1–4 篇)可解讀、公制、且節省:從少數幾張影像就能以真實單位復原真實距離,而且你能稽核每一步——一個消失點、一條對極線、一個三角測量出的點。它的弱點在外觀與密度:它難以處理會反光、隨視角變的表面,本身也不會給你一個逼真、可渲染的世界。神經方法(本篇)則是它的鏡像:NeRF 與高斯潑濺帶來令人驚掉下巴的逼真感與流暢的新視角,但它們渴求大量影像、吃重運算,也較難解讀或量測。

重點在於它們是互補,而非競爭。本篇的兩半其實是互相交棒:現代逼真系統先用古典的 SfM 估計相機姿態與一個起始點雲,再把它們餵進 神經輻射場高斯潑濺 模型來渲染。幾何負責測量世界,神經場負責替它上色。今天最好的流程拒絕在兩者間二選一。

這個領域往哪走?好幾條戰線同時快速推進。即時與裝置端 3D:高斯潑濺已能在瀏覽器與手機上運行,把擷取的空間裝進你口袋。生成式 3D:把一行文字或一張影像變成完整 3D 資產的模型,將 2D 生成軌道的擴散想法與你剛學到的渲染器結合。動態與可編輯場景:把這些表示沿時間延伸到會動的人與物,並讓它們像 3D 模型一樣好編輯。而應用的拉力極大——需要公制、可導航地圖的機器人;要求以每秒 90 幀呈現逼真世界的 AR/VR;融合幾何與學習以三維方式理解街道的自駕車。