OpenCV 相机转换为 OpenGL 相机

0.引言

1.预备知识和概述

本文假设您已经熟悉针孔相机的概念,这是OpenCV库中典型相机的相机校准模型。关于相机模型和投影几何的更多细节,最好的解释来自Richard Hartley和Andrew Zisserman的书《Multiple View Geometry in Computer Vision》,特别是第6章“相机模型”(这是我的极其偏见的看法)。我在本教程的其余部分将缩写该书为“H-Z书”。

使用OpenGL函数调用(例如glFrustrum()和glOrtho())可以设置许多相机参数。但是,在本教程中,我直接设置矩阵,然后将这些矩阵发送到着色器。如果这些细节对您来说是新的,请参考本教程中的代码示例,这将更加清晰。由于直接使用矩阵,本教程可能提供了一些线索,以推导出类似于针孔模型的相似相机模型的OpenGL投影矩阵。

2.资源

这是我用来解决这个问题的两个资源。两者都是很好的资源。如果您在这里,很可能您会发现这两个资源没有详细说明如何将OpenCV校准矩阵转换为OpenGL矩阵,这也是本教程的目标。

如果您不熟悉现代OpenGL,则下面的一组教程是一个很好的起点:

3.OpenCV和OpenGL中的图像坐标系统

3.1.OpenCV / H-Z和OpenGL中的主轴

首先,我们将讨论两个标准的图像坐标系统的所有细节。

在H-Z和OpenCV坐标系统中,相机都假设主轴与正 z 轴对齐。换句话说,正 z 轴指向相机的视场。另一方面,在OpenGL中,主轴与负z轴对齐在图像坐标系中。由于这些变化,两种表示之间的 x 轴也将旋转180度。

3.2.齐次坐标和OpenGL中的归一化设备坐标

  • 在OpenCV / H-Z框架中,有三个坐标系:图像坐标系,相机坐标系和世界坐标系。

  • 在OpenGL框架内,有四个坐标系:图像坐标系,相机坐标系,世界坐标系和… 归一化设备坐标或NDC。我将通过类比OpenCV / H-Z框架,说明这些坐标系如何工作,以及操作顺序和其他所需的基础知识。

4.OpenCV / H-Z框架中的投影

在这里插入图片描述
图1. 三个坐标系之间关系的示意图:OpenCV框架中的世界坐标系、相机坐标系和图像坐标系。相机所观察的场景位于相机坐标系的正Z轴方向。在图像坐标系中,请注意原点位于左下角,而Y轴向上-与数据矩阵布局中Y相反的方向。有关更多信息,请参见图3中的内容。( x 0 , y 0 x_0, y_0 x0,y0) 是图像坐标系中的主点,是在相机校准期间找到的一个参数。还有一个像素坐标系,不多解释了。


设:

  • K C V \mathbf{K}_{CV} KCV:上三角形的大小为 3 × 3 3 \times 3 3×3 的内部相机校准矩阵。
  • R \mathbf{R} R(旋转):正交的大小为 3 × 3 3 \times 3 3×3 的矩阵。
  • t \mathbf{t} t(平移):列向量,大小为 3。
  • X \mathbf{X} X(世界点):列向量,大小为 4。
  • x C V \mathbf{x}_{CV} xCV(图像点):列向量,大小为 3。

那么,

x C V = K C V [ R ∣ t ] X \mathbf{x}_{CV}=\mathbf{K}_{CV}[\mathbf{R}|\mathbf{t}]\mathbf{X} xCV=KCV[Rt]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)即可!

5.OpenGL框架中的投影

在这里插入图片描述
图2。重要的是,注意这个图是针对从OpenCV约定转换为OpenGL约定的高度特定的情况。通常,相机坐标系的主轴是负的 z。如果您的校准矩阵是这种情况,请停止并查阅其他指南。

假设我们假设的校准矩阵主轴是相机坐标系中的 +z,因此,与OpenCV相同,流水线的开始是

  • 通过旋转和平移 R ∣ t \mathbf{R}\mid\mathbf{t} Rt 将世界点转换。
  • 然后,相机坐标系略有不同;在OpenGL中有近平面和远平面的概念,这些参数由用户定义。通过 K G L \mathbf{K}_{GL} KGL ,将相机坐标系中的点转换到下一个空间 — 我称之为长方体空间,这不是一个正确的旋转和平移,而是一个反射到左手坐标系。
  • 然后, N D C \mathbf{NDC} NDC 变换将长方体空间变换为具有角为 ± 1 \pm 1 ±1 的立方体 — 标准化设备坐标系。

这就完成了所有用户需要指定的转换 — 一旦坐标处于左手标准化设备坐标系中,那么OpenGL将把这些坐标转换为图像坐标。要进行故障排除和自己进行转换,可以在Conversion Corner 1中找到方程式。


设:

  • 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=NDC  KGL[ 0R0 0t1]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 操作的输出并不是我们在 OpenCV 中习惯使用的真正的图像坐标——换句话说,不是数据矩阵中的坐标。您是对的。OpenGL 负责将其转换为图像空间,但了解这些转换是如何工作的是很有用的。因此,为了故障排除目的,请参阅下面的转换公式框。


要将 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= 2cols0002rows0001 xGL

注意,由于 OpenGL 中的图像坐标系统与 OpenCV 中的定义不同(参见图 3),因此需要进一步转换才能将这些坐标转换为 OpenCV 坐标:

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= 10001rows001 ximage,GL


OpenGL投影矩阵

首先,使用 OpenCV 矩阵中的 [ R ∣ t ] [\mathbf{R}\mid\mathbf{t}] [Rt],但在其上添加一行使其成为方阵。例如:

[ 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} [Rt]GL=[ 0R0 0t1]

假设您在 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 中建模。然而,我没有测试过这个,而且我在标定时倾向于将偏移参数设置为零,所以留给你来测试!

在完成这些准备工作,并以图像的 rows 和 cols 作为尺寸后,我们在 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=nearfar
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(colsx0)(rowsy0)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= cols20000rows20000farnear2011(farnear)(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
  • 将 OpenCV 矩阵中的所有值替换为上述 OpenGL 矩阵。请注意,Matlab 和/或 octave 是从 1 而不是 0 开始的索引的语言——相应地进行调整。
  • 如果 x C V \mathbf{x}_{CV} xCV在图像平面上,那么 x G L \mathbf{x}_{GL} xGL也应该在图像平面上。如果在归一化后, x G L \mathbf{x}_{GL} xGL 的任何坐标小于-1或大于+1,那么它将被裁剪。如果出现问题,请在此处进行故障排查!
  • 使用转换来检查 OpenCV 坐标是否等同于 OpenGL 坐标。

在这里插入图片描述

图 3. 上部子图展示了 OpenCV 和 OpenGL 上下文的图像坐标系统。左下角显示了 OpenGL 坐标系统的行(r)和列(c)索引的定义;OpenGL 坐标系统和图像坐标系统相同(r=y)。OpenCV 上下文中矩阵的原点在左上角,因此需要对从 glReadPixels() 抓取的 OpenGL 图像进行垂直翻转。

最后,我将以关于 数据矩阵(像素坐标系) 布局的一个学术观点来结束,换句话说,包含数据像素的行和列的索引,以及与图像坐标系统无关的布局。OpenGL 的布局与 OpenCV 不同,我在“转换角”中提到了这一点,并在图 3 的说明中详细描述和说明。

我的代码(此处)目前使用 OpenGL 渲染场景,具有正确的方向。然后,使用 glReadPixels() 抓取缓冲区,并将其写入 OpenCV Mat 图像结构 - 上下颠倒,以使其变为正向。详细代码信息参考原博文

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值