x
C
V
=
K
C
V
[
R
∣
t
]
X
\mathbf{x}_{CV}=\mathbf{K}_{CV}[\mathbf{R}|\mathbf{t}]\mathbf{X}
xCV=KCV[R∣t]X由于我们正在处理齐次坐标,因此需要对
x
C
V
\mathbf{x}_{CV}
xCV 进行归一化。假设向量的索引是以0为基础的(换句话说,第一个项目的索引为0,因此第三个项目的索引为(2):
x
C
V
=
x
C
V
x
C
V
(
2
)
\mathbf{x}_{CV}=\frac{\mathbf{x}_{CV}}{\mathbf{x}_{CV}(2)}
xCV=xCV(2)xCV 在此操作之后,
x
C
V
(
0
)
=
x
c
o
l
x
C
V
(
1
)
=
x
r
o
w
x
C
V
(
2
)
=
1
\begin{aligned}\mathbf{x}_{CV}(0)&=x_{col}\\\mathbf{x}_{CV}(1)&=x_{row}\\\mathbf{x}_{CV}(2)&=1\end{aligned}
xCV(0)xCV(1)xCV(2)=xcol=xrow=1
如果
x
c
o
l
x_{col}
xcol 或
x
r
o
w
x_{row}
xrow 不在图像空间内,则这些点不会绘制在图像上。例如,小于零的图像坐标会被丢弃,大于图像尺寸的坐标也会被丢弃。OpenGL中也发生了类似的过程,只需添加一个维度(z)即可!
N
D
C
\mathbf{NDC}
NDC(归一化设备坐标):
4
×
4
4 \times 4
4×4 内参相机标定矩阵。
K
G
L
\mathbf{K}_{GL}
KGL:
4
×
4
4 \times 4
4×4 内参相机标定矩阵。
R
\mathbf{R}
R(旋转):正交的
3
×
3
3 \times 3
3×3 矩阵。
t
\mathbf{t}
t(平移):列向量,大小为 3。
X
\mathbf{X}
X(世界坐标点):列向量,大小为 4。
x
G
L
\mathbf{x}_{GL}
xGL (图像点):列向量,大小为 4。
接着,
x
G
L
=
N
D
C
K
G
L
[
R
t
0
0
0
1
]
X
\mathbf{x}_{GL}=\mathbf{NDC} \ \ \mathbf{K}_{GL} \begin{bmatrix} \ &\mathbf{R} & \ & \mathbf{t} \\ 0 & 0& 0& 1\end{bmatrix}\mathbf{X}
xGL=NDCKGL[0R00t1]X
暂时不指定
N
D
C
\mathbf{NDC}
NDC 矩阵,而是指定 OpenGL 中所有坐标系统的工作方式。但不用担心,我们会逐一说明这些项目。
首先,在 OpenGL 中,有一个剪裁点/对象的概念,它们位于 near 和 far 平面之间。然而,在 OpenCV 框架中,我们认为位于主平面和正无穷远之间的任何点都是可视点,这在 OpenGL 中并非如此。为了解释这些平面,在图像空间中的剪裁(在上一节中)在 OpenCV 中非常直观——如果点不在图像中(定义为
∈
[
0
,
c
o
l
s
)
×
[
0
,
r
o
w
s
)
\in[0, cols)\times[0, rows)
∈[0,cols)×[0,rows)),则不绘制它——OpenGL 使用 4 元齐次向量来实现类似的目标。
我将 OpenGL 的 NDC 坐标表示为
x
G
L
\mathbf{x}_{GL}
xGL,它是一个具有 4 个元素的列向量。它也是一个齐次向量,其最后一个元素通常用字母 w 表示。与 OpenCV 表示法中一样,我们通过除以第 4 个条目(再次假设从 0 开始索引)对图像点
x
G
L
\mathbf{x}_{GL}
xGL 进行归一化;我将称 4 元素向量在最后一个元素等于 1 时归一化:
x
G
L
=
x
G
L
x
G
L
(
3
)
\mathbf{x}_{GL}=\frac{\mathbf{x}_{GL}}{\mathbf{x}_{GL}(3)}
xGL=xGL(3)xGL
与以前类似,我们得到相似的结果:
x
G
L
(
0
)
=
x
N
D
C
\mathbf{x}_{GL}(0)=x_{NDC}
xGL(0)=xNDC
x
G
L
(
1
)
=
y
N
D
C
\mathbf{x}_{GL}(1)=y_{NDC}
xGL(1)=yNDC
x
G
L
(
2
)
=
z
N
D
C
\mathbf{x}_{GL}(2)=z_{NDC}
xGL(2)=zNDC
x
G
L
(
3
)
=
1
\mathbf{x}_{GL}(3)=1
xGL(3)=1 这些坐标不一定是图像坐标。OpenGL 需要 z 值来计算对象的绘制顺序。NDC 空间是一个每边长度为 2 的立方体,其尺寸为
[
−
1
,
1
]
×
[
−
1
,
1
]
×
[
−
1
,
1
]
[-1, 1]\times[-1, 1]\times[-1, 1]
[−1,1]×[−1,1]×[−1,1]。Song Ho 的网站上有一些关于 NDC 空间的很好的插图。
如果
(
x
G
L
)
(\mathbf{x}_{GL})
(xGL) 中的任何坐标 a 满足
∣
a
∣
>
1
\mid a\mid > 1
∣a∣>1,那么
x
G
L
\mathbf{x}_{GL}
xGL 将不会被绘制(或者说,带有该坐标的边缘将被剪裁)。换句话说,如果任何坐标小于 -1 或大于 1,那么它就在 NDC 空间之外。
要将 OpenGL 的 NDC 坐标转换为 OpenGL 图像坐标,其中
x
i
m
a
g
e
,
G
L
\mathbf{x}_{image, GL}
ximage,GL 是一个3元素向量,且
x
G
L
\mathbf{x}_{GL}
xGL 已经归一化:
x
i
m
a
g
e
,
G
L
=
[
c
o
l
s
2
0
0
0
r
o
w
s
2
0
0
0
1
]
x
G
L
x_{image, GL} = \begin{bmatrix} \frac{cols}{2} & 0 & 0 \\ 0 & \frac{rows}{2} & 0 \\ 0 & 0 & 1 \end{bmatrix} x_{GL}
ximage,GL=2cols0002rows0001xGL
x
C
V
=
[
1
0
0
0
−
1
0
0
r
o
w
s
1
]
x
i
m
a
g
e
,
G
L
x_{CV} = \begin{bmatrix} 1 & 0 & 0 \\ 0 & -1 & 0 \\ 0 & rows & 1 \end{bmatrix} x_{image, GL}
xCV=1000−1rows001ximage,GL
OpenGL投影矩阵
首先,使用 OpenCV 矩阵中的
[
R
∣
t
]
[\mathbf{R}\mid\mathbf{t}]
[R∣t],但在其上添加一行使其成为方阵。例如:
[
R
∣
t
]
G
L
=
[
R
t
0
0
0
1
]
[\mathbf{R}\mid\mathbf{t}]_{GL} = \begin{bmatrix} \ & \mathbf{R} & \ & \mathbf{t} \\ 0 & 0 & 0 & 1\end{bmatrix}
[R∣t]GL=[0R00t1]
假设您在 OpenCV 上下文中拥有一个内参矩阵
K
C
V
\mathbf{K}_{CV}
KCV,其形式如下:
K
C
V
=
[
α
0
x
0
0
β
y
0
0
0
1
]
\mathbf{K}_{CV} = \begin{bmatrix} {\alpha} & 0 & x_0 \\ 0 & \beta & y_0 \\ 0 & 0 & 1\end{bmatrix}
KCV=α000β0x0y01
接下来,我们将使用这个修改过的 OpenCV 矩阵
K
C
V
\mathbf{K}_{CV}
KCV 来创建对应的 OpenGL 矩阵
K
G
L
\mathbf{K}_{GL}
KGL 透视投影矩阵。注意:在 OpenCV 内参矩阵的第一行第二列中的偏移参数很可能也可以通过类似 Kyle Simek 指南中描述的方式在
K
G
L
\mathbf{K}_{GL}
KGL 表示中对该参数取负值来在 OpenGL 中建模。然而,我没有测试过这个,而且我在标定时倾向于将偏移参数设置为零,所以留给你来测试!
A
=
−
(
n
e
a
r
+
f
a
r
)
A=−(near+far)
A=−(near+far)
B
=
n
e
a
r
∗
f
a
r
B=near∗far
B=near∗far
K
G
L
=
[
−
α
0
−
(
c
o
l
s
−
x
0
)
0
0
β
−
(
r
o
w
s
−
y
0
)
0
0
0
A
B
0
0
1
0
]
\mathbf{K}_{GL} = \begin{bmatrix} {-\alpha} & 0 & -(cols-x_0) & 0 \\ 0 & \beta & -(rows-y_0) & 0 \\ 0 & 0 & A & B \\ 0 & 0& 1 & 0\end{bmatrix}
KGL=−α0000β00−(cols−x0)−(rows−y0)A100B0
N
D
C
=
[
−
2
c
o
l
s
0
0
1
0
2
r
o
w
s
0
1
0
0
−
2
f
a
r
−
n
e
a
r
−
(
f
a
r
+
n
e
a
r
)
(
f
a
r
−
n
e
a
r
)
0
0
0
1
]
\mathbf{NDC} = \begin{bmatrix} {- \frac{2}{cols}} & 0 & 0 & 1 \\ 0 & \frac{2}{rows} & 0 & 1 \\ 0 & 0 & \frac{-2}{far-near} & \frac{-(far+near)}{(far-near)} \\ 0 & 0& 0 & 1\end{bmatrix}
NDC=−cols20000rows20000far−near−2011(far−near)−(far+near)1
现在,仔细看一下图 2 和这些矩阵,你可能会想,“天哪,为什么要在正负之间来回切换,从右手坐标系切换到左手坐标系等等。这不是拖累吗?”对此,我的回答是:“是的。”但是有几点需要注意:我从计算机视觉爱好者的角度介绍 OpenGL 管道,喜欢 H-Z 书和 OpenCV,并且有一定程度的手势。实际上,更令人困惑的是,OpenGL 的摄像机坐标系以负 z 轴为主轴。我再说一遍,以防你看到这里还没有注意到——如果你有标定时假定负 z 轴为主轴的矩阵,请查看其他资源。我做了很多测试以确认这是有效的。
在深入了解之前,如何测试您的相机模型?使用 Matlab 或 octave(免费)这样的脚本语言最简单,您还可以使用 C++ 和 Eigen、Python 或任何您熟悉的其他语言来完成。
从世界空间中获取一个坐标,这可以是来自对象(3D模型)文件。这是
X
\mathbf{X}
X,请记住,我们使用的是齐次坐标,因此它有 4 个元素。
使用您拥有的矩阵在 OpenCV 中计算投影到图像平面的
x
C
V
\mathbf{x}_{CV}
xCV 。
如果
x
C
V
\mathbf{x}_{CV}
xCV在图像平面上,那么
x
G
L
\mathbf{x}_{GL}
xGL也应该在图像平面上。如果在归一化后,
x
G
L
\mathbf{x}_{GL}
xGL 的任何坐标小于-1或大于+1,那么它将被裁剪。如果出现问题,请在此处进行故障排查!