坐标到达观察空间之后,我们需要将其投影到裁剪坐标。裁剪坐标会被处理至-1.0到1.0的范围内,并判断哪些顶点将会出现在屏幕上。
而
[
−
1.0
,
1.0
]
[-1.0,1.0]
[−1.0,1.0]构成的正方体又叫规则观察体(Canonical View Volume, CVV)
一、流程
计算机显示器是一个 2D 表面。OpenGL 渲染的 3D 场景必须作为 2D 图像投影到计算机屏幕上。
1、定义投影矩阵,指定了一个范围的坐标,以此来模拟相机的可视范围
2、投影矩阵接着会将在这个指定的范围内的坐标变换为标准化设备坐标的范围
[
−
1.0
,
1.0
]
[-1.0, 1.0]
[−1.0,1.0]。所有在范围外的坐标不会被映射到在-1.0到1.0的范围之间,所以会被裁剪掉。以此模拟相机的可视范围。
3、裁剪完后,进行透视除法,将4D裁剪空间坐标变换为3D标准化设备坐标。
过程中涉及到的坐标系变换如下:
View Space=>(投影矩阵)=> Clip Space => (透视除法) => NDC
二、正交投影
1、创建一个正射投影矩阵需要指定可见平截头体的宽、高和长度。在使用正射投影矩阵变换至裁剪空间之后处于这个平截头体内的所有坐标将不会被裁剪掉。
2、正交投影的变换矩阵可以理解为先将平截头体的中心平移到坐标系原点,再进行缩放
3、正交投影没有远近之分,所以
ω
\omega
ω为1,透视除法不改变方向,不翻转坐标系,直接对应理想的图像坐标系
三、透视投影
透视投影有两种理解方式:
一、看成透视到正交,再正交投影变换;
二、针孔模型的透视投影
第一种方式
如下所示,先将透视投影指定的平截头体变形成正交投影中的平截头体
可以看到,透视投影把所有通过原点(眼睛)的直线映射到平行于z轴的直线上,而并不移动z=n平面中直线上点的位置,如下所示:
变换过程中需要铭记两点:
- 变换前后近平面的点始终在近平面上;
- 变形前后远平面的点始终在远平面上;
如下所示:
根据第1、2点,且正交投影中平截头体的一条平行线在近平面、远平面的交点的
x
、
y
x、y
x、y坐标相等,有
近平面的交点
(
x
′
,
y
′
,
z
′
)
(x',y',z')
(x′,y′,z′)和远平面的蓝点(点
(
x
,
y
,
z
)
(x,y,z)
(x,y,z)变形之后)的
x
、
y
x、y
x、y坐标相等,由相似三角形得到:
y
′
=
n
z
y
x
′
=
n
z
x
y'=\frac{n}{z}y \quad x'=\frac{n}{z}x
y′=znyx′=znx由齐次坐标表示这一过程:
乘以
z
z
z是为了保留深度信息,所以将
ω
\omega
ω设为
z
z
z。
可以得到透视到正交的变换矩阵,
由近平面的点在透视到正交过程中不变,且近平面的
z
z
z坐标为
n
n
n,可以得到:
可以看到,第三行不含
x
、
y
x、y
x、y,因此必是
(
0
,
0
,
A
,
B
)
(0,0,A,B)
(0,0,A,B)。有:
同理,由远平面的点始终在远平面,远平面的
z
z
z坐标为
f
f
f,可得:
因此变换矩阵为:
M
p
e
r
s
p
→
o
r
t
h
o
=
(
n
0
0
0
0
n
0
0
0
0
n
+
f
−
n
f
0
0
1
0
)
M_{persp \rightarrow ortho}= \left( \begin{matrix} n & 0 & 0 & 0\\ 0 & n & 0 & 0\\ 0 & 0 & n+f & -nf\\ 0 & 0 & 1 & 0 \end{matrix} \right)
Mpersp→ortho=
n0000n0000n+f100−nf0
完整投影矩阵如下:
M
p
e
r
s
p
=
M
o
r
t
h
o
M
p
e
r
s
p
→
o
r
t
h
o
=
(
2
n
r
−
l
0
−
r
+
l
r
−
l
0
0
2
n
t
−
b
−
t
+
b
t
−
b
0
0
0
n
+
f
n
−
f
−
2
n
f
n
−
f
0
0
1
0
)
b
−
y
p
b
−
t
=
1
−
y
N
D
C
1
−
(
−
1
)
⇒
y
N
D
C
=
−
2
y
p
t
−
b
+
t
+
b
t
−
b
M_{persp}=M_{ortho}M_{persp \rightarrow ortho}=\left( \begin{matrix} \frac{2n}{r-l} & 0 & -\frac{r+l}{r-l} & 0 \\ 0 & \frac{2n}{t-b} & -\frac{t+b}{t-b} & 0 \\ 0 & 0 & \frac{n+f}{n-f} & -\frac{2nf}{n-f} \\ 0 & 0 & 1 & 0 \\ \end{matrix} \right)\\ \frac{b-y_p}{b-t}=\frac{1-y_{NDC}}{1-(-1)}\\ \Rightarrow y_{NDC}=-\frac{2y_p}{t-b}+\frac{t+b}{t-b}
Mpersp=MorthoMpersp→ortho=
r−l2n0000t−b2n00−r−lr+l−t−bt+bn−fn+f100−n−f2nf0
b−tb−yp=1−(−1)1−yNDC⇒yNDC=−t−b2yp+t−bt+b
注意:目前为止的内容均来自闫大神的GAMES101课件,上文出现的 l 、 r 、 t 、 b 、 n 、 f l、r、t、b、n、f l、r、t、b、n、f均为坐标,即包含正负值。并且裁剪空间为右手坐标系。
下面参考[9],根据相机坐标系、裁剪空间坐标系变换和相机朝向梳理常见投影矩阵。
在开始前需要注意以下四点:
1、OpenGL通常使用用户指定的 n n n和 f f f的绝对值,即相机朝向对公式有影响。为了与 M p e r s p M_{persp} Mpersp区分,通常设置 n e a r = ∣ n ∣ , f a r = ∣ f ∣ near=|n|,far=|f| near=∣n∣,far=∣f∣。
2、相机坐标系中 z e z_e ze表示与相机的位置关系,而裁剪空间中 ω c \omega_c ωc则体现了齐次坐标的意义——表示与相机的距离,即 z e z_e ze的模,因此有 ω c = ∣ z e ∣ \omega_c=|z_e| ωc=∣ze∣。
3、裁剪空间中 z c z_c zc表示深度,需要符合直觉——离得越近的物体,深度越小。
4、个人认为OpenGL的裁剪空间应为左手坐标系,因为透视除法只是对点的坐标进行缩放,并没有改变坐标系,而投影变换才是改变坐标系的原因。
- 相机坐标系
→
翻转
z
轴
\xrightarrow{翻转z轴}
翻转z轴裁剪空间,相机指向
z
z
z轴负向(OpenGL官方)
相机指向 z z z轴负向意味着 n = − ∣ n ∣ = − n e a r , f = − ∣ f ∣ = − f a r n=-|n|=-near,f=-|f|=-far n=−∣n∣=−near,f=−∣f∣=−far, M p e r s p M_{persp} Mpersp改写为:
M p e r s p = ( − 2 n e a r r − l 0 − r + l r − l 0 0 − 2 n e a r t − b − t + b t − b 0 0 0 n e a r + f a r n e a r − f a r 2 n e a r f a r n e a r − f a r 0 0 1 0 ) M_{persp}=\left( \begin{matrix} -\frac{2near}{r-l} & 0 & -\frac{r+l}{r-l} & 0 \\ 0 & -\frac{2near}{t-b} & -\frac{t+b}{t-b} & 0 \\ 0 & 0 & \frac{near+far}{near-far} & \frac{2nearfar}{near-far} \\ 0 & 0 & 1 & 0 \end{matrix} \right) Mpersp= −r−l2near0000−t−b2near00−r−lr+l−t−bt+bnear−farnear+far100near−far2nearfar0 相机坐标系: x x x正轴指向右侧, y y y正轴指向上方, z z z正轴指向相机的反方向(与GAMES101中相同)
裁剪空间: x x x正轴指向右侧, y y y正轴指向上方, z z z正轴指向相机同方向(与GAMES101中相同)
因此相机坐标系到裁剪空间只需翻转 z z z轴:
M r e v e r s e − z = ( 1 0 0 0 0 1 0 0 0 0 − 1 0 0 0 0 1 ) M_{reverse-z}=\left( \begin{matrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & -1 & 0\\ 0 & 0 & 0 & 1 \end{matrix} \right) Mreverse−z= 1000010000−100001 此外,在上文齐次坐标表示相似三角形那里,乘以 z z z,这个目的其实是为了将 ω e \omega_e ωe左乘 M p e r s p → o r t h o M_{persp \rightarrow ortho} Mpersp→ortho得到 ω c \omega_c ωc。 ω c = ∣ z e ∣ = − z e \omega_c=|z_e|=-z_e ωc=∣ze∣=−ze,即应该乘以 − z -z −z。
因此,有
M p e r s p → o r t h o O p e n G L = ( − 1 0 0 0 0 − 1 0 0 0 0 − 1 0 0 0 0 − 1 ) M p e r s p → o r t h o = M m i n u s M p e r s p → o r t h o M_{persp \rightarrow ortho}^{OpenGL}= \left( \begin{matrix} -1 & 0 & 0 & 0 \\ 0 & -1 & 0 & 0 \\ 0 & 0 & -1 & 0\\ 0 & 0 & 0 & -1 \end{matrix} \right)M_{persp \rightarrow ortho}=M_{minus}M_{persp \rightarrow ortho} Mpersp→orthoOpenGL= −10000−10000−10000−1 Mpersp→ortho=MminusMpersp→ortho最终OpenGL的投影矩阵 M O p e n G L M_{OpenGL} MOpenGL:
M O p e n G L = M r e v e r s e − z M o r t h o M m i n u s M p e r s p → o r t h o ⇒ M O p e n G L = M r e v e r s e − z M m i n u s M o r t h o M p e r s p → o r t h o ⇒ M O p e n G L = M r e v e r s e − z M m i n u s M p e r s p ⇒ M O p e n G L = ( 2 n e a r r − l 0 r + l r − l 0 0 2 n e a r t − b t + b t − b 0 0 0 n e a r + f a r n e a r − f a r 2 n e a r f a r n e a r − f a r 0 0 − 1 0 ) \begin{array}{ll} &M_{OpenGL}=M_{reverse-z}M_{ortho}M_{minus}M_{persp \rightarrow ortho}\\ \Rightarrow &M_{OpenGL}=M_{reverse-z}M_{minus}M_{ortho}M_{persp \rightarrow ortho}\\ \Rightarrow &M_{OpenGL}=M_{reverse-z}M_{minus}M_{persp}\\ \Rightarrow &M_{OpenGL}=\left( \begin{matrix} \frac{2near}{r-l} & 0 & \frac{r+l}{r-l} & 0 \\ 0 & \frac{2near}{t-b} & \frac{t+b}{t-b} & 0 \\ 0 & 0 & \frac{near+far}{near-far} & \frac{2nearfar}{near-far} \\ 0 & 0 & -1 & 0 \end{matrix} \right) \end{array} ⇒⇒⇒MOpenGL=Mreverse−zMorthoMminusMpersp→orthoMOpenGL=Mreverse−zMminusMorthoMpersp→orthoMOpenGL=Mreverse−zMminusMperspMOpenGL= r−l2near0000t−b2near00r−lr+lt−bt+bnear−farnear+far−100near−far2nearfar0
我们可以通过设置 l 、 r 、 b 、 t l、r、b、t l、r、b、t和near的值任意定义窗口,有时希望定义一个比较简单的系统,该系统是从窗口的中心看出去,因此 l = − r , b = − t l=-r,b=-t l=−r,b=−t,即OpenGL中gluPerspective(fov, aspect, near, far)函数的设置。
fov即视野,是视锥体在 x z xz xz平面或者 y z yz yz平面的开角角度,具体哪个平面都可以。OpenGL使用 y z yz yz平面,为俯仰角fovy。
aspect即投影平面的宽高比。
左侧为 x z xz xz平面,右侧为 y z yz yz平面。如图中所示, x z xz xz平面的top计算涉及除法,不安全,因此更多采用 y z yz yz平面。将右侧 y z yz yz平面公式代入投影矩阵,便得gluPerspective对应投影矩阵:
M p e r s p = ( 1 a s p e c t ⋅ t a n ( f o v y 2 ) 0 0 0 0 1 t a n ( f o v y 2 ) 0 0 0 0 n e a r + f a r n e a r − f a r − 2 n e a r f a r n e a r − f a r 0 0 − 1 0 ) M_{persp}=\left( \begin{matrix} \frac{1}{aspect \cdot tan(\frac{fovy}{2})} & 0 &0 & 0 \\ 0 & \frac{1}{tan(\frac{fovy}{2})} & 0 & 0 \\ 0 & 0 & \frac{near+far}{near-far} & -\frac{2nearfar}{near-far} \\ 0 & 0 & -1 & 0 \end{matrix} \right) Mpersp= aspect⋅tan(2fovy)10000tan(2fovy)10000near−farnear+far−100−near−far2nearfar0
- 相机坐标系
→
翻转
z
轴
\xrightarrow{翻转z轴}
翻转z轴裁剪空间,相机指向
z
z
z轴正向(OpenCV)
相机指向 z z z轴正向意味着 n = ∣ n ∣ = n e a r , f = ∣ f ∣ = f a r n=|n|=near,f=|f|=far n=∣n∣=near,f=∣f∣=far, M p e r s p M_{persp} Mpersp改写为:
M p e r s p = ( 2 n e a r r − l 0 − r + l r − l 0 0 2 n e a r t − b − t + b t − b 0 0 0 n e a r + f a r n e a r − f a r − 2 n e a r f a r n e a r − f a r 0 0 1 0 ) M_{persp}=\left( \begin{matrix} \frac{2near}{r-l} & 0 & -\frac{r+l}{r-l} & 0 \\ 0 & \frac{2near}{t-b} & -\frac{t+b}{t-b} & 0 \\ 0 & 0 & \frac{near+far}{near-far} & -\frac{2nearfar}{near-far} \\ 0 & 0 & 1 & 0 \end{matrix} \right) Mpersp= r−l2near0000t−b2near00−r−lr+l−t−bt+bnear−farnear+far100−near−far2nearfar0 相机坐标系: x x x正轴指向右侧, y y y正轴指向下方, z z z正轴指向相机同方向(与GAMES101中不同)
裁剪空间: x x x正轴指向右侧, y y y正轴指向上方, z z z正轴指向相机同方向(与GAMES101中不同)
因此需由GAMES101中坐标系先绕 x x x轴旋转180度到本相机坐标系,再由本坐标系翻转 y y y轴到裁剪空间:
M r o t a t e − x = ( 1 0 0 0 0 − 1 0 0 0 0 − 1 0 0 0 0 1 ) M r e v e r s e − y = ( 1 0 0 0 0 − 1 0 0 0 0 1 0 0 0 0 1 ) M_{rotate-x}=\left( \begin{matrix} 1 & 0 & 0 & 0 \\ 0 & -1 & 0 & 0 \\ 0 & 0 & -1 & 0\\ 0 & 0 & 0 & 1 \end{matrix} \right)\\ M_{reverse-y}=\left( \begin{matrix} 1 & 0 & 0 & 0 \\ 0 & -1 & 0 & 0 \\ 0 & 0 & 1 & 0\\ 0 & 0 & 0 & 1 \end{matrix} \right) Mrotate−x= 10000−10000−100001 Mreverse−y= 10000−10000100001 此外,在齐次坐标表示相似三角形处因 ω c = ∣ z e ∣ = z e \omega_c=|z_e|=z_e ωc=∣ze∣=ze,所以保持不变。
最终OpenCV的投影矩阵 M O p e n C V M_{OpenCV} MOpenCV:
M O p e n C V = M r e v e r s e − y M o r t h o M p e r s p → o r t h o M r o t a t e − x ⇒ M O p e n C V = M r e v e r s e − y M r o t a t e − x M o r t h o M p e r s p → o r t h o ⇒ M O p e n C V = ( 2 n e a r r − l 0 − r + l r − l 0 0 2 n e a r t − b − t + b t − b 0 0 0 − n e a r + f a r n e a r − f a r 2 n e a r f a r n e a r − f a r 0 0 1 0 ) \begin{array}{ll} &M_{OpenCV}=M_{reverse-y}M_{ortho}M_{persp \rightarrow ortho}M_{rotate-x}\\ \Rightarrow &M_{OpenCV}=M_{reverse-y}M_{rotate-x}M_{ortho}M_{persp \rightarrow ortho}\\ \Rightarrow &M_{OpenCV}=\left( \begin{matrix} \frac{2near}{r-l} & 0 & -\frac{r+l}{r-l} & 0 \\ 0 & \frac{2near}{t-b} & -\frac{t+b}{t-b} & 0 \\ 0 & 0 & -\frac{near+far}{near-far} & \frac{2nearfar}{near-far} \\ 0 & 0 & 1 & 0 \end{matrix} \right) \end{array} ⇒⇒MOpenCV=Mreverse−yMorthoMpersp→orthoMrotate−xMOpenCV=Mreverse−yMrotate−xMorthoMpersp→orthoMOpenCV= r−l2near0000t−b2near00−r−lr+l−t−bt+b−near−farnear+far100near−far2nearfar0
第二种方式
根据针孔相机的成像原理,将透视投影分为两步:
- 从平截头体内一点投影到近平面
- 对投影点坐标进行缩放,得到clip坐标,范围为 [ − ω , ω ] 4 [-\omega,\omega]^4 [−ω,ω]4
投影过程如下所示:
(
x
e
,
y
e
,
z
e
)
(x_e,y_e,z_e)
(xe,ye,ze)为观察空间中的点
a
a
a的3D坐标,
(
x
p
,
y
p
,
z
p
)
(x_p,y_p,z_p)
(xp,yp,zp)为点
a
a
a的投影点
a
′
a'
a′的坐标,
z
p
=
−
n
z_p=-n
zp=−n。
由相似三角形得到,
缩放成规则立方体:
(
x
c
,
y
c
,
z
c
,
ω
c
)
(x_c,y_c,z_c,\omega_c)
(xc,yc,zc,ωc)为点a在裁剪空间的齐次坐标,
ω
c
\omega_c
ωc设为
−
z
e
-z_e
−ze的原因上面已有说明。
将点
a
′
a'
a′的
x
p
x_p
xp和
y
p
y_p
yp以线性关系映射到裁剪空间的
x
c
x_c
xc和
y
c
y_c
yc,有:
[
l
,
r
]
→
[
−
ω
,
ω
]
→
[
z
e
,
−
z
e
]
[
b
,
t
]
→
[
−
ω
,
ω
]
→
[
z
e
,
−
z
e
]
[l,r] \rightarrow [-\omega,\omega] \rightarrow [z_e,-z_e]\\ [b,t] \rightarrow [-\omega,\omega] \rightarrow [z_e,-z_e]
[l,r]→[−ω,ω]→[ze,−ze][b,t]→[−ω,ω]→[ze,−ze]由线性归一化的等比例性质,有
{
x
p
−
l
r
−
l
=
x
c
−
z
e
−
z
e
−
z
e
y
p
−
b
t
−
b
=
y
c
−
z
e
−
z
e
−
z
e
⇒
{
x
c
=
−
2
z
e
x
p
−
l
r
−
l
+
z
e
=
−
2
z
e
n
⋅
x
e
−
z
e
−
l
r
−
l
+
z
e
=
2
n
r
−
l
⋅
x
e
+
r
+
l
r
−
l
⋅
z
e
y
c
=
−
2
z
e
y
p
−
b
t
−
b
+
z
e
=
−
2
z
e
n
⋅
y
e
−
z
e
−
b
t
−
b
+
z
e
=
2
n
t
−
b
⋅
y
e
+
t
+
b
t
−
b
⋅
z
e
\left\{ \begin{array}{ll} \frac{x_p-l}{r-l}=\frac{x_c-z_e}{-z_e-z_e} \\ \frac{y_p-b}{t-b}=\frac{y_c-z_e}{-z_e-z_e} \end{array} \right.\\ \Rightarrow \left\{ \begin{array}{ll} x_c=-2z_e\frac{x_p-l}{r-l}+z_e=-2z_e\frac{\frac{n \cdot x_e}{-z_e}-l}{r-l}+z_e=\frac{2n}{r-l}\cdot x_e+\frac{r+l}{r-l}\cdot z_e\\ y_c=-2z_e\frac{y_p-b}{t-b}+z_e=-2z_e\frac{\frac{n \cdot y_e}{-z_e}-b}{t-b}+z_e=\frac{2n}{t-b}\cdot y_e+\frac{t+b}{t-b}\cdot z_e \end{array} \right.
{r−lxp−l=−ze−zexc−zet−byp−b=−ze−zeyc−ze⇒{xc=−2zer−lxp−l+ze=−2zer−l−zen⋅xe−l+ze=r−l2n⋅xe+r−lr+l⋅zeyc=−2zet−byp−b+ze=−2zet−b−zen⋅ye−b+ze=t−b2n⋅ye+t−bt+b⋅ze从上述方程中,可以得到投影矩阵的第一、二行
实际上,
z
p
z_p
zp对于投影后的点
a
′
a'
a′已经没有意义了,这个信息点已经没用了,因此
z
c
z_c
zc不取决于
x
e
x_e
xe和
y
e
y_e
ye。但对于3D图形管线来说,为了便于进行后面的片元操作,例如
z
z
z缓冲消隐算法,有必要把投影之前的
z
e
z_e
ze保存下来,方便后面使用。因此:
z
c
=
A
z
e
+
B
已知
z
e
∈
[
−
n
,
−
f
]
→
z
c
∈
[
−
ω
c
,
ω
c
]
当
z
e
=
−
n
时,
z
c
=
−
ω
c
=
−
(
−
z
e
)
=
−
n
当
z
e
=
−
f
时,
z
c
=
ω
c
=
−
z
e
=
f
⇒
{
−
n
=
−
A
n
+
B
f
=
−
A
f
+
B
z_c=Az_e+B\\ 已知z_e \in [-n,-f] \rightarrow z_c \in [-\omega_c,\omega_c]\\ 当z_e=-n时,z_c=-\omega_c=-(-z_e)=-n\\ 当z_e=-f时,z_c=\omega_c=-z_e=f\\ \Rightarrow \left\{ \begin{array}{ll} -n=-An+B\\ f=-Af+B \end{array} \right.
zc=Aze+B已知ze∈[−n,−f]→zc∈[−ωc,ωc]当ze=−n时,zc=−ωc=−(−ze)=−n当ze=−f时,zc=ωc=−ze=f⇒{−n=−An+Bf=−Af+B求解得到
完整投影矩阵如下:
四、OpenGL和OpenCV结合
首先,需要明白OpenGl的透视投影模型和普通相机的小孔投影模型是类似的,其投影矩阵对应于相机的内参矩阵
K
K
K,观察矩阵对应于相机的外参矩阵
[
R
∣
T
]
[R|T]
[R∣T]。
内参矩阵
K
K
K:
K
=
[
f
x
0
c
x
0
f
y
c
y
0
0
1
]
K=\left[ \begin{matrix} fx& 0 & c_x \\ 0 & fy & c_y \\ 0 & 0 & 1 \\ \end{matrix} \right]
K=
fx000fy0cxcy1
f
x
、
f
y
fx、fy
fx、fy代表像距,表示相机在水平和垂直方向上的像距(
f
x
、
f
y
fx、fy
fx、fy一般相等),
c
x
、
c
y
cx、cy
cx、cy表示理想的图像坐标系原点在像素坐标系下的坐标。
将内参矩阵按照几何特性和光学特性拆分:
K
=
[
1
d
x
0
c
x
0
1
d
y
c
y
0
0
1
]
⋅
[
f
0
0
0
f
0
0
0
1
]
K=\left[ \begin{matrix} \frac{1}{dx}& 0 & c_x \\ 0 & \frac{1}{dy} & c_y \\ 0 & 0 & 1 \\ \end{matrix} \right] \cdot \left[ \begin{matrix} f& 0 & 0 \\ 0 & f & 0 \\ 0 & 0 & 1 \\ \end{matrix} \right]
K=
dx1000dy10cxcy1
⋅
f000f0001
左侧为几何特性,描述图像坐标系到像素坐标系的转换;右侧为光学特性,描述相机坐标系到图像坐标系的转换。
d
x
、
d
y
dx、dy
dx、dy表示
x
x
x轴与
y
y
y轴物理像素尺寸,即1个像素是
d
x
dx
dx毫米。
f
f
f表示像距,以下为方便与远平面
f
f
f区分,用
f
o
c
a
l
focal
focal表示。
有
f
x
=
f
d
x
,
f
y
=
f
d
y
f_x=\frac{f}{dx},f_y=\frac{f}{dy}
fx=dxf,fy=dyf
注意:在计算机视觉中,一般把 f f f称为焦距,其实是不准确的,在摄影测量学中更准确的表达是像距(主距),即焦点到像平面的距离。焦距实际上是光学中心到焦点的距离。
需要明白的是,观察空间的三维点是投影到像平面的,需要注意近平面、远平面和像平面的区别。在OpenGL中,近、远平面到原点的距离是由用户指定,默认近平面就是像平面,到原点的距离是
n
e
a
r
near
near,即
f
o
c
a
l
=
n
e
a
r
focal=near
focal=near。
视图矩阵不是本文的重点,因此,外参矩阵
[
R
∣
T
]
[R|T]
[R∣T]就不展开描述了。
K
K
K和
[
R
∣
T
]
[R|T]
[R∣T]不能直接使用,原因如下:
- OpenGL投影模型使用的坐标系与OpenCV的不同;
- OpenCV中,坐标系变换为世界坐标系->相机坐标系->图像坐标系->像素坐标系;
- OpenGL仅有屏幕空间(图像空间),没有图像空间与像素空间的区别,即世界坐标系->相机坐标系->裁剪空间->NDC->屏幕空间
OpenGL和OpenCV结合也因此有两种方式:
- 采用OpenGL坐标系变换
- 采用OpenCV坐标系变换
4.1 采用OpenGL坐标系变换
- 首先,需要明确的是,相机的外参矩阵和内参矩阵都是在OpenCV坐标系下的。
- 其次,根据自己的需求,确定相机空间和裁剪空间分别是什么坐标系。
- 然后,确定相机内参矩阵与投影矩阵参数 l 、 r 、 b 、 t l、r、b、t l、r、b、t对应关系。
- 最后,进行坐标系变换,确定最终的投影矩阵。
4.1.1 M O p e n C V M_{OpenCV} MOpenCV
最基本的,相机空间和裁剪空间都采用OpenCV标准,即投影矩阵为
M
O
p
e
n
C
V
M_{OpenCV}
MOpenCV
图中
U
、
V
U、V
U、V为像素坐标系,原点为左上角,
U
U
U轴向右,
V
V
V轴向下,
u
0
、
v
0
u_0、v_0
u0、v0(即
c
x
、
c
y
c_x、c_y
cx、cy)为图像坐标系原点;
X
、
Y
X、Y
X、Y属于OpenCV相机坐标系,原点为中心,
X
X
X轴向右,
Y
Y
Y轴向下。面ABCD为相机坐标系近平面,
W
、
H
W、H
W、H为近平面宽、高。因此,有:
l
=
−
u
0
⋅
d
x
=
−
u
0
n
e
a
r
f
x
r
=
(
W
−
u
0
)
⋅
d
x
=
(
W
−
u
0
)
n
e
a
r
f
x
b
=
(
H
−
v
0
)
⋅
d
y
=
(
H
−
v
0
)
n
e
a
r
f
y
t
=
−
v
0
⋅
d
y
=
−
v
0
n
e
a
r
f
y
l=-u_0 \cdot dx=-u_0\frac{near}{f_x}\\ r=(W-u_0) \cdot dx=(W-u_0)\frac{near}{f_x}\\ b=(H-v_0) \cdot dy=(H-v_0)\frac{near}{f_y}\\ t=-v_0 \cdot dy=-v_0\frac{near}{f_y}
l=−u0⋅dx=−u0fxnearr=(W−u0)⋅dx=(W−u0)fxnearb=(H−v0)⋅dy=(H−v0)fyneart=−v0⋅dy=−v0fynear代入投影矩阵
M
O
p
e
n
C
V
M_{OpenCV}
MOpenCV,最终形式为:
M
O
p
e
n
C
V
=
(
2
f
x
W
0
2
c
x
−
W
W
0
0
−
2
f
y
H
H
−
2
c
y
H
0
0
0
−
n
e
a
r
+
f
a
r
n
e
a
r
−
f
a
r
2
n
e
a
r
⋅
f
a
r
n
e
a
r
−
f
a
r
0
0
1
0
)
M_{OpenCV}=\left( \begin{matrix} \frac{2f_x}{W} & 0 & \frac{2c_x-W}{W} & 0 \\ 0 & -\frac{2f_y}{H} & \frac{H-2c_y}{H} & 0 \\ 0 & 0 & -\frac{near+far}{near-far} & \frac{2near \cdot far}{near-far} \\ 0 & 0 & 1 & 0 \\ \end{matrix} \right)
MOpenCV=
W2fx0000−H2fy00W2cx−WHH−2cy−near−farnear+far100near−far2near⋅far0
4.1.2 M O p e n G L M_{OpenGL} MOpenGL
广泛使用的是相机空间和裁剪空间都采用OpenGL标准,即投影矩阵为
M
O
p
e
n
G
L
M_{OpenGL}
MOpenGL。
注意,由于此处相机空间为OpenGL标准,外参矩阵
[
R
∣
T
]
[R|T]
[R∣T]需要坐标系变换。
图中
U
、
V
U、V
U、V为像素坐标系,原点为左上角,
U
U
U轴向右,
V
V
V轴向下,
u
0
、
v
0
u_0、v_0
u0、v0(即
c
x
、
c
y
c_x、c_y
cx、cy)为图像坐标系原点;
X
、
Y
X、Y
X、Y属于OpenGL相机坐标系,原点为中心,
X
X
X轴向右,
Y
Y
Y轴向上。面ABCD为相机坐标系近平面,
W
、
H
W、H
W、H为近平面宽、高。因此,有:
l
=
−
u
0
⋅
d
x
=
−
u
0
n
e
a
r
f
x
r
=
(
W
−
u
0
)
⋅
d
x
=
(
W
−
u
0
)
n
e
a
r
f
x
b
=
(
v
0
−
H
)
⋅
d
y
=
(
v
0
−
H
)
n
e
a
r
f
y
t
=
v
0
⋅
d
y
=
v
0
n
e
a
r
f
y
l=-u_0 \cdot dx=-u_0\frac{near}{f_x}\\ r=(W-u_0) \cdot dx=(W-u_0)\frac{near}{f_x}\\ b=(v_0-H) \cdot dy=(v_0-H)\frac{near}{f_y}\\ t=v_0 \cdot dy=v_0\frac{near}{f_y}
l=−u0⋅dx=−u0fxnearr=(W−u0)⋅dx=(W−u0)fxnearb=(v0−H)⋅dy=(v0−H)fyneart=v0⋅dy=v0fynear代入投影矩阵
M
O
p
e
n
G
L
M_{OpenGL}
MOpenGL,最终形式为:
M
O
p
e
n
G
L
=
(
2
f
x
W
0
W
−
2
c
x
W
0
0
2
f
y
H
2
c
y
−
H
H
0
0
0
n
e
a
r
+
f
a
r
n
e
a
r
−
f
a
r
2
n
e
a
r
⋅
f
a
r
n
e
a
r
−
f
a
r
0
0
−
1
0
)
M_{OpenGL}=\left( \begin{matrix} \frac{2f_x}{W} & 0 & \frac{W-2c_x}{W} & 0 \\ 0 & \frac{2f_y}{H} & \frac{2c_y-H}{H} & 0 \\ 0 & 0 & \frac{near+far}{near-far} & \frac{2near \cdot far}{near-far} \\ 0 & 0 & -1 & 0 \\ \end{matrix} \right)
MOpenGL=
W2fx0000H2fy00WW−2cxH2cy−Hnear−farnear+far−100near−far2near⋅far0
坐标系变换如下:
V
c
,
O
p
e
n
G
L
=
M
O
p
e
n
G
L
M
v
i
e
w
V
w
=
M
r
e
v
e
r
s
e
−
y
K
M
t
o
V
i
e
w
[
R
∣
t
]
V
w
,
M
t
o
V
i
e
w
为外参矩阵对应的坐标系变换
\begin{array}{ll} V_{c,OpenGL}&=M_{OpenGL}M_{view}V_w\\ &=M_{reverse-y}KM_{toView}[R|t]V_w,M_{toView}为外参矩阵对应的坐标系变换 \end{array}
Vc,OpenGL=MOpenGLMviewVw=Mreverse−yKMtoView[R∣t]Vw,MtoView为外参矩阵对应的坐标系变换
M
O
p
e
n
G
L
M_{OpenGL}
MOpenGL与
M
O
p
e
n
C
V
M_{OpenCV}
MOpenCV对应关系:
M
O
p
e
n
G
L
=
M
r
e
v
e
r
s
e
−
y
M
O
p
e
n
C
V
M
r
e
v
e
r
s
e
−
z
=
M
r
e
v
e
r
s
e
−
y
M
r
e
v
e
r
s
e
−
z
M
O
p
e
n
C
V
\begin{array}{ll} M_{OpenGL}&=M_{reverse-y}M_{OpenCV}M_{reverse-z}\\ &=M_{reverse-y}M_{reverse-z}M_{OpenCV} \end{array}
MOpenGL=Mreverse−yMOpenCVMreverse−z=Mreverse−yMreverse−zMOpenCV
当然,也可以相机空间为OpenGL标准,裁剪空间为OpenCV标准,反之亦然。但这两者用的较少,具体感兴趣的可以看[9]
4.2 采用OpenCV坐标系变换
这种方式不使用OpenGL管线实现3D到2D的转换,只是用其进行渲染,一般步骤如下[10]:
- 将像素坐标转换为纹理坐标
- 将纹理坐标转换为相机坐标系下的三维点
- 使用OpenCV相机内参矩阵,将相机坐标系下的三维点投影到相机模型的二维图像平面上
- 将二维坐标重新映射回图像坐标系下的像素坐标
- 绘制纹理映射后的图像
4.3 进阶-针孔投影模型去畸变
先简要描述畸变模型的作用:
畸变模型主要用于描述相机成像过程中由于透镜等光学元件引起的畸变效应。这些畸变效应导致实际成像与理想成像之间存在差异。常见的畸变包括径向畸变和切向畸变。
径向畸变(Radial Distortion):
桶形畸变(Barrel Distortion): 在图像边缘呈现出凸起的形状,导致离光轴较远的点在图像上显得更远。这是由于透镜对非中心位置的光线有更强的聚焦效应。
枕形畸变(Pincushion Distortion): 在图像边缘呈现凹陷的形状,导致离光轴较远的点在图像上显得更近。这是由于透镜对中心位置的光线有更强的聚焦效应。
切向畸变(Tangential Distortion):
由于透镜与成像平面不平行,造成图像中的物体呈现出倾斜或歪斜的形状。
畸变模型通常加在图像坐标系下,表达式如下:
首先,考虑径向畸变有:
x
d
=
x
n
⋅
(
1
+
k
1
r
2
+
k
2
r
4
+
k
3
r
6
)
y
d
=
y
n
⋅
(
1
+
k
1
r
2
+
k
2
r
4
+
k
3
r
6
)
x_d=x_n \cdot (1+k_1r^2+k_2r^4+k_3r^6)\\ y_d=y_n \cdot (1+k_1r^2+k_2r^4+k_3r^6)\\
xd=xn⋅(1+k1r2+k2r4+k3r6)yd=yn⋅(1+k1r2+k2r4+k3r6)其中,
r
2
=
x
n
2
+
y
n
2
r^2=x_n^2+y_n^2
r2=xn2+yn2,
k
1
、
k
2
、
k
3
k_1、k_2、k_3
k1、k2、k3是径向畸变系数。
接着,考虑切向畸变有:
x
d
d
=
x
d
+
(
2
p
1
x
n
y
n
+
p
2
(
r
2
+
2
x
n
2
)
)
y
d
d
=
y
d
+
(
p
1
(
r
2
+
2
y
n
2
)
+
2
p
2
x
n
y
n
)
x_{dd}=x_d+(2p_1x_ny_n+p_2(r^2+2x_n^2))\\ y_{dd}=y_d+(p_1(r^2+2y_n^2)+2p_2x_ny_n)
xdd=xd+(2p1xnyn+p2(r2+2xn2))ydd=yd+(p1(r2+2yn2)+2p2xnyn)其中,
p
1
、
p
2
p_1、p_2
p1、p2是切向畸变系数。
如果采用OpenCV与OpenGL结合的第一种方式,只需在透视除法后对NDC的
x
n
、
y
n
x_n、y_n
xn、yn应用上述公式即可。
如果采用OpenCV与OpenGL结合的第二种方式,只需将第3步改为畸变相机模型即可。
参考[8]中的加入畸变平面的投影模型的示意图:
其中,Normalized Plane即
(
x
n
,
y
n
,
1
)
(x_n,y_n,1)
(xn,yn,1)所在平面,Distortion Plane即
(
x
d
d
,
y
d
d
,
1
)
(x_{dd},y_{dd},1)
(xdd,ydd,1)所在平面。
参考
[1]OpenGL Projection Matrix
[2]深入探索透视投影变换
[3]坐标系统
[4]OpenGL与OpenCV实现增强现实
[5]GAMES101
[6]Fundamentals of Computer Graphics (4th Edition)
[7]焦平面、像平面、主距、焦距辨析
[8]吐血整理:从相机模型(针孔、鱼眼、全景)到OpenCV源码实现
[9]OpenGL 投影矩阵与摄像机内参的关系
[10]如何使用 OpenGL 实现 OpenCV 鱼眼相机模型对图像去畸变?