位置梯度计算

在这一篇文章中,我们计算位置参数的梯度。完整的链条是:

最终损失 \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

以上对应的代码如下。有重复的梯度值在之前已经计算过了,可以结合 《协方差(相机空间)梯度计算》 查看。

  1. # 7. 计算 3D 位置在世界空间下的梯度并累加到 grad_pos 上
  2. # 沿着 u, v 维度对所有像素求和
  3. tile_grad_u = (2.0 * dL_da * x1).sum(dim=1)  # (N_tile,)
  4. tile_grad_v = (2.0 * dL_da * x2).sum(dim=1)  # (N_tile,)
  5. tile_grad_uv = torch.stack([tile_grad_u, tile_grad_v], dim=-1)  # (N_tile, 2)

《协方差(世界空间)梯度计算》 中的结果也能复用,用于二维空间转三维空间。因为之前省略了推导过程,所以此处也直接拿来用。

  1. # 经由投影变换的雅可比矩阵 T_tile (N_tile, 2, 3) 映射到 3D 世界空间
  2. tile_grad_pos = (tile_grad_uv.unsqueeze(1) @ T_tile).squeeze(1)  # (N_tile, 3)
  3.  
  4. # 累加到 grad_pos
  5. grad_pos.scatter_add_(
  6.     0,
  7.     orig_ids_tile.unsqueeze(1).expand(-1, 3),
  8.     tile_grad_pos
  9. )

我们加入位置参数的优化。从可视化结果可以看到,背景的细节变得更多了。