在这一篇文章中,我们计算位置参数的梯度。完整的链条是:
最终损失 \mathcal{L} \rightarrow 像素颜色 C_i \rightarrow 不透明度权重 \alpha_n \rightarrow 指数项 \sigma_n \rightarrow 二维均值 \mu'_n。
在了解梯度之前,我们要先看前向渲染是如何定义二维高斯的形状和位置的。2D 高斯的指数部分 \sigma_n 被定义为:
\sigma_n = \frac{1}{2} \Delta_n^\top \Sigma^{\prime -1} \Delta_n
其中,\Delta_n \in \mathbb{R}^2 是像素中心 x_{pixel} 与 2D 高斯中心 \mu'_n 之间的偏移量。
\Delta_n = x_{pixel} - \mu'_n
当需要求损失 \mathcal{L} 对 2D 中心 \mu'_n 的梯度时,需要使用链式法则:
\frac{\partial \sigma_n}{\partial \mu'_n} = \frac{\partial \sigma_n}{\partial \Delta_n} \frac{\partial \Delta_n}{\partial \mu'_n}
因为 \frac{\partial \Delta_n}{\partial \mu'_n} = -1,所以偏导数为:
\frac{\partial \sigma_n}{\partial \mu'_n} = - \Sigma^{\prime -1}_n \Delta_n
综合透明度 \alpha_n 的计算公式是 \alpha_n = o_n \cdot \exp(-\sigma_n) 。
综合链式法则计算最终梯度:
\frac{\partial \alpha_n}{\partial \mu'_n} = \frac{\partial \alpha_n}{\partial \sigma_n} \frac{\partial \sigma_n}{\partial \mu'_n} = (-\alpha_n) \times (- \Sigma^{\prime -1}_n \Delta_n) = \alpha_n \Sigma^{\prime -1}_n \Delta_n
以上对应的代码如下。有重复的梯度值在之前已经计算过了,可以结合 《协方差(相机空间)梯度计算》 查看。
- # 7. 计算 3D 位置在世界空间下的梯度并累加到 grad_pos 上
- # 沿着 u, v 维度对所有像素求和
- tile_grad_u = (2.0 * dL_da * x1).sum(dim=1) # (N_tile,)
- tile_grad_v = (2.0 * dL_da * x2).sum(dim=1) # (N_tile,)
- tile_grad_uv = torch.stack([tile_grad_u, tile_grad_v], dim=-1) # (N_tile, 2)
在 《协方差(世界空间)梯度计算》 中的结果也能复用,用于二维空间转三维空间。因为之前省略了推导过程,所以此处也直接拿来用。
- # 经由投影变换的雅可比矩阵 T_tile (N_tile, 2, 3) 映射到 3D 世界空间
- tile_grad_pos = (tile_grad_uv.unsqueeze(1) @ T_tile).squeeze(1) # (N_tile, 3)
-
- # 累加到 grad_pos
- grad_pos.scatter_add_(
- 0,
- orig_ids_tile.unsqueeze(1).expand(-1, 3),
- tile_grad_pos
- )
我们加入位置参数的优化。从可视化结果可以看到,背景的细节变得更多了。