Opengl投影变换理解

坐标到达观察空间之后,我们需要将其投影到裁剪坐标。裁剪坐标会被处理至-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. 变形前后远平面的点始终在远平面上;

如下所示:
在这里插入图片描述
根据第1、2点,且正交投影中平截头体的一条平行线在近平面、远平面的交点的 x 、 y x、y xy坐标相等,有

在这里插入图片描述
近平面的交点 ( x ′ , y ′ , z ′ ) (x',y',z') (x,y,z)和远平面的蓝点(点 ( x , y , z ) (x,y,z) (x,y,z)变形之后)的 x 、 y x、y xy坐标相等,由相似三角形得到:
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 xy,因此必是 ( 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) Mpersportho= n0000n0000n+f100nf0 完整投影矩阵如下:
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=MorthoMpersportho= rl2n0000tb2n00rlr+ltbt+bnfn+f100nf2nf0 btbyp=1(1)1yNDCyNDC=tb2yp+tbt+b

注意:目前为止的内容均来自闫大神的GAMES101课件,上文出现的 l 、 r 、 t 、 b 、 n 、 f l、r、t、b、n、f lrtbnf均为坐标,即包含正负值。并且裁剪空间为右手坐标系。

下面参考[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的裁剪空间应为左手坐标系,因为透视除法只是对点的坐标进行缩放,并没有改变坐标系,而投影变换才是改变坐标系的原因。

  1. 相机坐标系 → 翻转 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= rl2near0000tb2near00rlr+ltbt+bnearfarnear+far100nearfar2nearfar0 相机坐标系: 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) Mreversez= 1000010000100001 此外,在上文齐次坐标表示相似三角形那里,乘以 z z z,这个目的其实是为了将 ω e \omega_e ωe左乘 M p e r s p → o r t h o M_{persp \rightarrow ortho} Mpersportho得到 ω 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} MpersporthoOpenGL= 1000010000100001 Mpersportho=MminusMpersportho最终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=MreversezMorthoMminusMpersporthoMOpenGL=MreversezMminusMorthoMpersporthoMOpenGL=MreversezMminusMperspMOpenGL= rl2near0000tb2near00rlr+ltbt+bnearfarnear+far100nearfar2nearfar0

我们可以通过设置 l 、 r 、 b 、 t l、r、b、t lrbt和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= aspecttan(2fovy)10000tan(2fovy)10000nearfarnear+far100nearfar2nearfar0

  1. 相机坐标系 → 翻转 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= rl2near0000tb2near00rlr+ltbt+bnearfarnear+far100nearfar2nearfar0 相机坐标系: 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) Mrotatex= 1000010000100001 Mreversey= 1000010000100001 此外,在齐次坐标表示相似三角形处因 ω 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=MreverseyMorthoMpersporthoMrotatexMOpenCV=MreverseyMrotatexMorthoMpersporthoMOpenCV= rl2near0000tb2near00rlr+ltbt+bnearfarnear+far100nearfar2nearfar0

第二种方式

根据针孔相机的成像原理,将透视投影分为两步:

  1. 从平截头体内一点投影到近平面
  2. 对投影点坐标进行缩放,得到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. {rlxpl=zezexczetbypb=zezeycze{xc=2zerlxpl+ze=2zerlzenxel+ze=rl2nxe+rlr+lzeyc=2zetbypb+ze=2zetbzenyeb+ze=tb2nye+tbt+bze从上述方程中,可以得到投影矩阵的第一、二行
实际上, 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)=nze=f时,zc=ωc=ze=f{n=An+Bf=Af+B求解得到
在这里插入图片描述
完整投影矩阵如下:
在这里插入图片描述

四、OpenGL和OpenCV结合

首先,需要明白OpenGl的透视投影模型和普通相机的小孔投影模型是类似的,其投影矩阵对应于相机的内参矩阵 K K K,观察矩阵对应于相机的外参矩阵 [ R ∣ T ] [R|T] [RT]
内参矩阵 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 fxfy代表像距,表示相机在水平和垂直方向上的像距( f x 、 f y fx、fy fxfy一般相等), c x 、 c y cx、cy cxcy表示理想的图像坐标系原点在像素坐标系下的坐标。
将内参矩阵按照几何特性和光学特性拆分:
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 dxdy表示 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=dxffy=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] [RT]就不展开描述了。
K K K [ R ∣ T ] [R|T] [RT]不能直接使用,原因如下:

  1. OpenGL投影模型使用的坐标系与OpenCV的不同;
  2. OpenCV中,坐标系变换为世界坐标系->相机坐标系->图像坐标系->像素坐标系
  3. OpenGL仅有屏幕空间(图像空间),没有图像空间与像素空间的区别,即世界坐标系->相机坐标系->裁剪空间->NDC->屏幕空间

OpenGL和OpenCV结合也因此有两种方式:

  1. 采用OpenGL坐标系变换
  2. 采用OpenCV坐标系变换

4.1 采用OpenGL坐标系变换

  1. 首先,需要明确的是,相机的外参矩阵和内参矩阵都是在OpenCV坐标系下的。
  2. 其次,根据自己的需求,确定相机空间和裁剪空间分别是什么坐标系。
  3. 然后,确定相机内参矩阵与投影矩阵参数 l 、 r 、 b 、 t l、r、b、t lrbt对应关系。
  4. 最后,进行坐标系变换,确定最终的投影矩阵。

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 UV为像素坐标系,原点为左上角, U U U轴向右, V V V轴向下, u 0 、 v 0 u_0、v_0 u0v0(即 c x 、 c y c_x、c_y cxcy)为图像坐标系原点; X 、 Y X、Y XY属于OpenCV相机坐标系,原点为中心, X X X轴向右, Y Y Y轴向下。面ABCD为相机坐标系近平面, W 、 H W、H WH为近平面宽、高。因此,有:
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=u0dx=u0fxnearr=(Wu0)dx=(Wu0)fxnearb=(Hv0)dy=(Hv0)fyneart=v0dy=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= W2fx0000H2fy00W2cxWHH2cynearfarnear+far100nearfar2nearfar0

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] [RT]需要坐标系变换。
在这里插入图片描述

图中 U 、 V U、V UV为像素坐标系,原点为左上角, U U U轴向右, V V V轴向下, u 0 、 v 0 u_0、v_0 u0v0(即 c x 、 c y c_x、c_y cxcy)为图像坐标系原点; X 、 Y X、Y XY属于OpenGL相机坐标系,原点为中心, X X X轴向右, Y Y Y轴向上。面ABCD为相机坐标系近平面, W 、 H W、H WH为近平面宽、高。因此,有:
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=u0dx=u0fxnearr=(Wu0)dx=(Wu0)fxnearb=(v0H)dy=(v0H)fyneart=v0dy=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= W2fx0000H2fy00WW2cxH2cyHnearfarnear+far100nearfar2nearfar0
坐标系变换如下:
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=MreverseyKMtoView[Rt]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=MreverseyMOpenCVMreversez=MreverseyMreversezMOpenCV

当然,也可以相机空间为OpenGL标准,裁剪空间为OpenCV标准,反之亦然。但这两者用的较少,具体感兴趣的可以看[9]

4.2 采用OpenCV坐标系变换

这种方式不使用OpenGL管线实现3D到2D的转换,只是用其进行渲染,一般步骤如下[10]:

  1. 将像素坐标转换为纹理坐标
  2. 将纹理坐标转换为相机坐标系下的三维点
  3. 使用OpenCV相机内参矩阵,将相机坐标系下的三维点投影到相机模型的二维图像平面上
  4. 将二维坐标重新映射回图像坐标系下的像素坐标
  5. 绘制纹理映射后的图像

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_1​r^2+k_2​r^4+k_3​r^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 k1k2k3是径向畸变系数。
接着,考虑切向畸变有:
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 p1p2是切向畸变系数。
如果采用OpenCV与OpenGL结合的第一种方式,只需在透视除法后对NDC的 x n 、 y n x_n、y_n xnyn应用上述公式即可。
如果采用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 鱼眼相机模型对图像去畸变?

  • 1
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
要使用OpenGL绘制立方体的正交投影,需要进行以下步骤: 1. 设置正交投影矩阵 使用glOrtho函数设置正交投影矩阵。该函数的原型如下: ``` void glOrtho(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble nearVal, GLdouble farVal); ``` 其中,left、right、bottom和top表示投影平面的四个边界,nearVal和farVal表示和远的裁剪平面距离,单位为坐标系中的单位。 2. 绘制立方体 使用glBegin和glEnd函数开始和结束绘制操作,并使用glVertex函数绘制立方体的顶点。注意,需要绘制出立方体的六个面。 3. 设置模型视图矩阵 使用glMatrixMode和glLoadIdentity函数将当前矩阵模式设置为模型视图矩阵,并将其初始化为单位矩阵。 4. 设置立方体的位置和方向 使用glTranslatef、glRotatef和glScalef函数对立方体进行平移、旋转和缩放操作,以确定其在场景中的位置和方向。 完整的OpenGL代码如下所示: ``` #include <GL/glut.h> void display() { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // 设置正交投影矩阵 glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(-1, 1, -1, 1, -1, 1); // 绘制立方体 glColor3f(1, 0, 0); glBegin(GL_QUADS); // front face glVertex3f(-0.5, -0.5, 0.5); glVertex3f( 0.5, -0.5, 0.5); glVertex3f( 0.5, 0.5, 0.5); glVertex3f(-0.5, 0.5, 0.5); // back face glVertex3f(-0.5, -0.5, -0.5); glVertex3f(-0.5, 0.5, -0.5); glVertex3f( 0.5, 0.5, -0.5); glVertex3f( 0.5, -0.5, -0.5); // top face glVertex3f(-0.5, 0.5, -0.5); glVertex3f(-0.5, 0.5, 0.5); glVertex3f( 0.5, 0.5, 0.5); glVertex3f( 0.5, 0.5, -0.5); // bottom face glVertex3f(-0.5, -0.5, -0.5); glVertex3f( 0.5, -0.5, -0.5); glVertex3f( 0.5, -0.5, 0.5); glVertex3f(-0.5, -0.5, 0.5); // left face glVertex3f(-0.5, -0.5, -0.5); glVertex3f(-0.5, -0.5, 0.5); glVertex3f(-0.5, 0.5, 0.5); glVertex3f(-0.5, 0.5, -0.5); // right face glVertex3f( 0.5, -0.5, 0.5); glVertex3f( 0.5, -0.5, -0.5); glVertex3f( 0.5, 0.5, -0.5); glVertex3f( 0.5, 0.5, 0.5); glEnd(); // 设置模型视图矩阵 glMatrixMode(GL_MODELVIEW); glLoadIdentity(); // 设置立方体的位置和方向 glTranslatef(0, 0, -2); glRotatef(45, 1, 1, 0); glutSwapBuffers(); } int main(int argc, char **argv) { glutInit(&argc, argv); glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH); glutInitWindowSize(400, 400); glutCreateWindow("Orthographic Projection"); glutDisplayFunc(display); glEnable(GL_DEPTH_TEST); glutMainLoop(); return 0; } ```
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值