大家好,欢迎来到《分享本周所学》第十二期。本人是一名人工智能初学者,刚刚读完大二。前几天自学了一下3D Gaussian Splatting(3DGS),觉得非常有意思。写这篇文章主要是因为网上大部分关于3DGS的文章都比较晦涩,我自己学的时候也是翻阅了大量的论文博客视频,所以想结合自己的学习过程,写一篇让所有人都能看懂的文章。我不会假设你有任何机器学习或者数学的基础知识,即使你只是刚刚接触人工智能领域的小白,我也会让你看懂。如果你觉得我有任何一个地方(即使只是一个标点符号)写的不对、不好或者不清楚,麻烦你在评论区指出来,这会对我有极大的帮助。

        这里先放一下原论文:

https://repo-sam.inria.fr/fungraph/3d-gaussian-splatting/3d_gaussian_splatting_low.pdficon-default.png?t=N7T8https://repo-sam.inria.fr/fungraph/3d-gaussian-splatting/3d_gaussian_splatting_low.pdf

        同时这篇文章参考了一个B站视频。本文的内容有很多参考这个视频的地方,感谢这位大佬。

https://www.bilibili.com/video/BV1cz421872F/icon-default.png?t=N7T8https://www.bilibili.com/video/BV1cz421872F/

目录

一、啥是三维重建啊

1. 三维重建的定义

2. 三维模型的表示方法

1.1 点云

1.2 体素

1.3 神经网络

1.4 3D高斯

二、啥是3D Gaussian啊

1. 位置

2. 协方差矩阵

3. 透明度与球谐系数

三、怎么获得3D高斯的信息啊

四、啥是Splatting啊

1. 2D高斯的位置

2. 2D高斯的协方差矩阵

3. 2D高斯的颜色

五、自适应密度控制

六、下期预告


上期文章链接:

https://blog.csdn.net/weixin_48978134/article/details/132740002icon-default.png?t=N7T8https://blog.csdn.net/weixin_48978134/article/details/132740002本期封面:

26793f97fe704eaea3fcbd26807a8363.png


一、啥是三维重建啊

        本章会介绍一些三维重建的基础知识,如果你已经对三维重建有一定了解,可以直接跳到下一章。

1. 三维重建的定义

        三维重建一般就是指通过一些二维的数据(例如照片、视频等)去还原一个三维的场景。什么意思呢?我们可以拿自己的眼睛做个类比。人之所以能看见东西,是因为外部的光线投射在了我们眼睛的视网膜上,由视杆和视锥细胞将光线的强度、颜色等信息传递给大脑,让我们能够看见东西。显然,人眼只能看到一个二维的画面,无法感知物体的三维结构信息。那为什么我们在看东西的时候会感觉东西是立体的呢?主要有两点原因。

        第一,人有两只眼睛,我们在看一个场景的时候,两只眼睛看到的画面会有一些微小的差异,这个差异叫做“视差”。这两张画面在大脑中经过处理之后,会被合成一张单一的画面,并且让你感觉看到的画面仿佛是立体的。那么对于计算机来说,如果想要合成一个物体的3D模型,就需要从至少两个不同的角度拍摄物体的照片,来模拟人眼的视差。通常来讲,为了得到一个很好的重建效果,使用两张照片是远远不够的,我们通常会使用几十张甚至几百张不同角度的照片来还原一个场景。

        第二,我们都知道,远处的物体看起来会比较小,而近处的物体看起来会比较大。对于同一个物体来说,它离我们较远的一端会看起来比较小,而离我们较近的一端会看起来比较大。这个现象叫做“透视”。我们能够根据透视关系来判断物体与我们之间的距离,而这种距离感会让我们感觉看到的画面是立体的。在进行三维重建时,我们也会有专门的算法来处理透视,在后文中会提到。

        综上所述,三维重建主要有两个关键点:一是多张不同角度的照片,二是对图片中透视关系的处理。不过三维重建中要考虑的不止这两点,还有一些其他的因素,比如三维模型的表示方法……

2. 三维模型的表示方法

        在三维重建中,我们表示三维模型的方法和在三维建模软件中(例如3ds Max、Maya、AutoCAD等)有很大区别。做过建模师、动画师或者设计师的朋友们应该知道,在三维建模软件中,一个模型是由大量的顶点、边和面构成的。比如下图是Maya官网的宣传片中展示的《霍格沃茨之遗》中一个角色的模型:

2b369a76d19a441b9d126a9c5d8eb6ba.png

        仔细看图片中绿色的物体,你会发现,这些绿色其实是布满整个物体的密密麻麻的线。其实它们就是构成这个物体的边。可以说,这些三维建模软件把要建模的物体表示为一个面数极多的多面体,多面体的面数越多,就能把物体表示得越精细。

        在三维重建中,用顶点、边和面来表示三维模型的确是一种可行的选择,我们把这种表示方法称作“多边形网格”。这种表示方法的好处是能够将重建出的三维模型与很多建模软件进行无缝衔接,能够便于后续的编辑和应用。然而,这种表示方法也有很大的缺陷。我们知道,多面体的一个面是由很多条边组成的,而每条边又会依赖于两个顶点。在一个面数较多的场景中,这种点、边和面之间的依赖关系是极为庞大和复杂的。为了能够更好地还原场景并降低计算量,我们希望用尽可能简洁的方式表示一个三维模型,希望三维模型中的每个元素是相互独立的。因此,这种多边形网格的表示方法现在极少被应用在三维重建中,取而代之的是以下几种更为常见的表示方法……

1.1 点云

        点云,说白了就是一大堆点。我们把一个三维场景表示为一大堆点,并且纪录每个点的位置、颜色和透明度等信息。

527e2542621549d88292802fad7ae9bf.webp

        上图是从百度百科上找的一个用点云对一栋建筑进行三维重建的图片,由于点的数量比较密集,可能比较难以看出单个的点。

        我们很容易想出,点云与多边形网格相比的优势在于每个点都是独立的,它们的存在不会依赖于其他东西,因此没有庞杂的依赖关系。但是它的缺点在于数据量过大。为了表达一个精细的模型,我们通常需要大量的点,而相邻的点之间不会有太大区别。比如说,如果我要用点云来表示一面墙,那有很大可能这面墙上的所有点的颜色和透明度等信息都几乎完全相同。这就造成了大量的冗余。

1.2 体素

        大家都知道,图片是由像素构成的,把一张图片划分成许多个小格子,每个格子都有一个颜色值,有的图片格式还会给每个格子一个透明度值。体素与像素类似,就是把一个三维场景划分成大量的小正方体,每个正方体有颜色和透明度等信息。体素存在的问题是,有可能一个场景中绝大部分空间都是没有任何东西存在的,因此会产生大量完全透明、没有任何信息含量的冗余体素。当然也有一些解决这个问题的办法,比如使用稀疏体素,即只记录场景中非空体素的信息,忽略完全透明的体素。

1.3 神经网络

        神经网络属于是比较先进比较现代的一种表示方法。大家可能听说过NeRF,也就是神经辐射场。NeRF就使用了一个八层的MLP(全连接神经网络)来表示三维模型,神经网络的输入是三维空间中某个点的坐标以及观察的角度,输出是这个点的颜色和透明度。NeRF的发明者考虑到,由于光照和材质等条件的影响,从不同角度观察一个物体看到的颜色可能有所不同,因此将观察的角度作为输入的一部分传递给神经网络。

        神经网络一个非常大的优势在于占用的空间非常小,一个八层的MLP占用的空间肯定比点云中几十万个点要小多了。除此之外,它的重建效果也非常不错。

1.4 3D高斯

        这是我们今天的主角。3D高斯和点云其实比较相似,都是记录位置、颜色和透明度信息,只不过把点换成了3D高斯。我们将一个三维场景表示为一系列3D高斯,记录它们的位置、协方差矩阵、透明度和球谐系数。至于这些都是什么东西,可以看下一章……

二、啥是3D Gaussian啊

        相信大家都听说过高斯分布。高斯分布,也叫正态分布,是一种非常常见的概率分布。如果你熟悉一维的高斯分布,可以直接跳到本章的第1节。如果你不了解高斯分布也没有关系,可以看一下下面这张图(图片来自搜狗百科):

693a376528e64a32bd119a936fcdb0c6.jpeg

        这就是一张高斯分布的图片。图中,\mu表示均值,\sigma表示标准差。简单来说,假设我有一个变量x,它可能是任何一个实数。现在,我们假设它遵循上图这个高斯分布,那么上图中函数值越高的地方,x就越有可能取到这个数值。我们可以看到,函数值最高的地方是\mu,也就是说x的值最有可能是\mu。离\mu越远的地方,x取到这个数值的可能性越低。有多低呢?这是由\sigma决定的。从图中可以看出,x的值有68.27%的概率位于\mu - \sigma\mu + \sigma之间,有95.45%的概率位于\mu -2 \sigma\mu +2 \sigma之间,有99.73%的概率位于\mu -3 \sigma\mu +3 \sigma之间。可以说,\sigma越大,x就越有可能取到一个离\mu较远的值。\mu\sigma是高斯分布的两个参数,它们决定了高斯分布的形状。

        如果你感兴趣的话,可以看一下高斯分布的公式,不过你并不需要记住或看懂它

f(x)= \frac{1}{\sigma \sqrt{2 \pi}} e^{- \frac{​{(x- \mu)}^2}{2 {\sigma}^2}}

        了解了一维的高斯分布,我们就来看一下3D Gaussian,也就是3D高斯。为了描述一个3D高斯,我们需要四个属性:位置、协方差矩阵、透明度和球谐系数。我会在讲解中着重分析三维与一维高斯分布的区别。

1. 位置

        这其实就是一个3D高斯在三维空间中的位置,与1D高斯的均值eq?%5Cmu对应。显然,我们需要三个数值才能表示三维空间中的一个位置。因此,我们可以用一个包含三个元素的向量\mu = \left< x,y,z \right>来表示3D高斯的位置,其中xyz分别表示3D高斯在x轴、y轴和z轴上的坐标。

2. 协方差矩阵

        3D高斯的协方差矩阵对应的是1D高斯的标准差\sigma。一个3D高斯的协方差矩阵是一个3行3列的矩阵:

\Sigma = \begin{bmatrix} \sigma^2_x & \sigma_{xy} & \sigma_{xz} \\ \sigma_{yx} & \sigma^2_y & \sigma_{yz} \\ \sigma_{zx} & \sigma_{zy} & \sigma^2_z \end{bmatrix}

        有的朋友可能要问了:既然1D高斯只用了一个数\sigma就表示了高斯分布的分散程度,为什么3D高斯足足要用九个数呢?这是因为一个3D高斯在x轴、y轴和z轴上可能有不同的分散程度。比如以下这两张图:

d015054493f643cf845d9d5075720368.png

        图中越黄、越亮的位置表示概率越高,越紫越暗的地方表示概率越低。两张图中,3D高斯的均值都位于原点,左图中,3D高斯在x轴、y轴、z轴上的方差均为1,而右图中,3D高斯在x轴和z轴上的方差为1,在y轴上的方差为2。很明显,右图中的3D高斯在垂直方向上更加分散,呈现出一个椭圆的形状。因此可以看出,单独一个数值不足以表达3D高斯的分散程度,我们需要至少三个数,来表示三个方向上不同的分散程度。这三个数就构成了协方差矩阵对角线上的三个数。

        这时可能又有朋友要问了,那如果x轴、y轴和z轴分别有三个不同的方差,使用这三个数还不足以表达一个3D高斯的分散程度吗?遗憾的是,确实不能。大家可以看出,上面两张图中的3D高斯都是关于x轴和y轴轴对称的。如果我想创建一个斜着的、不关于x轴或y轴轴对称的3D高斯,那仅有三个变量是不够的。因此,我们需要引入协方差。

        那什么是协方差呢?我们可以把协方差看作一个用于表示两个变量之间相关性的数值。如果两个变量之间的协方差为正,那么这两个变量是正相关的,也就是说,如果其中一个变量增加,那么另外一个变量也会增加;反之,如果两个变量之间的协方差为负,那么这两个变量就是负相关的,当其中一个变量增加时,另外一个会减少。上面的两张图中,两个3D高斯所有方向上的协方差均为0。为了更好地看出协方差能够带来的变化,我们可以看下面这两张图:

9c5d893ca8344b629c2fd904593fdc75.png

        如图,左侧是一个x轴和z轴上的方差为1、y轴上方差为2、所有协方差均为0的3D高斯,而右侧在左侧的基础上,将x轴和y轴之间的协方差变为1。明显可以看出,由于协方差的引入,椭圆变得倾斜了。因此可以证明,我们的确需要不止三个数才能有效地表示一个3D高斯。

        不过其实我们也并不需要多达九个数值,只需要六个就够了。这是因为x轴与y轴之间的协方差和y轴与x轴之间的协方差本质上是一个数,也就是说\sigma_{xy}\sigma_{yx}是相等的。之所以使用九个数,是因为九个数可以组成一个3×3的矩阵,更加便于运算。

        需要注意一下,协方差矩阵有一个非常重要的性质,那就是协方差矩阵一定是半正定的。什么是半正定呢?半正定矩阵的定义是:如果矩阵A是半正定矩阵,那么对于任意实非零列向量x,都有x^TAx \geq 0。至于这具体是什么意思,以及怎么证明,其实在三维重建中不是特别重要,感兴趣的朋友可以自己去查一下,不过你需要记住协方差矩阵有这么个性质,后面会用到。

        这里也给感兴趣的朋友们放一下三维高斯分布的公式,和前面一维的一样,你不需要记住或者看懂它

f(X)= \frac{1}{\sqrt{​{(2 \pi )}^3 | \Sigma |}} e^{- \frac{1}{2} {(X- \mu )}^T {\Sigma}^{-1} (X- \mu )}

3. 透明度与球谐系数

        这两个属性都不是3D高斯本身带有的属性,所以放在一起说。透明度很好理解,就是描述这个3D高斯有多透明。透明度只包含一个数值\alpha。值得注意的是,3D高斯的透明度不止受透明度影响,概率密度越高的地方就越不透明,概率密度越低就越透明。由于高斯分布的概率密度在靠近中心的位置最高,因此每个3D高斯最中心是不透明度最高的。具体可以看一下这篇文章的封面图:

26793f97fe704eaea3fcbd26807a8363.png

        可以看出图中共有三个3D高斯,每个3D高斯在中心位置色彩最亮、最鲜艳,而到边缘位置会逐渐发黑。这是因为3D高斯在边缘位置的不透明度较低,露出了黑色的背景。

        重点在于这个球谐系数。球谐系数可以理解为用于表示颜色的一组数值。我们通常在表示颜色时使用三个数值,分别表示红光、绿光和蓝光的强度。而如果使用球谐系数来表示颜色,每个3D高斯需要多达48个数值。至于为什么使用这么多数值,简单来说是因为球谐系数不止能够表达单一的颜色,而是能表达物体表面不同位置的不同色彩和纹理,表达能力要远远强于用RGB表达的颜色。至于球谐系数到底是什么,这个比较难解释清楚。我打算之后单独出一篇文章来解释,等文章写好之后会把链接放在这里,大家可以关注我一下,这样写好的时候文章就会第一时间推到你那里。不过即使你只用RGB三个数值来表达颜色,也能有不错的效果。

三、怎么获得3D高斯的信息啊

        我们现在已经知道了需要用哪些信息来描述一个3D高斯。那么问题来了,怎么才能获得这些信息呢?我们前面说到,在进行三维重建时,我们需要对场景从不同角度拍摄大量照片。我们现在就要使用这些照片来获得3D高斯的信息。我们可以使用一些工具来辅助提取这些信息,比如COLMAP。COLMAP是一个非常好用的工具,我们只要输入拍摄的照片,COLMAP就可以自动为我们分析出每张照片的拍摄位置和拍摄角度。同时,COLMAP也会以点云的形式对场景进行初步的重建,并且为每个点提供位置和颜色信息,如下图所示:

8a3f33e915774bcda7bb424f2df08f7d.png

        如图所示,左边展示了每张图片对应的相机位置和角度、以及重建出的点云,右侧是选中的相机的一些参数。COLMAP的项目地址放在这里,他们的GitHub项目页面里写了下载链接,大家可以去下载一下。

colmap/colmap: COLMAP - Structure-from-Motion and Multi-View Stereo (github.com)icon-default.png?t=N7T8https://github.com/colmap/colmap

        至于COLMAP具体的使用方法,我之后计划会单独出一篇文章进行介绍,写好后会把链接放在这里。大家可以期待一下。

        言归正传。现在我们有了点云,如何通过它们来获取3D高斯的参数呢?其实很简单,我们只要在点云中每个点的位置放置一个3D高斯就行了。3D高斯的球谐系数可以通过点的颜色计算得到,透明度直接设成1(完全不透明),而协方差矩阵直接设成单位矩阵,表示三个轴上的方差均为1,其他位置的协方差均为0:

\begin{bmatrix} 1 & 0 & 0 \\ 0 & 1 & 0 \\ 0 & 0 & 1 \end{bmatrix}

        但是显然,这样的设置未免有些过于潦草,无法表示一个精细的模型。为了使3D高斯的参数更加精确,我们需要使用梯度下降对3D高斯的参数进行更新。

        具体怎么更新呢?其实也很简单。我们不是用相机拍了很多不同角度的照片吗?现在,我们随便选择其中一个相机,从这个相机的角度去观察我们刚刚创建的那堆3D高斯,随后与相机实际拍摄的图片作对比,如下:

862094fdfbb647a08f0ae4b9036b9b0c.png

        如图,左侧是从相机的拍摄位置对场景中的3D高斯进行渲染得到的图像,而右图是实际的图像。我们可以计算这两张图像的差异有多大,一般采用L1损失。如果你不知道L1损失是什么也没有关系,其实说白了就是把两张图片对应位置的像素的颜色作差,然后把所有的差值求平均值。比如说左边这张图片有三个像素,每个像素用RGB三个数值表示,分别是(100,100,100)(110,110,110)(120,120,120),右边的图片也有三个像素,分别是(90,100,110)(110,110,130)(100,100,100),那么两张图片之间的L1损失就是:

\frac{|100-90|+|100-100|+|100-110|+|110-110|+|110-110|+|110-130|+|120-100|+|120-100|+|120-100|}{9}

        L1损失也可以用公式表示。对于两个含有n个元素的数组XY,它们之间的L1损失是:

\frac{\sum_{i=1}^{n} | X_i - Y_i |}{n}

        很简单的公式,对吧?

        既然有了损失函数,我们就可以对损失值进行反向传播,然后进行梯度下降。和一般的机器学习不一样的是,这里没有神经网络,我们要更新的参数是3D高斯的那几个参数,也就是位置、协方差矩阵、透明度和球谐系数。以上就是3D高斯的训练过程。我们可以用不同角度拍摄的照片来训练这些3D高斯,使得它们更加精细、真实。这也就是我们需要大量不同角度照片的原因。

        听起来3DGS的过程到这里就结束了是不是?但是其实还有一个很严重的问题。还记不记得我们之前说过,协方差矩阵是半正定的?如果用梯度下降的方法,我们在更新协方差矩阵的时候,不能保证更新后的矩阵是半正定的。如果不能保证协方差矩阵是半正定的,那么协方差矩阵就失去了几何意义。这是我们需要避免的。

        为了解决这个问题,我们需要利用矩阵的一个性质:对于任意矩阵AAA^T一定是半正定的。恰好,一个3D高斯的协方差矩阵可以被转换成它的旋转矩阵R和缩放矩阵S的乘积:\Sigma =RS S^T R^T,而S^T R^T刚好等于{(RS)}^T,也就是\Sigma =(RS) {(RS)}^T。这样一来,如果我们储存并更新RS而不是直接更新协方差矩阵,就能保证不管RS被更新成什么样,计算出的协方差矩阵都是半正定的。

        那什么是旋转和缩放矩阵呢?我们刚刚讲协方差矩阵的时候提到,之所以3D高斯的分散程度不能用单个数值表示,就是因为3D高斯在x、y、z三个方向上可能有不同的分散程度,并且可能需要旋转和倾斜。我们可以把它拆成两部分,先用一个矩阵描述三个轴上的分散程度,这个矩阵就是缩放矩阵,相当于协方差矩阵只保留对角线上的三个数;再用另一个矩阵描述这个3D高斯的旋转,这个矩阵就是旋转矩阵。将旋转矩阵和缩放矩阵以RSS^TR^T的形式相乘,就得到了两个矩阵的叠加,也就是协方差矩阵。

        那么到此为止,使用3D高斯重建三维场景的流程就结束了。我们再整体回顾一下。首先,用COLMAP这样的工具获取相机的位置和角度,并且重建出点云。随后,用点云中的点来初始化3D高斯,在每个点的位置放置一个3D高斯,用点的颜色计算它的初始球谐系数,然后随便给它一个初始的旋转矩阵、缩放矩阵和透明度,其中旋转矩阵和缩放矩阵可以用于计算协方差矩阵。最后,我们从相机的位置和角度观察场景中的3D高斯,得到一幅画面,用这个画面和相机实际拍摄的照片作对比,计算L1损失,然后反向传播、梯度下降、更新参数。

        不过这其中有一个被我一笔带过的步骤,还需要拿出来再重点说一下,那就是将场景中的3D高斯渲染成一幅画面的过程。这也就是3DGS中的另一个重点——Splatting。

四、啥是Splatting啊

        学过英语的朋友应该能看出来,“splatting”是一个动名词,它的原形应该是“splat”。那什么是splat呢?splat本身是一个拟声词,描述的是雪球砸在墙上的声音。

        我们可以想象一下这个场景:我们捏了一个雪球扔在墙上,雪球在墙上砸得粉碎,雪花四处飞溅。尘埃落定之后,我们观察墙面,会发现被雪球直接命中的位置留下的痕迹最重,越远离命中中心,雪花的痕迹越少(下图由DALL·E创作)。

5853ade35ae14410a6da10098ce0eb5d.webp

        这是不是很像我们之前提到的3D高斯的透明度?越靠近高斯的中心,颜色就越重,不透明度越高;离中心越远,不透明度就越低,颜色就越黯淡。这也就是为什么渲染高斯的过程被称作“splatting”。如果硬要翻译的话,可以把splatting翻译成“喷溅”。

        为了将3D高斯渲染成一张二维的图像,我们需要将它们投影到二维平面上,因此需要将它们转换为2D的高斯。splatting本质上就是把3D的高斯转换成2D的过程。这个过程主要分三步:第一,确定2D高斯的位置;第二,确定2D高斯的协方差矩阵;第三,根据3D高斯的球谐系数和透明度计算2D高斯的颜色。我们逐一来分析……

1. 2D高斯的位置

        为了求出2D高斯的位置,我们需要知道几个条件:3D高斯的位置、相机的位置、相机的角度以及相机的视锥大小。那什么是相机的视锥呢?这里的视锥指的不是人眼中的视锥细胞。不知道各位还记不记得,我在文章开头曾经提到过,人眼能够感知三维空间的一个很重要的原因就是透视效应,也就是物体离我们越近,看起来就越大的现象。我们在将3D高斯投影到2D画面的过程中需要考虑到透视效应,因此我们将这种投影称为“透视投影”。这里提一句,也有一种不考虑透视效应的投影,我们把它称作“正交投影”。那我们什么情况下会使用正交投影呢?正交投影主要应用于一些透视效应不重要,或者不需要表现真实的三维场景的场合,比如在一些2D游戏中,我们将摄像机放置在场景上方,居高临下地观察一个2D的游戏场景,这时就可以使用正交投影来渲染相机的画面,因为我们不需要表现三维的内容。

        扯远了,说回透视投影。由于透视现象,离相机越近的地方,我们的视野范围就越小;离相机越远的地方,我们的视野范围就越大。因此,我们可以把相机的视野范围看成一个四棱锥:

0c59e4b5f2104e9eab453cb0c2e590a3.png

        图是我手画的,画得比较烂(准确地说应该是稀烂,烂得不能再烂),大概能明白是什么意思就行。

        那么这个四棱锥就是所谓的视锥。不过一般来讲,我们不会用一个完整而四棱锥来表示视锥,而是会把四棱锥的尖头截掉:

a033fb58d32e44958a5b531c5392095e.png

        这样一来,视锥就变成了一个四棱台。我们把四棱台离相机较近的那个平面称为near plane(也叫成像平面),把离相机较远的那个平面称为far plane。near plane和far plane似乎没有一个固定的翻译,我姑且按照Unity官方中文文档中的翻译,将它们译为“近剪裁面”和“远剪裁面”。

        那什么决定了视锥的大小呢?是相机的视野。我们用手机拍照的时候,如果将画面放大,视野就会变小,能看到的物体就越少,视锥也就越小;如果将画面缩小,视野就会变大,能看到的物体就越多,视锥也就越大。如果我们将相机的位置与画面最左侧和最右侧的物体分别连一条线,这两条线的夹角就是相机的水平视野。同理,如果将相机的位置与画面最上端和最下端的物体分别连一条线,这两条线的夹角就是相机的垂直视野。如果你没有理解的话,可以看下图:

393a136bb4454b8483f20d93699edfa1.png

        如图,相机的视锥中,红色与绿色线的夹角就是水平视野,而绿色与蓝色线的夹角就是垂直视野。

        现在,我们将相机的水平视野记为{fov}_h,将垂直视野记为{fov}_v,将近剪裁面到相机的距离记为n,将远剪裁面到相机的距离记为f。我们将下面的矩阵称为“投影矩阵”:

\begin{bmatrix} \frac{1}{\tan{\frac{​{fov}_h}{2}}} & 0 & 0 & 0 \\ 0 & \frac{1}{\tan{\frac{​{fov}_v}{2}}} & 0 & 0 \\ 0 & 0 & - \frac{f+n}{f-n} & - \frac{2nf}{f-n} \\ 0 & 0 & -1 & 0 \end{bmatrix}

        至于这个矩阵是怎么来的,涉及到一些计算机视觉方面的知识,一时半会难以说清,我就不写在这里占用篇幅了。感兴趣的朋友们可以自己查阅一下。我们只需要知道,将这个矩阵乘以3D高斯的坐标,就能得到2D高斯的位置。这个过程称为投影变换。

        不过,这里又有两个问题需要解答。第一,如何确定{fov}_h{fov}_vnf的值?第二,一个3D高斯的坐标只有三个数,如何用一个4×4的矩阵与它相乘?

        对于第一个问题,{fov}_h{fov}_v的值是由相机决定的。显然在一张照片被拍摄好的时候,它的视野大小就已经确定了。我们可以直接根据相机的视野大小来设置{fov}_h{fov}_v的值。至于nf,我们可以自己设置它们的值。通常n的值较小,比如可以设它为0.1,f可以设为100左右。

        而对于第二个问题,显然,我们需要一个含有四个数的向量才能与一个4×4的矩阵相乘。解决办法很简单,我们直接在坐标的末尾追加一个1就行了,也就是:

\begin{bmatrix} x \\ y \\ z \end{bmatrix} \rightarrow \begin{bmatrix} x \\ y \\ z \\ 1 \end{bmatrix}

        这种用四个数表示三维坐标的方式称为“齐次坐标”。那我们为什么非要用四个数来表示一个三维坐标呢?这是因为三维空间中任何的平移、旋转、缩放等变换都可以通过用一个4×4的矩阵乘以一个齐次坐标来完成。这是普通的三维坐标无法做到的。另外,普通三维坐标与齐次坐标的转换非常简单。将普通三维坐标转换成齐次坐标只需要想刚刚那样在末尾追加一个1,而将齐次坐标转换为普通三维坐标只需要将前三个数分别除以最后一个数,也就是:

\begin{bmatrix} x \\ y \\ z \\ w \end{bmatrix} \rightarrow \begin{bmatrix} \frac{x}{w} \\ \frac{y}{w} \\ \frac{z}{w} \end{bmatrix}

        总结一下,如果要获取2D高斯的位置,我们只需要将3D高斯的位置表示成一个齐次坐标,然后用投影矩阵乘以这个齐次坐标,最后再把齐次坐标还原为普通三维坐标就行了。

2. 2D高斯的协方差矩阵

        与计算位置相比,计算协方差矩阵是一个更为复杂的过程,这是因为透视投影是一个非线性的变换,而我们不能对协方差矩阵进行非线性变换。

        那什么是线性变换和非线性变换呢?线性变换就是满足加法封闭性和数乘封闭性的变换。简单来说,如果有一个含有三个元素的向量x,那么函数f(x)=Ax就是线性变换,其中A是一个3×3的矩阵。既然如此,刚刚的投影变换也是直接用一个矩阵乘以一个向量,为什么说它是非线性变换呢?这是因为我们在刚刚的变换中使用了齐次坐标,相当于把三维坐标转换成一个四维的坐标,然后在四维空间中进行了一个线性变换。如果是在三维空间中,我们没有办法用一个线性变换来描述投影变换。

        那为什么可以对3D高斯的位置进行非线性变换,但不能对协方差矩阵进行非线性变换呢?这是因为高斯的位置就是一个点,一个点无论怎么变换都还只是一个点,不会被拉伸。而协方差矩阵描述了一个高斯的分散程度,它描述的不是一个点,而是一种形状,在非线性变换的过程中,协方差矩阵可能会被拉伸,这种拉伸会导致3D高斯在投影到2D平面后不再是一个高斯,这是我们不希望发生的。

        因此,我们需要一种将非线性变换转换为线性变换的手段,这个手段就是雅可比矩阵。不知道有多少位朋友了解雅可比矩阵。雅可比矩阵的概念其实并不复杂,和线性近似非常类似。

5fd480e46df64929b8b7b98d8a61d63c.png

        我们知道,如果想求一个函数在某一点的函数值,可以通过函数在该点附近的一条切线来求一个近似值。比如上图中,如果我想求\sqrt{6}的值,可以在x=4的位置作f(x)= \sqrt{x}的切线,然后求当x=6时切线的函数值是多少。

        雅可比矩阵使用了同样的理念,它相当于将线性近似拓展到了高维的空间。也就是说,对于一个3D高斯的协方差矩阵\Sigma,我们无法直接在三维空间中对它进行线性的投影变换,但是我们可以使用一个线性变换f(x)=J \Sigma J^T来代替投影变换,并且这个线性变换的效果与投影变换非常近似。其中,矩阵J就被称为“雅可比矩阵”。这里直接给出J的值:

\begin{bmatrix} \frac{n}{z} & 0 & - \frac{nx}{z^2} \\ 0 & \frac{n}{z} & - \frac{ny}{z^2} \\ 0 & 0 & \frac{nf}{z^2} \end{bmatrix}

        其中,xyz是3D高斯的位置,nf和刚刚一样,表示相机到近剪裁面和远剪裁面的距离。

        不过其实刚刚的公式里还少了一项,完整的公式(也是原论文中给出的公式)应该是\Sigma '=JW \Sigma W^TJ^T,其中W是相机的旋转矩阵的转置。

3. 2D高斯的颜色

        2D高斯的颜色由两个因素决定:球谐系数和透明度。其中球谐系数如果要展开说的话篇幅会比较大,我之后会单独出一篇文章来讲,大家只需要知道球谐系数能用于计算颜色就可以了。这里主要讲的是透明度。

        之前提到,高斯的透明度由一个标量\alpha表示。不过我们这里主要考虑的并不是\alpha,而是由协方差矩阵决定的高斯本身的透明度。\alpha只需要最后作为一个系数乘在这个透明度上就可以了。

        首先,由于我们的高斯已经转换为2D,所以现在的协方差矩阵也应该是一个2×2的矩阵。那有朋友可能要问了,刚刚用于计算2D协方差矩阵的几个数都是3×3的矩阵,那计算结果理应也是一个3×3的矩阵,怎么会是2×2呢?这个好说,我们简单粗暴地把矩阵的第3行和第3列去掉就好了。那现在协方差矩阵应该长这样:

\Sigma '= \begin{bmatrix} {\sigma}_x^2 & {\sigma}_{xy} \\ {\sigma}_{yx} & {\sigma}_y^2 \end{bmatrix}

        为了求出透明度,我们需要预先计算三个参数:

o_1 = \frac{​{\sigma}_y^2}{|\Sigma '|}

o_2 =- \frac{​{\sigma}_{xy}}{|\Sigma '|}

o_3 = \frac{​{\sigma}_x^2}{|\Sigma '|}

        对于2D平面上的一点\left< x,y \right>,该点的透明度为:

\alpha e^{- \frac{o_1 x^2 + o_3 y^2}{2} - o_2 xy}

        最后,将透明度乘以该点的颜色,就得到了最终的颜色。

        那么到此为止,整个3DGS的流程就算是完完全全地结束了。最后,我们来看看原论文的作者在3DGS中使用的一个小技巧。它虽然不算是最核心的部分,但也为模型的训练效率和效果提供了极大的帮助。

五、自适应密度控制

        自适应密度控制是一个既能加快模型收敛速度又能提高模型质量的非常重要的技巧。那什么是自适应密度控制呢?我们可以看一下原论文中的这张图:

b5aa136f955d46f7aaf206e938e917c2.png

        大概的意思是,假设我们要拟合这个有点像个月牙形的图案。在重建的初期,3D高斯都被初始化得比较小(也就是协方差矩阵的数值较小),因此无法表达出想要的形状。这时,我们可以对这个3D高斯进行复制,将原本的一个高斯复制为两个。两个高斯同时训练,能加快训练速度,同时也能拟合出单个高斯难以拟合的形状。到了训练后期,高斯的尺寸可能会过大,导致无法精细地拟合出这个形状。这时,我们可以将这个高斯拆分成两个较小的高斯,它们在经过训练后能够更加贴合我们想要的形状。作者设置了两个阈值,第一个用来判断一个高斯是否过小,另一个用于判断高斯是否过大。每次训练中,我们对每个高斯进行检查,如果尺寸小于第一个阈值,就将它复制;如果尺寸大于第二个阈值,就将它拆分。

        大家会发现,无论是对高斯进行复制还是拆分,高斯的总数都会增加,因此训练后的模型中的高斯数量会远大于训练前的模型。这是一件好事,因为更多的高斯有能力拟合更复杂的形状,但是这也可能导致出现一些冗余的高斯,占用大量的计算资源和储存空间。

六、下期预告

        目前对于下期文章写什么,我有四个打算:第一是写如何自己复现3DGS的代码,第二是COLMAP的使用方法,第三是讲球谐系数,第四是原论文代码的安装和使用教程。不知道大家想看什么,可以在评论区说一下,如果没有人说的话我就随便选一个写了。

        全文共11648字。码字不易,如果帮助到你,希望能留个赞。

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐