框架思路与原理

这是一个完全基于梯度下降做优化的框架,流程为产生高斯点、高斯点投影到图像、优化相机位姿、优化高斯点属性。

产生高斯点

初始化

高斯点由点云生成,点云即每个像素点随机产生一个深度信息,投影到世界坐标系即可(如果由深度信息,就不会随机产生)。
拥有点云后进行一个下采样,减少点云密度,并由赋给新点云透明度、rot等属性,成为高斯点。

优化

优化分为生长和去除。
高斯点的生长又分为克隆和分裂,主要针对梯度较大区域

  • 区域高斯点面积大:分裂成n个小高斯点,提高这个区域的分辨率
  • 区域高斯点面积小:克隆至n个小高斯点,提高这个区域的分辨率
    高斯点生长完后,再进行去除,即计算每个高斯点的透明度,去除透明的高斯点。

高斯点投影到图像

3dgs核心,主要工作是

  • 将3D高斯投影到2D,同时计算协方差(协方差表征高斯点面积)
  • 计算每一个像素点受所有高斯点的影响,生成渲染图

优化相机位姿(前端)

获取到图像后,使用了两个掩膜去滤去不关注的background,然后直接计算渲染图和真实图的像素L1距离作为损失函数,使用Adam优化器进行优化。
注意,此时只优化了相机位姿以及曝光

优化高斯点属性(后端)

初始化

对第一帧进行较长时间优化,可以获得比较好的高斯点状态,然后再回到前端,准备给GUI推送实时渲染画面。

维护关键帧

后端维护关键帧,只有当前端推送关键帧时,对所有关键帧一起优化,并随机额外抽取视点进行优化。
同样采用渲染图与真实图的像素L1距离作为损失函数,使用Adam优化器进行优化。
优化参数包括高斯点的位置、尺度、颜色、透明度、旋转角度等。

  • 每优化一定轮次(default=150)进行高斯点更新,即高斯点的生长和去除。
  • 每优化一定伦茨(default=500)进行高斯点透明度激活,通过一个激活函数修改透明度。
  • 每优化一定轮次完全重置透明度。

代码流程和框架

总共三个进程:前端(主进程)、后端、GUI(可关闭)。
主进程结束后会进入结果评估。

前端

初始化流程待续。

  • 等待vis2main队列数据(GUI界面数据,即是否暂停)
  • 先推送第一帧至后端,等待后端完成1050轮的初始化
  • 等待完毕
    • 通过Camera类获取相机数据(内参和色彩、位姿、像素深度真值)
    • 计算梯度
    • tracking
      • 将上一帧位姿作为初值
      • 配置求解器
      • 迭代求解,将高斯点渲染成二维图,计算像素的L1距离作为损失函数,仅更新相机位姿和曝光
      • 每十轮输出到GUI显示
    • 结果推送给GUI显示
    • 是否送入滑动窗口给后端优化
      • 先判断是否为关键帧,根据视场重叠情况(高斯点的n_touched的相似度)、两关键帧时间间隔
      • 整理滑动窗口

后端

  • 等待后端队列更新(前端调用request请求时写入)
  • 等待到关键帧请求
    • add_next_kf函数添加关键帧
      • 生成点云数据,下采样后制成高斯点
      • 传入优化器等待优化
    • 读取滑动窗口所有相机位姿和曝光,建立Adam优化器
    • 调用map进行优化(若是最后一个关键帧,会进行特殊优化)
      • 所有关键帧渲染损失叠加
      • 随机选择其他视点渲染损失
      • 若满足一定轮数,进行高斯点生长和去除
      • 若满足一定论述,进行高斯点透明度激活、重置
      • 跟新高斯点和相机位姿
    • 将结果推送到前端(主要是优化后的高斯点数据)

GUI

待续

高斯渲染依赖库

这套框架的渲染模型完全基于Differential Gaussian Rasterization这个开源库
下面为该开源库的学习笔记。

基本情况

这个库层层嵌套,接口为python编写,实际将一个c++函数绑定到python接口,而c++函数又调用了cuda编写的函数文件。
调用路径为:
init.py(rasterize_gaussians)
-> ext.cpp(绑定c++函数RasterizeGaussiansCUDA)
-> rasterize_points.cu(继续调用CudaRasterizer::Rasterizer::forward)
-> rasterizer_impl.cu(高斯渲染全过程,先preprocess预处理,最后调用render进行渲染)
-> forward.cu(绝大多数底层算法都在这里)
以上均为前端调用过程,后端调用基本一致。

前端算法细节

preprocess

3D->2D投影

// 世界坐标系3D坐标投影到图像坐标系,归一化,并保留z,在渲染时判断遮挡关系需要。
float3 p_orig = { orig_points[3 * idx], orig_points[3 * idx + 1], orig_points[3 * idx + 2] };
float4 p_hom = transformPoint4x4(p_orig, projmatrix);
float p_w = 1.0f / (p_hom.w + 0.0000001f);
float3 p_proj = { p_hom.x * p_w, p_hom.y * p_w, p_hom.z * p_w };

计算2D协方差

 // 计算雅可比矩阵,根据论文公式计算2D协方差
float3  t= transformPoint4x3(mean, viewmatrix);

const float limx = 1.3f * tan_fovx;
const float limy = 1.3f * tan_fovy;
const float txtz = t.x / t.z;
const float tytz = t.y / t.z;
t.x = min(limx, max(-limx, txtz)) * t.z;
t.y = min(limy, max(-limy, tytz)) * t.z;

glm::mat3 J = glm::mat3(
	focal_x / t.z, 0.0f, -(focal_x * t.x) / (t.z * t.z),
	0.0f, focal_y / t.z, -(focal_y * t.y) / (t.z * t.z),
	0, 0, 0);

glm::mat3 W = glm::mat3(
	viewmatrix[0], viewmatrix[4], viewmatrix[8],
	viewmatrix[1], viewmatrix[5], viewmatrix[9],
	viewmatrix[2], viewmatrix[6], viewmatrix[10]);

glm::mat3 T = W * J;

glm::mat3 Vrk = glm::mat3(
	cov3D[0], cov3D[1], cov3D[2],
	cov3D[1], cov3D[3], cov3D[4],
	cov3D[2], cov3D[4], cov3D[5]);
// 协方差公式 cov = W * J * Vrk * (W * J)^T
glm::mat3 cov = glm::transpose(T) * glm::transpose(Vrk) * T;

cov[0][0] += 0.3f;
cov[1][1] += 0.3f;
return { float(cov[0][0]), float(cov[0][1]), float(cov[1][1]) };

至此获得所有2D高斯点位置和大小,可以进行渲染

render

计算所有2D高斯点对每一个像素点的作用,生成渲染结果。


float2 xy = collected_xy[j];
float2 d = { xy.x - pixf.x, xy.y - pixf.y };
float4 con_o = collected_conic_opacity[j];
float power = -0.5f * (con_o.x * d.x * d.x + con_o.z * d.y * d.y) - con_o.y * d.x * d.y;
if (power > 0.0f)
	continue;

float alpha = min(0.99f, con_o.w * exp(power));
if (alpha < 1.0f / 255.0f)
	continue;
float test_T = T * (1 - alpha);
if (test_T < 0.0001f)
{
	done = true;
	continue;
}

// Eq. (3) from 3D Gaussian splatting paper.
for (int ch = 0; ch < CHANNELS; ch++)
	C[ch] += features[collected_id[j] * CHANNELS + ch] * alpha * T;

T = test_T;

后端优化算法细节

待续