赞
踩
三维重建,再从不同视角渲染成2D图像。
体积场景函数:一个连续的5D函数,用于描述一个场景中每个点的颜色和密度。
输入:三维空间位置,二维视角方向。
输出:该位置的体积密度和从该视角看到的颜色。
函数表示: f ( X , θ , ϕ ) = ( c , σ ) f(X, \theta, \phi) = (c, \sigma) f(X,θ,ϕ)=(c,σ),其中, X = ( x , y , z ) X=(x,y,z) X=(x,y,z)为三维坐标; θ , ϕ \theta,\phi θ,ϕ为观察该点的方向; c = ( r , g , b ) c=(r,g,b) c=(r,g,b)是从给定视角观察该点的颜色; σ \sigma σ 是该点的体积密度,影响光线通过该点的衰减。
该函数用深度神经网络来近似。
神经辐射场在三维空间渲染的物理方程:
L o ( x , d ) = L e ( x , d ) + ∫ Ω f r ( x , d , ω i ) L i ( x , ω i ) cos θ d ω i L_o(x,d)=L_e(x,d)+\int_{\Omega}f_r(x,d,\omega_i)L_i(x,\omega_i)\cos{\theta}d\omega_i Lo(x,d)=Le(x,d)+∫Ωfr(x,d,ωi)Li(x,ωi)cosθdωi其中, x x x为当前待分析的三维空间坐标; d d d为光线照射方向; L e ( x , d ) L_e(x,d) Le(x,d)为光源点 x x x在 d d d方向上的辐射量;后面的部分为该点光源照射到其他表面后,折射(沿着入射方向)在 d d d方向的辐射, f r ( x , d , ω i ) f_r(x,d,\omega_i) fr(x,d,ωi)为散射函数, L i ( x , ω i ) L_i(x,\omega_i) Li(x,ωi)为从 ω i \omega_i ωi方向接收到的辐射, θ \theta θ为 ω i \omega_i ωi与 d d d的夹角。
为了好理解:光源点 x x x在 d d d方向上散射的光强度变化(不完全不散射(0)----完全散射(1)),乘上 ω i \omega_i ωi方向上的辐射值在 d d d方向上的值,从而得到 ω i \omega_i ωi方向对 d d d方向上的贡献的辐射值。
NeRF 渲染
体渲染物理公式:
C ( r ) = ∫ t n t f T ( t ) σ ( r ( t ) ) c ( r ( t ) , d ) d t C(r)=\int_{t_n}^{t_f}T(t)\sigma(r(t))c(r(t),d)dt C(r)=∫tntfT(t)σ(r(t))c(r(t),d)dt其中, t n , t f t_n,t_f tn,tf表示起点和终点; T ( t ) = exp ( − ∫ t n t σ ( r ( s ) ) d s ) T(t)=\exp{(-\int_{t_n}^{t}\sigma(r(s))ds)} T(t)=exp(−∫tntσ(r(s))ds)为光线累积量,光线累积量是一个随着光线的路径长度增加,而不断对体素密度积分的量,光线到达地方深度和其大小成反比(越远说明透明度在下降),简单来说即光线到达这里光还剩多少; σ ( t ) \sigma(t) σ(t)为体素密度; c ( t ) c(t) c(t)为颜色。
这个像素的颜色,由这个点的颜色的权重 T ( t ) σ ( r ( t ) ) T(t)\sigma(r(t)) T(t)σ(r(t))控制
NeRF 分层体素渲染
为了平衡计算消耗,需要控制采样起始点,选择合适的起始、终止点,能大幅提高采样效率和质量。
NeRF 采用两个网络同时训练(coarse和fine网络),coarse网络输入的点是对光线均匀采样得到的;根据coarse网络预测的体密度值,对光线的分布进行估计,再根据估计出的分布进行第二次采样;然后把所有采样的点输入到fine网络中进行预测。
分层采样流程:
先使用粗采样得到 N c N_c Nc个点(把射线平均分成N个小区间,每个区间随机采样一个点),采样通过coarse的渲染方程计算,对颜色加权权重 ω i = T i ( 1 − exp ( − σ i δ i ) ) \omega_i=T_i(1-\exp{(-\sigma_i\delta_i)}) ωi=Ti(1−exp(−σiδi))作为对应区间采样的概率:
C ^ ( r ) = ∑ i = 1 N T i ( 1 − exp ( − σ i δ i ) ) c i \hat{C}(r)=\sum_{i=1}^{N}T_i(1-\exp{(-\sigma_i\delta_i)})c_i C^(r)=i=1∑NTi(1−exp(−σiδi))ci其中, δ i = t i + 1 − t i \delta_i=t_{i+1}-t_{i} δi=ti+1−ti; ω i = T i ( 1 − exp ( − σ i δ i ) ) \omega_i=T_i(1-\exp{(-\sigma_i\delta_i)}) ωi=Ti(1−exp(−σiδi))为权重,归一化之后成为概率值; T i = exp ( − ∑ j = 1 i − 1 σ j δ j ) T_i=\exp{(-\sum_{j=1}^{i-1}\sigma_j\delta_j)} Ti=exp(−∑j=1i−1σjδj)。
ω ^ i = ω i ∑ j = 1 N c ω j \hat{\omega}_i=\frac{\omega_i}{\sum_{j=1}^{N_c}\omega_j} ω^i=∑j=1Ncωjωi
以 ω ^ i \hat{\omega}_i ω^i为概率分布采样 N f N_f Nf个点,用 N c + N f N_c+N_f Nc+Nf个点训练精细网络。
3D Gaussian分布即为椭球,用 中心位置 μ \mu μ、不透明度 α \alpha α、三维协方差矩阵 Σ \Sigma Σ(表示缩放程度)和颜色 c c c 表示。 c c c用 球谐函数表示,以呈现视角依赖的外观。所有属性都可学习。
球谐函数 Spherical Harmonics:3D空间中的傅里叶级数,可以用来表示定义在球面上的函数。球谐函数原本公式表示为 r = f ( θ , ϕ ) r=f(\theta,\phi) r=f(θ,ϕ), θ \theta θ表示与 z z z轴得夹角,即极角, ϕ \phi ϕ表示其在 x y xy xy平面上的投影与 x x x轴的夹角,即方位角, r r r为半径。
在图形学中,将半径 r r r换为颜色的值,三个颜色通道就要三个球谐函数。
Q:为什么要用球谐函数表示颜色?
A:为了让该点的颜色不是显示单一的颜色,需要满足从不同角度看,该点展现不同的颜色或者不同饱和度。
球谐函数 Y l m Y^m_l Ylm表达式:
Y l m ( θ , ϕ ) = { 2 K l m cos ( m ϕ ) P l m ( cos θ ) ( m > 0 ) 2 K l m sin ( − m ϕ ) P l − m ( cos θ ) ( m < 0 ) K l 0 P l 0 ( cos θ ) ( m = 0 ) Y^m_l(\theta,\phi)=Ylm(θ,ϕ)=⎩ ⎨ ⎧2 Klmcos(mϕ)Plm(cosθ)(m>0)2 Klmsin(−mϕ)Pl−m(cosθ)(m<0)Kl0Pl0(cosθ)(m=0)⎧⎩⎨⎪⎪2–√Kmlcos(mϕ)Pml(cosθ)(m>0)2–√Kmlsin(−mϕ)P−ml(cosθ)(m<0)K0lP0l(cosθ)(m=0)
3D高斯分布的概率密度函数:
f
(
x
∣
μ
,
Σ
)
=
1
(
2
π
)
3
∣
Σ
∣
exp
(
−
1
2
(
(
x
)
−
μ
)
T
Σ
−
1
(
x
−
μ
)
)
f(\bf{x}|\mu,\Sigma)=\frac{1}{\sqrt{(2\pi)^3}|\Sigma|}\exp(-\frac{1}{2}(\bf(x)-\mu)^T\Sigma^{-1}(\bf{x}-\mu))
f(x∣μ,Σ)=(2π)3
∣Σ∣1exp(−21((x)−μ)TΣ−1(x−μ))
其中, x \bf{x} x表示三维点坐标。
标准化+模型中心放置原点(方便缩放旋转),放入世界空间时再加上平移,得到了论文中的3D高斯表示:
G
(
x
)
=
e
−
1
2
x
T
Σ
−
1
(
x
)
G(x)=e^{-\frac{1}{2}x^T\Sigma^{-1}(x)}
G(x)=e−21xTΣ−1(x)
三维协方差计算:
Σ
=
R
S
S
T
R
T
\Sigma=RSS^TR^T
Σ=RSSTRT
其中, S S S是缩放变换,由于缩放变换是沿着坐标轴,所以只需要3D向量 s s s表示缩放变换; R R R是旋转变换,用四元数 q q q表示。
- 方差:描述单个随机变量变化程度的量。
- 协方差:描述两个随机变量如何一起变化的量。
- 协方差矩阵: 3 × 3 3\times 3 3×3的对称矩阵,用于表示变量之间的线性关系。
Σ = [ σ x x σ x y σ x z σ y x σ y y σ y z σ z x σ z y σ z z ] \Sigma=Σ= σxxσyxσzxσxyσyyσzyσxzσyzσzz ⎡⎣⎢σxxσyxσzxσxyσyyσzyσxzσyzσzz⎤⎦⎥
其中, σ x x , y y , z z \sigma_{xx,yy,zz} σxx,yy,zz分别是 x , y , z x,y,z x,y,z轴上的方差, σ x y \sigma_{xy} σxy是 x x x和 y y y之间的协方差。- 方差的影响:
- 缩放:方差值越大,表示在该轴上分布越分散,即在该轴上的更广。
- 旋转:协方差矩阵的非对角线元素(协方差)如果不为零,则表示两个轴之间存在相关性。这种相关性使得分布呈现出一定的倾斜或旋转,而这种倾斜正是由于两个轴之间的线性关系造成的。
把椭球投影到2D成像平面,即Splatting渲染方法。
给定视图变换
W
W
W和三维协方差矩阵
Σ
\Sigma
Σ,计算投影的二维协方差矩阵
Σ
′
\Sigma'
Σ′公式如下:
Σ
′
=
J
W
Σ
W
T
J
T
\Sigma'=JW\Sigma W^TJ^T
Σ′=JWΣWTJT
其中, J J J是投影变换的仿射近似的雅可比矩阵,将3D向量转换为2D向量的矩阵。
使用Tiled-based Rasterizer:
自适应密度控制 ADC
- 初始化:3D GS从SfM产生的稀疏点云初始化或随机初始化高斯。
- 密集化:自适应增加高斯密度,以更好地捕捉场景细节。在规定迭代次数后执行,针对在视图空间中具有较大位置梯度(超过规定阈值)的高斯进行密集化操作。在未充分重建区域克隆小高斯,或分裂过度重建区域的大高斯。
def densify_and_clone(self, grads, grad_threshold, scene_extent): """ 克隆 """ # Extract points that satisfy the gradient condition selected_pts_mask = torch.where(torch.norm(grads, dim=-1) >= grad_threshold, True, False) selected_pts_mask = torch.logical_and(selected_pts_mask, torch.max(self.get_scaling, dim=1).values <= self.percent_dense*scene_extent) # 直接复制,并添加到存储空间中 new_xyz = self._xyz[selected_pts_mask] new_features_dc = self._features_dc[selected_pts_mask] new_features_rest = self._features_rest[selected_pts_mask] new_opacities = self._opacity[selected_pts_mask] new_scaling = self._scaling[selected_pts_mask] new_rotation = self._rotation[selected_pts_mask] self.densification_postfix(new_xyz, new_features_dc, new_features_rest, new_opacities, new_scaling, new_rotation) def densify_and_split(self, grads, grad_threshold, scene_extent, N=2): """ 分裂,对满足梯度条件的点进行相应操作 """ n_init_points = self.get_xyz.shape[0] # Extract points that satisfy the gradient condition padded_grad = torch.zeros((n_init_points), device="cuda") padded_grad[:grads.shape[0]] = grads.squeeze() # 标记大于梯度阈值的点 selected_pts_mask = torch.where(padded_grad >= grad_threshold, True, False) selected_pts_mask = torch.logical_and(selected_pts_mask, torch.max(self.get_scaling, dim=1).values > self.percent_dense*scene_extent) # 使用repeat操作,实现分裂 stds = self.get_scaling[selected_pts_mask].repeat(N,1) means =torch.zeros((stds.size(0), 3),device="cuda") samples = torch.normal(mean=means, std=stds) # 采样 rots = build_rotation(self._rotation[selected_pts_mask]).repeat(N,1,1) # 新的点=高斯噪声+重复的xyz,即做了初始位置调整 new_xyz = torch.bmm(rots, samples.unsqueeze(-1)).squeeze(-1) + self.get_xyz[selected_pts_mask].repeat(N, 1) # 缩放 new_scaling = self.scaling_inverse_activation(self.get_scaling[selected_pts_mask].repeat(N,1) / (0.8*N)) new_rotation = self._rotation[selected_pts_mask].repeat(N,1) new_features_dc = self._features_dc[selected_pts_mask].repeat(N,1,1) new_features_rest = self._features_rest[selected_pts_mask].repeat(N,1,1) new_opacity = self._opacity[selected_pts_mask].repeat(N,1) # 存储新的点 self.densification_postfix(new_xyz, new_features_dc, new_features_rest, new_opacity, new_scaling, new_rotation) # 根据梯度阈值mask创建剪枝过滤器,因为新克隆一个点,所以是N+1维 prune_filter = torch.cat((selected_pts_mask, torch.zeros(N * selected_pts_mask.sum(), device="cuda", dtype=bool))) # 剪枝 self.prune_points(prune_filter) def densification_postfix(self, new_xyz, new_features_dc, new_features_rest, new_opacities, new_scaling, new_rotation): """ 添加密集化点 """ d = {"xyz": new_xyz, "f_dc": new_features_dc, "f_rest": new_features_rest, "opacity": new_opacities, "scaling" : new_scaling, "rotation" : new_rotation} # 添加新的点 optimizable_tensors = self.cat_tensors_to_optimizer(d) self._xyz = optimizable_tensors["xyz"] self._features_dc = optimizable_tensors["f_dc"] self._features_rest = optimizable_tensors["f_rest"] self._opacity = optimizable_tensors["opacity"] self._scaling = optimizable_tensors["scaling"] self._rotation = optimizable_tensors["rotation"] self.xyz_gradient_accum = torch.zeros((self.get_xyz.shape[0], 1), device="cuda") self.denom = torch.zeros((self.get_xyz.shape[0], 1), device="cuda") self.max_radii2D = torch.zeros((self.get_xyz.shape[0]), device="cuda")
- 剪枝:移除冗余或影响较小的高斯。消除几乎透明的高斯(不透明度 α \alpha α低于阈值)和过大的高斯。为了防止输入相机附近的高斯密度不合理地增加,这些高斯会在固定迭代次数后,将 α \alpha α设置为接近0的值。在保证高斯精度和有效性的情况下,节约资源。
def prune_points(self, mask): valid_points_mask = ~mask # 使用mask剪枝 optimizable_tensors = self._prune_optimizer(valid_points_mask) self._xyz = optimizable_tensors["xyz"] self._features_dc = optimizable_tensors["f_dc"] self._features_rest = optimizable_tensors["f_rest"] self._opacity = optimizable_tensors["opacity"] self._scaling = optimizable_tensors["scaling"] self._rotation = optimizable_tensors["rotation"] self.xyz_gradient_accum = self.xyz_gradient_accum[valid_points_mask] self.denom = self.denom[valid_points_mask] self.max_radii2D = self.max_radii2D[valid_points_mask] def densify_and_prune(self, max_grad, min_opacity, extent, max_screen_size): """剪枝""" grads = self.xyz_gradient_accum / self.denom grads[grads.isnan()] = 0.0 self.densify_and_clone(grads, max_grad, extent) self.densify_and_split(grads, max_grad, extent) # 根据透明度筛选 prune_mask = (self.get_opacity < min_opacity).squeeze() if max_screen_size: big_points_vs = self.max_radii2D > max_screen_size big_points_ws = self.get_scaling.max(dim=1).values > 0.1 * extent prune_mask = torch.logical_or(torch.logical_or(prune_mask, big_points_vs), big_points_ws) # 剪枝 self.prune_points(prune_mask) torch.cuda.empty_cache()
# 在计算loss之后,反向传播设置为false,开始密集化
# Densification
if iteration < opt.densify_until_iter:
# Keep track of max radii in image-space for pruning
gaussians.max_radii2D[visibility_filter] = torch.max(gaussians.max_radii2D[visibility_filter], radii[visibility_filter])
gaussians.add_densification_stats(viewspace_point_tensor, visibility_filter)
if iteration > opt.densify_from_iter and iteration % opt.densification_interval == 0:
size_threshold = 20 if iteration > opt.opacity_reset_interval else None
gaussians.densify_and_prune(opt.densify_grad_threshold, 0.005, scene.cameras_extent, size_threshold)
if iteration % opt.opacity_reset_interval == 0 or (dataset.white_background and iteration == opt.densify_from_iter):
gaussians.reset_opacity()
为了减少对每个像素进行高斯运算,将图像划分成多个不重叠的块(Tile)。
像素位置
x
x
x通过视图变换
W
W
W,可以计算与所有重叠高斯体的距离,即深度。再根据深度排序得到深度列表
N
N
N。最后使用Alpha Blending(混合alpha合成)计算整个图像的最终颜色
C
C
C:
C
=
∑
i
∈
N
c
i
α
i
′
∏
j
=
1
i
−
1
(
1
−
α
j
′
)
C=\sum_{i\in N}c_i\alpha'_i\prod^{i-1}_{j=1}(1-\alpha'_j)
C=i∈N∑ciαi′j=1∏i−1(1−αj′)
其中,
c
i
c_i
ci是学到的颜色;
α
′
\alpha'
α′是最终的透明度,通过学到的透明度
a
a
a与高亮的亮度:
α
i
′
=
α
i
×
exp
(
−
1
2
(
x
′
−
μ
i
′
)
T
∑
i
′
−
1
(
x
′
−
μ
i
′
)
)
\alpha'_i=\alpha_i\times \exp(-\frac{1}{2}(x'-\mu'_i)^T\sum'^{-1}_i(x'-\mu'_i))
αi′=αi×exp(−21(x′−μi′)Ti∑′−1(x′−μi′))
其中, x ′ , u i ′ x',u'_i x′,ui′是投影到同一坐标系。
赞
踩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。