详解相机模型:矩阵变化+代码解析

0. 写在前面

在3dmm中,重要的一步是对3d模型进行拍照。

这里引出问题:怎么给3d模型拍照?

下面解决这个拍照问题。

1. 相机模型

本节目标:

  1. 理解针孔相机的模型,内参与径向畸变参数。
  2. 理解一个空间点是如何投影到相机成像。

我们知道:
一张照片(二维):由多个像素组成,每个像素记录了色彩或亮度的信息。
一个物体(三维世界):物体反射或发出的光线,通过相机光心后,投影到相机的成像平面。

相机将三维世界中的坐标点(单位米)映射到二维图像平面(单位像素)的过程能够用一个几何模型进行描述。

这个模型有很多种,其中最简单的为针孔模型。事实上,真实的相机镜头是透镜,会使得光线投影到成像平面的过程会产生畸变。

那么总结一下,所谓相机模型实际是是:针孔相机模型+畸变模型。

在3dmm中我们暂不考虑畸变模型。

1.1 针孔相机模型 (Pinhole camera)

详解相机模型:矩阵变化+代码解析

在小孔成像过程中,小孔模型将三维世界中的蜡烛投影到一个二维成像平面。

首先我们要先认识一下4种坐标:

  1. 世界坐标 (World reference system)
  2. 相机坐标 (camera coordinate)
  3. 归一化相机坐标 (normalized coordinate)
  4. 像素坐标 (pixel coordinate)

详解相机模型:矩阵变化+代码解析

我们来品一下这张图><。

  1. 世界坐标 是我们客观存在的世界,它有自己的固有坐标。在这里我们定义三维空间的三个方向分别为:xw,yw,zwx_w, y_w, z_w
    例子
    例子1: 长城。它就存在在那里,有它自己的坐标。
    例子2: 3d模型。3d模型在计算机中是以点的坐标来存储的,这个坐标代表点在xw,yw,zwx_w, y_w, z_w三个方向上的大小,比如一个点BB,它的存储形式是(12,15,18)(12, 15, 18)。这样的点有nn个,在3dmm中实际上有53215个这样的三维点,这些点组成了基本的人脸3d模型。

  2. 相机坐标 是从相机的角度去看世界,相机本身是这个坐标系的原点。
    在这里我们定义在这个角度的三维空间的三个方向分别为:xc,yc,zcx_c, y_c, z_c
    例子
    例子1: 小时候我总觉得门前的山特别高,后来长大之后回到老地方,发现这个山也没那么高嘛。山变了吗?山没变,是我看山的角度变了。
    例子2: 正所谓【横看成岭侧成峰】,说的也是这回事。

  3. 归一化相机坐标 物理成像平面。在这里我们可以定义三维空间的三个方向分别为:xn,yn,znx_n, y_n, z_n
    原点Oˊ\acute O是相机坐标的zcz_c轴与物理成像平面的交点。一般取znz_n的方向与zcz_c方向相同,将点PP在针孔相机模型到物理成像平面的距离统一为zn=fz_n = f,其中ff为焦距,这就相当于对相机坐标进行归一化。

  4. 像素坐标 水平方向是U,垂直方向是V,通过这个平面的,二维的UV坐标系。我们可以定位图象上的任意一个像素。
    详解相机模型:矩阵变化+代码解析

注意 这里的像素坐标的单位是像素,而上述3个坐标系的单位为米。
在这里我们定义二维空间的二个方向分别为:u,vu, v

1.2 计算过程

详解相机模型:矩阵变化+代码解析

================== 计算过程(1) 从世界坐标到相机坐标 ==================

已知我们有一个点PP,它的世界坐标(我们常说的坐标)为:
Pw=[xwywzw]P_w =\begin{bmatrix} x_w \\ y_w \\ z_w \end{bmatrix}

这个点PP,它在相机视角下的坐标为:
Pc=[xcyczc]P_c=\begin{bmatrix} x_c \\ y_c \\ z_c \end{bmatrix}

我们知道PwP_wPcP_c是同一点在不同坐标系下的表达方式,那么PwP_wPcP_c之间是什么关系呢?其实是:

PwP_w +\underrightarrow{旋转变换+平移变换} PcP_c

这里需要引入一下齐次坐标的概念。

齐次坐标就是将一个原本是n维的向量用一个n+1维向量来表示,是指一个用于投影几何里的坐标系统,如同用于欧氏几何里的笛卡儿坐标一般。

比如从欧式坐标变换到齐次坐标时,即欧式坐标 \rightarrow 齐次坐标
(x,y)=[xyz](x, y) = \begin{bmatrix} x \\ y\\ z\\ \end{bmatrix} (x,y,z)=[xyz1](x, y, z) = \begin{bmatrix} x \\ y\\ z\\ 1 \\\end{bmatrix}
从齐次坐标变换到欧式坐标时,即齐次坐标 \rightarrow 欧式坐标

[xyw]=(xw,yw)\begin{bmatrix} x \\ y\\ w\\ \end{bmatrix} = (\frac{x}{w}, \frac{y}{w}) [xyzw]=(xw,yw,zw)\begin{bmatrix} x \\ y\\ z\\ w \\ \end{bmatrix} = (\frac{x}{w}, \frac{y}{w},\frac{z}{w})

接下来回到PwP_w +\underrightarrow{旋转变换+平移变换} PcP_c

(1)旋转变换

Rx(α)=[ 1000cosαsinα0sinαcosα]R_x(\alpha) = \begin{bmatrix}\ 1 &amp; 0 &amp;0 \\ 0 &amp; \cos \alpha &amp; -\sin \alpha \\ 0 &amp;\sin \alpha &amp;\cos \alpha \\ \end{bmatrix}
Ry(β)=[ cosβ0sinβ010sinβ0cosβ]R_y(\beta) = \begin{bmatrix}\ \cos \beta&amp; 0 &amp; \sin \beta\\ 0 &amp; 1 &amp; 0 \\ - \sin \beta&amp; 0 &amp; \cos \beta\\ \end{bmatrix}
Rx(γ)=[cosγsinγ0sinγcosγ0001]R_x(\gamma) = \begin{bmatrix}\cos \gamma &amp; -\sin \gamma &amp; 0\\ \sin \gamma &amp; \cos \gamma &amp; 0 \\ 0 &amp; 0 &amp; 1 \\ \end{bmatrix}

此时:
R=Rx(α)Ry(β)Rx(γ)R = R_x(\alpha) R_y(\beta) R_x(\gamma)

(2)平移变换
T=[TxTyTz]T = \begin{bmatrix} T_x \\ T_y\\ T_z\\ \end{bmatrix}

结合(1)(2),那么对于点PP(下角标h_h表示齐次坐标):
Phc=[xcyczc1]=[RT01]4×4[xwywzw1]4×1=[RT01]4×4PhwP_{hc} = \begin{bmatrix} x_c \\ y_c\\ z_c\\ 1 \\ \end{bmatrix} = \begin{bmatrix} R&amp; T \\ 0 &amp; 1 \\ \end{bmatrix}_{4\times4} \begin{bmatrix} x_w \\ y_w\\ z_w\\ 1\end{bmatrix} _{4\times1 } = \begin{bmatrix} R&amp; T \\ 0 &amp; 1 \\ \end{bmatrix}_{4\times4} P_{hw}

================== 计算过程(2) 从相机坐标到像素坐标 ==================

由图可知:
详解相机模型:矩阵变化+代码解析

OOˊPˊ△O \acute O \acute POPˊPˊˊ△O \acute P\acute {\acute P}相似,那么对于点PP在相机坐标下的表示Pc=[xcyczc]P_c = \begin{bmatrix} x_c \\ y_c\\ z_c \end{bmatrix} 及在归一化相机坐标系里的表现Pn=[xnynzn]P_n = \begin{bmatrix} x_n \\ y_n\\ z_n \end{bmatrix}, (zn=fz_n = f):
xn=fxczcx_n = f \frac{x_c}{z_c} yn=fyczcy_n = f \frac{y_c}{z_c}

接下来要从归一化相机坐标系继续变换到像素坐标系,归一化相机坐标系与像素坐标系之间,相差了一个缩放原点的平移

我们设像素坐标在uu轴上缩放了α\alpha倍,在vv轴上缩放了β\beta倍。同时,原点平移了 [cxcy]\begin{bmatrix} c_x \\ c_y \end{bmatrix} ,那么像素坐标系下的PuvP_{uv}与归一化相机坐标系下的PnP_n的关系为:
u=αxn+cx=αfxczc+cxu = \alpha x_n + c_x = \alpha f \frac{x_c}{z_c} + c_x v=βyn+cy=βfyczc+cyv = \beta y_n+ c_y =\beta f \frac{y_c}{z_c} + c_y
我们设αf=fx\alpha f = f_x, βf=fy\beta f = f_y,得到:
u=fxxczc+cxu = f_x \frac{x_c}{z_c} + c_x v=fyyczc+cyv = f_y\frac{y_c}{z_c} + c_y

上述内容总结一下,用齐次坐标来表示:
Phuv=[uv1]=[fxxczc+cxfyyczc+cy1]P_{huv} = \begin{bmatrix} u \\ v \\ 1 \end{bmatrix} = \begin{bmatrix} {f_x \frac{x_c}{z_c} + c_x} \\ f_y\frac{y_c}{z_c} + c_y \\ 1 \end{bmatrix}
因为齐次坐标乘以非零常数后表达相同含义:

[uv1]=[fxxczc+cxfyyczc+cy1]=[fxxc+cxzcfyyc+cyzczc]\begin{bmatrix} u \\ v \\ 1 \end{bmatrix} = \begin{bmatrix} {f_x \frac{x_c}{z_c} + c_x} \\ f_y\frac{y_c}{z_c} + c_y \\ 1 \end{bmatrix} = \begin{bmatrix} f_x x_c + c_x z_c \\ f_y y_c + c_y z_c\\ z_c \end{bmatrix} =[fx0cx0fycy001][xcyczc] = \begin{bmatrix} f_x &amp; 0 &amp;c_x \\ 0 &amp; f_y &amp; c_y \\ 0 &amp; 0 &amp; 1\end{bmatrix}\begin{bmatrix} x_c \\ y_c \\ z_c \end{bmatrix}

回忆一下的(1)部分的结果:
Phc=[xcyczc1]=[RT01]4×4[xwywzw1]4×1=[RT01]4×4PhwP_{hc} = \begin{bmatrix} x_c \\ y_c\\ z_c\\ 1 \\ \end{bmatrix} = \begin{bmatrix} R&amp; T \\ 0 &amp; 1 \\ \end{bmatrix}_{4\times4} \begin{bmatrix} x_w \\ y_w\\ z_w\\ 1\end{bmatrix} _{4\times1 } = \begin{bmatrix} R&amp; T \\ 0 &amp; 1 \\ \end{bmatrix}_{4\times4} P_{hw}

这里的PchP_{ch}PcP_c齐次坐标,为了将这一部分带入[uv1]\begin{bmatrix} u \\ v \\ 1 \end{bmatrix},
[uv1]=[fxxczc+cxfyyczc+cy1]\begin{bmatrix} u \\ v \\ 1 \end{bmatrix} = \begin{bmatrix} {f_x \frac{x_c}{z_c} + c_x} \\ f_y\frac{y_c}{z_c} + c_y \\ 1 \end{bmatrix}
=[fxxc+cxzcfyyc+cyzczc]= \begin{bmatrix} f_x x_c + c_x z_c \\ f_y y_c + c_y z_c\\ z_c \end{bmatrix}
=[fx0cx0fycy001][xcyczc]= \begin{bmatrix} f_x &amp; 0 &amp;c_x \\ 0 &amp; f_y &amp; c_y \\ 0 &amp; 0 &amp; 1\end{bmatrix}\begin{bmatrix} x_c \\ y_c \\ z_c \end{bmatrix}
&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;\,\,\,\,\,\, \,\,\,\,\,\,\, \,\,\,\,\,\,\,\,\,\,\,\,\,\,\,\,\,\,\uparrow
我们称这个矩阵为内部参数KK

=[fx0cx00fycy00010]3×4[xcyczc1]4×1=\begin{bmatrix} f_x &amp; 0 &amp;c_x &amp; 0\\ 0 &amp; f_y &amp; c_y &amp; 0 \\ 0 &amp; 0 &amp; 1 &amp; 0 \end{bmatrix}_{3 \times 4} \begin{bmatrix} x_c \\ y_c \\ z_c \\ 1 \end{bmatrix}_{4 \times 1}
&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace; &ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;\,\,\,\,\,\, \,\,\,\,\,\,\, \,\,\,\,\,\,\,\,\,\,\,\,\,\,\,\,\,\,\uparrow\,\,\,\,\,\,\,\,\,\,\,\,\,\,\,\,\,\, \,\ \,\,\, \,\,\,\,\,\,\,\,\,\,\uparrow
这可以写作K[I0]K \begin{bmatrix} I &amp; 0 \end{bmatrix} &ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;\,\,\,\,\,\, \,\,\,\,\,\,\,\,\,是不是很眼熟,这不是PcP_c的齐次坐标嘛!

=[fx0cx00fycy00010]3×4[RT01]4×4[xwywzw1]4×1= \begin{bmatrix} f_x &amp; 0 &amp;c_x &amp; 0\\ 0 &amp; f_y &amp; c_y &amp; 0 \\ 0 &amp; 0 &amp; 1 &amp; 0 \end{bmatrix}_{3 \times 4} \begin{bmatrix} R&amp; T \\ 0 &amp; 1 \\ \end{bmatrix}_{4\times4} \begin{bmatrix} x_w \\ y_w\\ z_w\\ 1\end{bmatrix} _{4\times1 }

=K[I0][RT01]Phw=K\begin{bmatrix} I &amp; 0 \end{bmatrix} \begin{bmatrix} R&amp; T \\ 0 &amp; 1 \\ \end{bmatrix}P_{hw}

=K[RT]Phw=K\begin{bmatrix} R &amp; T \end{bmatrix} P_{hw}

============================== 总结 ==============================

[uv1]=[fx0cx00fycy00010]3×4[RT01]4×4[xwywzw1]4×1\begin{bmatrix} u \\ v \\ 1 \end{bmatrix} = \begin{bmatrix} f_x &amp; 0 &amp;c_x &amp; 0\\ 0 &amp; f_y &amp; c_y &amp; 0 \\ 0 &amp; 0 &amp; 1 &amp; 0 \end{bmatrix}_{3 \times 4} \begin{bmatrix} R&amp; T \\ 0 &amp; 1 \\ \end{bmatrix}_{4\times4} \begin{bmatrix} x_w \\ y_w\\ z_w\\ 1\end{bmatrix} _{4\times1 }

即:
Phuv=K[RT]PhwP_{huv} = K\begin{bmatrix} R &amp; T \end{bmatrix} P_{hw}

当我们有一个3d模型的n个点的三维坐标,可以通过这个变换得到它的照片。

2. 例子 + 代码

比如以我们提到的点BB为例,它的世界坐标是(12,15,18)(12, 15, 18)

================== 计算过程(1) 从世界坐标到相机坐标 ==================

(1)旋转变换

Rx(α)=[ 1000cosαsinα0sinαcosα]R_x(\alpha) = \begin{bmatrix}\ 1 &amp; 0 &amp;0 \\ 0 &amp; \cos \alpha &amp; -\sin \alpha \\ 0 &amp;\sin \alpha &amp;\cos \alpha \\ \end{bmatrix}
Ry(β)=[ cosβ0sinβ010sinβ0cosβ]R_y(\beta) = \begin{bmatrix}\ \cos \beta&amp; 0 &amp; \sin \beta\\ 0 &amp; 1 &amp; 0 \\ - \sin \beta&amp; 0 &amp; \cos \beta\\ \end{bmatrix}
Rx(γ)=[cosγsinγ0sinγcosγ0001]R_x(\gamma) = \begin{bmatrix}\cos \gamma &amp; -\sin \gamma &amp; 0\\ \sin \gamma &amp; \cos \gamma &amp; 0 \\ 0 &amp; 0 &amp; 1 \\ \end{bmatrix}

此时:
R=Rx(α)Ry(β)Rx(γ)R = R_x(\alpha) R_y(\beta) R_x(\gamma)

def angle2matrix(angles):
    
    ''' 
    根据右手系三个旋转角,
    得到三个旋转矩阵。
    Args:
        angles: [3,]. x, y, z angles
        x: pitch. positive for looking down.
        y: yaw. positive for looking left. 
        z: roll. positive for tilting head right. 
    Returns:
        R: [3, 3]. rotation matrix.
    
    '''
    
    #np.deg2rad将角度变为弧度pi
    x, y, z = np.deg2rad(angles[0]), np.deg2rad(angles[1]), np.deg2rad(angles[2])
    # x
    Rx=np.array([[1,      0,       0],
                 [0, cos(x),  -sin(x)],
                 [0, sin(x),   cos(x)]])
    # y
    Ry=np.array([[ cos(y), 0, sin(y)],
                 [      0, 1,      0],
                 [-sin(y), 0, cos(y)]])
    # z
    Rz=np.array([[cos(z), -sin(z), 0],
                 [sin(z),  cos(z), 0],
                 [     0,       0, 1]])
    
    R=Rz.dot(Ry.dot(Rx))
    return R.astype(np.float32)

(2)平移变换
T=[TxTyTz]T = \begin{bmatrix} T_x \\ T_y\\ T_z\\ \end{bmatrix}

结合(1)(2),那么对于点PP:
Phc=[xcyczc1]=[RT01]4×4[xwywzw1]4×1=[RT01]4×4PhwP_{hc} = \begin{bmatrix} x_c \\ y_c\\ z_c\\ 1 \\ \end{bmatrix} = \begin{bmatrix} R&amp; T \\ 0 &amp; 1 \\ \end{bmatrix}_{4\times4} \begin{bmatrix} x_w \\ y_w\\ z_w\\ 1\end{bmatrix} _{4\times1 } = \begin{bmatrix} R&amp; T \\ 0 &amp; 1 \\ \end{bmatrix}_{4\times4} P_{hw}

def similarity_transform(vertices, s, R, t3d):
    ''' similarity transform. dof = 7.
    3D: s*R.dot(X) + t
    Homo: M = [[sR, t],[0^T, 1]].  M.dot(X)
    Args:(float32)
        vertices: [nver, 3]. 
        s: [1,]. scale factor.
        R: [3,3]. rotation matrix.
        t3d: [3,]. 3d translation vector.
    Returns:
        transformed vertices: [nver, 3]
    '''
    t3d = np.squeeze(np.array(t3d, dtype = np.float32))
    transformed_vertices = s * vertices.dot(R.T) + t3d[np.newaxis, :]

    return transformed_vertices

值得注意的是:函数angle2matrix(angles)中从世界坐标到相机坐标的计算并没有用到齐次坐标。只在欧式坐标下计算,
Pc=R3×3[xwywzw]+[TxTyTz]P_c = R_{3 \times 3} \begin{bmatrix} x_w \\ y_w \\ z_w \end{bmatrix} + \begin{bmatrix} T_x \\ T_y \\ T_z \end{bmatrix}这并没有关系,因为齐次坐标和非齐次坐标本质上并没有区别:
Phc=[xcyczc1]&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;Pc=[xcyczc ]P_{hc} = \begin{bmatrix} x_c \\ y_c\\ z_c\\ 1 \\ \end{bmatrix} \, \, \, \, \, \, \, \, \, \, \, \, \, \, \, \, \, \, \, \, \, \, \, \, P_c = \begin{bmatrix} x_c \\ y_c\\ z_c\ \end{bmatrix} 只要在用的时候区分开就好。

我们之所以引入齐次坐标是为了直接计算Phuv=K[RT]PhwP_{huv} = K\begin{bmatrix} R &amp; T \end{bmatrix} P_{hw}

================== 计算过程(2) 从相机坐标到像素坐标 ==================

我们已经知道:

[uv1]=[fx0cx0fycy001][xcyczc] \begin{bmatrix} u \\ v \\ 1 \end{bmatrix} = \begin{bmatrix} f_x &amp; 0 &amp;c_x \\ 0 &amp; f_y &amp; c_y \\ 0 &amp; 0 &amp; 1\end{bmatrix}\begin{bmatrix} x_c \\ y_c \\ z_c \end{bmatrix}

还原一下这个过程,相机坐标\rightarrow归一化相机坐标\rightarrow像素坐标。

(2.1) 相机坐标\rightarrow归一化相机坐标
zn=fz_n = f xn=fxczcx_n = f \frac{x_c}{z_c} yn=fyczcy_n = f \frac{y_c}{z_c}

我们可以假设物品的深度,远远小于物体与相机间的距离,比如两个点i,ji,j,两个点的zcz_c方向坐标zizjz_i \approx z_j,此时我们可以直接删去zcz_c方向坐标,且归一化时我们取f=1f = 1:
zn=f=1z_n = f = 1 xn=xcx_n = x_c yn=ycy_n = y_c
这样的映射也称为平行映射(orthographic project).

def orthographic_project(vertices):
    return vertices.copy()

这里的代码虽然保留zcz_c,计算上其实我们只取xc,ycx_c, y_c

(2.2) 归一化相机坐标\rightarrow像素坐标

设归一化相机坐标\rightarrow像素坐标时无缩放, 归一化相机坐标的圆心Oˊ\acute O落在像素为(w,h)(w,h)的2d照片的中心,此时Oˊ\acute O在像素坐标系下的坐标为 (w/2,h/2)(w/2, h/2)

那么对于点BB:

u=fxxczc+cx=xc+w/2u = f_x \frac{x_c}{z_c} + c_x = x_c + w/2 v=fyyczc+cy=yc+h/21v = f_y\frac{y_c}{z_c} + c_y = -y_c + h/2 - 1

def to_image(vertices, h, w):
    ''' 
    Args:
        vertices: [nver, 3]
        h: height of the rendering
        w : width of the rendering
    Returns:
        projected_vertices: [nver, 3]  
    '''
    image_vertices = vertices.copy()

    # move to center of image
    image_vertices[:,0] = image_vertices[:,0] + w/2
    image_vertices[:,1] = image_vertices[:,1] + h/2
    # flip vertices along y-axis.
    image_vertices[:,1] = h - image_vertices[:,1] - 1
    return image_vertices

至此,得到三维点BB在照片里的像素坐标。