Gaussian Splatting and beyond

about 2 months ago
graphicsai

Novel view synthesis cũng ra mắt được khá lâu rồi, nhưng mà gần đây làm nghiên cứu liên quan Bundle Adjustment nên mới tìm hiểu kĩ, với cả chạy thử luôn nữa.

Idea đơn giản là có nhiều "3d blob" trong không gian. Mỗi blob giống kiểu 1 nguồn sáng nhỏ lan ra xung quanh. Tưởng tượng nó giống kiểu một khối năng lượng phát sáng, nhưng không có "chân thể á". Data structure cho cái unit elements này như thế này:

class GaussianBlob: glm::vec3 pos; // trọng tâm của blob glm::vec4 quat; // quarternion? nói chung là một continuous representation của phép xoay glm::vec3 scale; // scale theo 3 chiều trong không gian glm::vec3 color; // màu rgb cơ bản float opacity; // độ mờ (kiểu 1 cái proxy cho intensity của nguồn sáng, màu càng mờ tức là blob càng ít sáng)

Còn với math convention thì mình dùng kí hiệu khác:

μ:trọng taˆmΣ:ma trận positive definite biểu dieˆ˜n cả quat vaˋ scale−Σ=R.diag(s)R:ma trận xoay - tương đương với quarternion qC:maˋuα:opacity\begin{align*} \mu &: \text{trọng tâm}\\ \Sigma &: \text{ma trận positive definite biểu diễn cả quat và scale} - \Sigma = R.\text{diag}(s)\\ R &: \text{ma trận xoay - tương đương với quarternion } q\\ C &: \text{màu}\\ \alpha &: \text{opacity} \end{align*}

Vậy nếu có một vài 3d blob trong không gian, và ta nhìn từ một hướng nhất định, ta sẽ thấy gì?

À thực ra motivation cho câu hỏi này khá đơn giản, là trên máy tính thì mỗi pixel chỉ hiện thị được một màu thôi. Và mỗi pixel thực ra tương đương với 1 tia từ mắt (xấp xỉ thôi chứ không chính xác, nếu đổi góc nhìn vào màn hình là nó không còn giống "nhìn qua cửa sổ" nữa).

Ok, vậy thì giả sử chỉ có một blob (μ,Σ,C,α)(\mu, \Sigma, C, \alpha), và có một tia x=r(t)=e+tdx=r(t)=e+td (e là eye đó :>).

Ta quy ước Δ=e−μ\Delta = e-\mu, vậy density của xx sẽ là:

G(x)=exp⁡(−12(x−μ)TΣ−1(x−μ))=exp⁡(−12(Δ+td)TΣ−1(Δ+td))=exp⁡(−12(at2+2bt+c))=exp⁡(−a(t−t∗)2+q∗2)\begin{align*} G(x) &= \exp\left(-\frac{1}{2}(x-\mu)^T\Sigma^{-1}(x-\mu)\right)\\ &= \exp\left(-\frac{1}{2}(\Delta+td)^T\Sigma^{-1}(\Delta+td)\right)\\ &= \exp\left(-\frac{1}{2}(at^2+2bt+c)\right)\\ &= \exp\left(-\frac{a(t-t^*)^2 + q^*}{2}\right) \end{align*}

với:

  • a=dTΣ−1da = d^T\Sigma^{-1}d
  • b=dTΣ−1Δb = d^T\Sigma^{-1}\Delta
  • c=ΔTΣ−1Δc = \Delta^T\Sigma^{-1}\Delta
  • t∗=−b/at^* = -b/a là chiếu vuông góc của tâm blob xuống tia nhìn
  • q∗=c−b2/aq^* = c - b^2/a. Cái này mình cũng không rõ, mình nghĩ là residual term gì đó thôi.

nhưng mà đại khái thì sau mấy phép biến đổi tương đương thì phép toán cuối cùng thực ra lại là một Gaussian distribution trên không gian 1 chiều. How coincident!!! (Thực ra là người ta cố tình chọn vì nó có mấy tính chất tiện tiện vậy mà)

Còn mấy phép toán liên quan đến hệ số của biễu diễn toàn phương thì dịch cabin ra là thay vì nhìn cái blob như hiện tại, thì mình "bóp méo" cái "point of view" của mình để cái blob trở thành một hình cầu cơ bản, thì nó trực quan và dễ tính toán hơn.

Ok, trở lại tiếp về việc nhìn từ tia này sẽ thấy màu gì, thì trước mình có dùng từ "density" cũng hơi khó hiểu. Thực ra mình cũng không hiểu rõ bản chất của nó lắm, nhưng mà hiện tại thì mình nghĩ đại khái là blob giống kiểu vô số hạt phát quang, phân tán trong không gian ý:

  • tại ô trung tâm của blob thì có rất nhiều hạt phát quang
  • tại ô xa trung tâm dần thì số hạt phát quang càng ít đi

Vậy thì tại 1 điểm trên tia nhìn của mình, cũng có 1 lượng hạt phát quang nhất định (và nó tỉ lệ thuận với cái density G(r(t))G(r(t)) kia).

Và mỗi hạt phát quang sẽ truyền 1 lượng tín hiệu quang học nhất định theo ngược lại tia nhìn để hướng đến mắt mình. Và để tính được lượng tín hiệu này, thì mình phải tích phân theo chiều tt của tia nhìn:

τ=∫σG(r(t))dt=σexp⁡(−q∗/2)∫exp⁡(−a(t−t∗)22)dt=σexp⁡(−q∗/2)Q(a(tnear−t∗))\begin{align*} \tau &= \int \sigma G(r(t)) dt\\ &= \sigma \exp(-q^*/2) \int \exp\left(\frac{-a(t-t^*)^2}{2}\right) dt\\ &= \sigma \exp(-q^*/2) Q(\sqrt{a}(t_\text{near}-t^*)) \end{align*}

với QQ là quantile function của unit normal distribution, tneart_\text{near} là khoảng cách nhỏ nhất từ mắt mà mình bắt đầu tích phân được (đơn giản là tia không bắt đầu từ mắt mà bắt đầu từ pixel trên màn hình á, tại vì mình đang mô phỏng cảm giác "nhìn qua cửa sổ" mà). Vậy trên kia là cách suy ra công thức của một blob, vậy nếu có nhiều blob thì màu mình nhìn sẽ thay đổi như nào?

Thì với mỗi blob, ta tính mấy cái giá trị sau:

  • ti∗t_i^*: chiếu vuông góc tâm blob xuống tia nhìn
  • qi∗q_i^*: residual term (ko nhớ ý nghĩa nữa)
  • αi=min⁡(0.99, σiexp⁡(−qi∗/2))\alpha_i = \min(0.99,\ \sigma_i \exp(-q_i^*/2))
  • ci=SH(μi−cam_pos)c_i = SH(\mu_i - \text{cam\_pos}): kiểu bản thân tâm của blob contribute vào tầm nhìn bnh màu

Mình không biết giải thích về αi\alpha_i sao, chắc cứ coi là 1 term để tiện tính toán thôi cũng được. Hoặc có thể coi là: dọc theo tia nhìn, blob ở trước sẽ làm mờ các blob ở sau với hệ số αi\alpha_i. Blob nào ở trước, blob nào ở sau thì đơn giản sắp xếp bằng ti∗t^*_i thôi.

C = 0 T = 1 for i in sorted(tstar): if alpha[i] < 1/255: continue # negligible, skip C += color[i] * alpha[i] * T # blob thứ i share bao nhiêu T *= (1-alpha[i]) # làm mờ các blob "đằng sau" blob hiện tại if T < 1e-4: break # bị làm mờ quá rồi thì thôi bỏ không nhìn nữa

Đó, vậy nếu có vô cùng nhiều blob trong không gian thì mình vẫn biết được ở một tia nhất định, mình sẽ nhìn thấy gì. Và tính màu của tất cả các tia đi qua màn hình, ta có thể biết được cả màn hình sẽ trông như nào (Như bên cạnh này nè).

Uki, vậy thì, đơn giản là mình với explain là với một đống blob trong không gian, cộng với một cái góc camera thì mình sẽ nhìn thấy gì. Đấy đơn giản gọi là một shading function. Thực ra thì cái shading function cũng có mấy thứ thiếu sót, ví dụ như:

  • công thức làm mờ giống analogy của tấm kính đục ở trước mặt hơn, và nguồn sáng thực sự đến từ rất xa tầm mắt, chứ không phải bản thân những hạt phát quang tại điểm kia truyền năng lượng tới mắt mình.
  • thực ra thì không có nhiều thuật toán render làm việc này, nhưng mà tính sóng của ánh sáng không được đưa vào phương trình á. Cái này thì khó giải thích, nhưng mà cũng không cần thiết.

Anyway, phương trình shading vẫn hơi thiếu sót, nhưng cái quan trọng là nó không cần quá bám sát thực tế, việc của nó là:

  • Dựa vào data structure mình đưa cho nó (các 3d blob), và số liệu camera
  • Hiển thị ra hình ảnh mà mình sẽ nhìn thấy từ một góc nhất định

Và nếu hình hiển thị ra có thể giống thứ mình mong muốn, thì mình coi như là shading function "useful" :>

Thì bài toán 3dgs tóm gọn lại là như này:

  • Cho nn cặp (imgi,cami)i≤n(\text{img}_i, \text{cam}_i)_{i\le n}.
  • Tìm một shading function (à và data structure nữa) để hình hiển thị ra từ các góc cam ban đầu giống với hình gốc nhất có thể (hi vọng đừng ai nghĩ tới vụ đặt mấy tấm 2d ở đúng góc cam ban đầu. Tù vl -,-)

Thì cái data structure coi như là tham số θ\theta có thể được tối ưu hoá đi. Thì objective function sẽ là:

∑i=1nL(shade(θ,cami),imgi)\sum_{i=1}^n \mathcal{L}(\text{shade}(\theta, \text{cam}_i), \text{img}_i)

với L\mathcal{L} có thể là bất cứ loss function nào để đo sự khác biệt giữa 2 tấm ảnh (e.g., L1, L2, LPIPS, ...). Còn lại thì Pytorch autograd lo.

À thực ra thì mình còn làm một số trò nữa. Đấy là dùng VGGT để khởi tạo (cami)i≤n(\text{cam}_i)_{i\le n}, vậy thì mình sẽ không cần ảnh đưa vào ban đầu có kèm theo camera config nữa. Sau đấy mình optimize cả đống cam config này cùng với đống blob θ\theta thôi.

VGGT là model DL để dự đoán:

  • camera config (position, focal length, rotation, ...)
  • per frame depth field

từ nhiều tấm ảnh đưa vào ban đầu. Và thực ra mình chỉ cần cái mục đầu tiên thôi. Thực ra nếu có cả 2 mục thì mình có thể render point cloud như mình để ở ảnh bên (kiểu mình dựa vào depth field, xong mình ném các pixel trở lại không gian 3d á).

Vậy đó, 3dgs tính ra cũng dễ :)

Comments (0)

No comments yet. Be the first to share your thoughts!