赞
踩
3DGS 的 投影采用的是 CG系的投影矩阵 P P P, 默认相机的 principal point (相机光心) 位于图像的中点处。但是 实际应用的 绝大多数的 相机 并不满足这样一个设定, 因此我们 需要根据 f , c x , c y {f,c_x, c_y} f,cx,cy 这几个参数重新构建3D GS 的 投影矩阵。
目的: 将一个 相机View 坐标系的一个3D 点 变换到 NDC 坐标系
维基百科:https://www.songho.ca/opengl/gl_projectionmatrix.html
View 坐标系 通过转化 矩阵 M p r o j M_{proj} Mproj 转化到Clip 坐标系。**先进行 缩放变换,**缩放之后的坐标是 ( x p , y p , z p ) (x_p,y_p,z_p) (xp,yp,zp), 缩放之后继续做正交投影 【 就是把 (l,r)映射到 (-1,1)】,最后才可以变换到Clip坐标系下面的坐标 ( x c , y c , z c ) (x_c,y_c,z_c) (xc,yc,zc)。
Clip 坐标系通过除以 齐次坐标系的 最后一个分量转换到 NDC 坐标系
n为视锥体近面z坐标,f为远面z坐标,
t为视锥体top面z坐标,b为 bottom面y坐标,
r为视锥体right x坐标,left为左面x坐标,
主要是通过相似三角形的 原理去列方程:
x
p
=
−
n
⋅
x
e
z
e
=
n
⋅
x
e
−
z
e
x_p=\frac{-n \cdot x_e}{z_e}=\frac{n \cdot x_e}{-z_e}
xp=ze−n⋅xe=−zen⋅xe
y
p
=
−
n
⋅
y
e
z
e
=
n
⋅
y
e
−
z
e
y_p=\frac{-n \cdot y_e}{z_e}=\frac{n \cdot y_e}{-z_e}
yp=ze−n⋅ye=−zen⋅ye
z
p
z_p
zp 坐标的求解,可以观看 闫令琪 的计算机图像学:有两个基本假设:
得到了 缩放之后的
(
x
p
,
y
p
,
z
p
)
(x_p,y_p, z_p)
(xp,yp,zp), 然后我们再通过线性变换做正交投影将Cuboid 的长和宽分别缩放到 一个 单位立方体, 即将 [l, r] ⇒ [-1, 1] and [b, t] ⇒ [-1, 1]。
Eq2:
x
c
=
α
x
x
p
+
β
x
x_c=\alpha_x x_p+\beta_x
xc=αxxp+βx
y
c
=
α
y
y
p
+
β
y
y_c=\alpha_y y_p+\beta_y
yc=αyyp+βy
将 x p x_p xp和 x e x_e xe 的关系带入上面Eq2式子当中。以 x 坐标为例,由于l对应-1,r对应1,求解出 α \alpha α 和 β \beta β我们有:
x c = 2 x p r − l − r + l r − l ( x p = n x e − z e ) = 2 ⋅ n ⋅ x e − z e r − l − r + l r − l = 2 n ⋅ x e ( r − l ) ( − z e ) − r + l r − l = 2 n r − l ⋅ x e − z e − r + l r − l = 2 n r − l ⋅ x e − z e + r + l r − l ⋅ z e − z e = ( 2 n r − l ⋅ x e + r + l r − l ⋅ z e ⏟ x c ) / − z e xc=2xpr−l−r+lr−l(xp=nxe−ze)=2⋅n⋅xe−zer−l−r+lr−l=2n⋅xe(r−l)(−ze)−r+lr−l=2nr−l⋅xe−ze−r+lr−l=2nr−l⋅xe−ze+r+lr−l⋅ze−ze=(2nr−l⋅xe+r+lr−l⋅ze⏟xc)/−ze xc=r−l2xp−r−lr+l(xp=−zenxe)=r−l2⋅−zen⋅xe−r−lr+l=(r−l)(−ze)2n⋅xe−r−lr+l=−zer−l2n⋅xe−r−lr+l=−zer−l2n⋅xe+−zer−lr+l⋅ze=(xc r−l2n⋅xe+r−lr+l⋅ze)/−ze
y c = 2 y p t − b − t + b t − b ( y p = n y e − z e ) = 2 ⋅ n ⋅ y e − z e t − b − t + b t − b = 2 n ⋅ y e ( t − b ) ( − z e ) − t + b t − b = 2 n t − b ⋅ y e − z e − t + b t − b = 2 n t − b − z e ⋅ y e + t + b t − b − z e = ( 2 n t − b ⋅ y e + t + b t − b ⋅ z e ⏟ y c ) / − z e yc=2ypt−b−t+bt−b(yp=nye−ze)=2⋅n⋅ye−zet−b−t+bt−b=2n⋅ye(t−b)(−ze)−t+bt−b=2nt−b⋅ye−ze−t+bt−b=2nt−b−ze⋅ye+t+bt−b−ze=(2nt−b⋅ye+t+bt−b⋅ze⏟yc)/−ze yc=t−b2yp−t−bt+b(yp=−zenye)=t−b2⋅−zen⋅ye−t−bt+b=(t−b)(−ze)2n⋅ye−t−bt+b=−zet−b2n⋅ye−t−bt+b=−zet−b⋅ye2n+−zet−bt+b=(yc t−b2n⋅ye+t−bt+b⋅ze)/−ze
上面的恰好是 Clip 坐标系的 齐次坐标系。 发现 计算的
x
c
,
y
c
x_c,y_c
xc,yc 恰好是 除以了
−
z
e
-z_e
−ze, 因此 我们可以预先指定 齐次坐标的 第四项是:
w
c
=
−
z
e
w_c = -z_e
wc=−ze earlier。 下面是 Clip 坐标系的齐次坐标:
(
x
c
y
c
z
c
w
c
)
=
(
2
n
r
−
l
0
r
+
l
r
−
l
0
0
2
n
t
−
b
t
+
b
t
−
b
0
⋅
⋅
⋅
⋅
0
0
−
1
0
)
(
x
e
y
e
z
e
w
e
)
\left(xcyczcwc\right)=\left(2nr−l0r+lr−l002nt−bt+bt−b0⋅⋅⋅⋅00−10\right)\left(xeyezewe\right)
xcyczcwc
=
r−l2n0⋅00t−b2n⋅0r−lr+lt−bt+b⋅−100⋅0
xeyezewe
$Z的推导 和 x,y 没有关系。因此 上面的矩阵写成下面的形式:
(
x
c
y
c
z
c
w
c
)
=
(
2
n
r
−
l
0
r
+
l
r
−
l
0
0
2
n
t
−
b
t
+
b
t
−
b
0
0
0
A
B
0
0
−
1
0
)
(
x
e
y
e
z
e
w
e
)
\left(xcyczcwc\right)=\left(2nr−l0r+lr−l002nt−bt+bt−b000AB00−10\right)\left(xeyezewe\right)
xcyczcwc
=
r−l2n0000t−b2n00r−lr+lt−bt+bA−100B0
xeyezewe
,
其中的 Z 项 单目提出来应该等于下面的式子:
z
n
=
z
c
/
w
c
=
A
z
e
+
B
w
e
−
z
c
z_n=z_c / w_c=\frac{A z_e+B w_e}{-z_c}
zn=zc/wc=−zcAze+Bwe
最后根据: Z_near 平面 和 Z_far 平面不会移动的原因,得到最后的 投影矩阵:
M
p
r
o
j
=
(
2
n
r
−
l
0
r
+
l
r
−
l
0
0
2
n
t
−
b
t
+
b
t
−
b
0
0
0
−
(
f
+
n
)
f
−
n
−
2
f
n
f
−
n
0
0
−
1
0
)
M_{proj}=\left(2nr−l0r+lr−l002nt−bt+bt−b000−(f+n)f−n−2fnf−n00−10\right)
Mproj=
r−l2n0000t−b2n00r−lr+lt−bt+bf−n−(f+n)−100f−n−2fn0
3DGS 设定:
self.zfar = 100.0
self.znear = 0.01
下面这个 Projection_Matrix 的构建 和上面公式推导会有一点不一样的地方,尤其是对于 Z值的计算上,Github 上也有人提出过疑问。 矩阵的P[2,2] 有误, 但是作者又说 他在 Code 中没有 使用 Z的数值。
https://github.com/graphdeco-inria/gaussian-splatting/issues/388
https://github.com/graphdeco-inria/gaussian-splatting/issues/376
def getProjectionMatrix(znear, zfar, fovX, fovY):
tanHalfFovY = math.tan((fovY / 2)) ## 视场角一半的正切数值
tanHalfFovX = math.tan((fovX / 2))
## 得到 l,b,top,right
top = tanHalfFovY * znear
bottom = -top
right = tanHalfFovX * znear
left = -right
P = torch.zeros(4, 4)
z_sign = 1.0
P[0, 0] = 2.0 * znear / (right - left)
P[1, 1] = 2.0 * znear / (top - bottom)
P[0, 2] = (right + left) / (right - left)
P[1, 2] = (top + bottom) / (top - bottom)
P[3, 2] = z_sign
P[2, 2] = z_sign * zfar / (zfar - znear)
P[2, 3] = -(zfar * znear) / (zfar - znear)
return P
或者其他人 也给了 Projection 基于 相机内参的写法:
https://github.com/graphdeco-inria/gaussian-splatting/issues/399
可以验证,下面的 Projection Matrix 和 Lego 这种 principal point 在中心的 场景,生成的 4*4 的 Projection Matrix 是完全一样的。
P[0, 0] = 2 * fx / W
P[1, 1] = 2 * fy / H
P[0, 2] = 2 * (cx / W) - 1.0
P[1, 2] = 2 * (cy / H) - 1.0
P[2, 2] = -(zfar + znear) / (zfar - znear)
P[3, 2] = 1.0
P[2, 3] = -(2 * zfar * znear) / (zfar - znear)
实际使用的过程当中,使用 W2C 矩阵,并不是 C2W 矩阵
3dGS 的外参数处理部分:
poses[idx,:3, 1:3] *= -1
# get the world-to-camera transform and set R, T
w2c = np.linalg.inv(poses[idx])
R = np.transpose(w2c[:3,:3]) # R is stored transposed due to 'glm' in CUDA code
T = w2c[:3, 3]
jiaxin 的 Gendata.py 是利用直接从 kitti360 读取的 Pose 来生成点云的,是 OpenCV 系下面的位姿, 生成点云并不是 用的 json 文件里的 Pose , 因此 嘉欣的 生成的 3DGS 的点云也是 OpenCV 系的点云。 我们在 readkitti.py 里面 3DGS对于 Pose 的处理已经从OpenGL 系转到了 OpenCV 系,所以不需要 对于点云进行处理,直接使用。
Centor Pose 对于在KITTI360上面对结果影响不大,但也不会降低结果可以加上。
但是 Scale Pose 在 3DGS 的源码中,会使得结果有略微的降低. 所以 ,不应该加上 Scale, 对于 场景进行缩放, 全部采用 世界系中的真实尺度。
nerf_normalization = getNerfppNorm(train_cam_infos
)
参考的网址如下:
- https://github.com/graphdeco-inria/gaussian-splatting/issues/38
这里主要讨论了 在不同的场景之下,场景的大小不一样,那么 Guassion 的 xyz 的学习率应该随着场景的大小变化而变化。比如:场景大的话,那么 Gaussian 应该需要移动的 距离是更远的。
50% 的点 有500 万个点 ,所有指标效果提升。
20% 的点有200 万个点, SSIM 和PSNR 继续提升。 Lpips 有部分下降。
结论: 1000 万个点 过多,不推荐。 使用 一定Drop 可以帮助 提升重建质量。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。