框架思路与原理
这是一个完全基于梯度下降做优化的框架,流程为产生高斯点、高斯点投影到图像、优化相机位姿、优化高斯点属性。
产生高斯点
初始化
高斯点由点云生成,点云即每个像素点随机产生一个深度信息,投影到世界坐标系即可(如果由深度信息,就不会随机产生)。
拥有点云后进行一个下采样,减少点云密度,并由赋给新点云透明度、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进行优化(若是最后一个关键帧,会进行特殊优化)
- 所有关键帧渲染损失叠加
- 随机选择其他视点渲染损失
- 若满足一定轮数,进行高斯点生长和去除
- 若满足一定论述,进行高斯点透明度激活、重置
- 跟新高斯点和相机位姿
- 将结果推送到前端(主要是优化后的高斯点数据)
- add_next_kf函数添加关键帧
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;
后端优化算法细节
待续