相机标定的目的

相机标定的最终目的是通过相机成像进行视觉测量,也就是通过图像的信息获取真实三维世界里的位置信息,而标定则是建立现实世界到图像的映射的过程,为了使测量可信,标定过程需要达成两个目的:

  1. 获取世界坐标系到像素坐标系的映射关系:获取相机的内参和外参;
  2. 获得相机的畸变参数:矫正畸变。

相机标定的原理

相机成像模型建立

相机成像是三次坐标系变换的过程:

  1. 世界坐标系到相机坐标系(刚体变换)

    $$\begin{bmatrix}x_c\\y_c\\z_c\\1\end{bmatrix}=\begin{bmatrix} R & t\\0^T & 1 \end{bmatrix} \begin{bmatrix} x_w\\y_w\\z_w\\1 \end{bmatrix}$$

    其中R是3×3 的正交旋转矩阵,t是3×1 的平移矢量,其实这个过程也可以用四元数或者欧拉角表达(这俩一般在游戏引擎里用得多),但是正交矩阵Rt在这里计算起来比较直观,可以设定世界坐标系里,标定平面与$x_w-y_w$平面重合,因此$z_w=0$,矩阵在计算时可进行以下简化:

    $$\begin{bmatrix} x_c \\ y_c \\ z_c \\ 1 \end{bmatrix} = \begin{bmatrix} R & t \\ 0^T & 1 \end{bmatrix} \begin{bmatrix} x_w \\ y_w \\ z_w \\ 1 \end{bmatrix} = \begin{bmatrix} r_1’ & r_2’ & r_3’ & t’ \end{bmatrix} \begin{bmatrix} x_w \\ y_w \\ 0 \\ 1 \end{bmatrix} = \begin{bmatrix} r_1 & r_2 & t’ \end{bmatrix} \begin{bmatrix} x_w \\ y_w \\ 1 \end{bmatrix}$$

  1. 相机坐标系到图像坐标系(中心透视投影)

    $$\begin{bmatrix}x_n\\y_n\\1\end{bmatrix}=\begin{bmatrix}f&0&0&0\\0&f&0&0\\0&0&1&0\end{bmatrix} \begin{bmatrix}x_c\\y_c\\z_c\\1\end{bmatrix}$$

    其中f为相机的焦距(这种中心投影模型仅适用于针孔相机模型,对广角不适用)。

  2. 图像坐标系到像素坐标系(仿射变换)

    $$\begin{bmatrix}x_u\\y_u\\1\end{bmatrix}=\begin{bmatrix}1/dx&0&u_0\\0&1/dy&v_0\\0&0&1\end{bmatrix}\begin{bmatrix}x_n\\y_n\\1\end{bmatrix}$$

    其中$u_0,v_0$为图像中心的像素坐标,$dx,dy$为图像每个像素的长宽。

其中1的转换矩阵$\begin{bmatrix} R & t\\0^T & 1 \end{bmatrix}$即为相机的外参,每张照片由于标定平面的位置不同,外参也不一样。

2的转换矩阵左乘上3的转换矩阵$\begin{bmatrix} f_x & 0 & u_0 & 0 \\ 0 & f_y & v_0 & 0 \\ 0 & 0 & 1 & 0 \end{bmatrix}$即为相机的内参,内参矩阵中(1, 2)位置的参数为扭曲参数,标准相机一般可将它设为0。

因此可以得到相机成像模型:

$$\begin{bmatrix} x_u \\ y_u \\ 1 \end{bmatrix} = \begin{bmatrix} f_x & 0 & u_0 & 0 \\ 0 & f_y & v_0 & 0 \\ 0 & 0 & 1 & 0 \end{bmatrix} \begin{bmatrix} R & t \\ 0^T & 1 \end{bmatrix} \begin{bmatrix} x_w \\ y_w \\ z_w \\ 1 \end{bmatrix} = \begin{bmatrix} f_x & 0 & u_0 & 0 \\ 0 & f_y & v_0 & 0 \\ 0 & 0 & 1 & 0 \end{bmatrix} \begin{bmatrix} r_1’ & r_2’ & t’ \end{bmatrix} \begin{bmatrix} x_w \\ y_w \\ 1 \end{bmatrix} = \begin{bmatrix} f_x & 0 & u_0 \\ 0 & f_y & v_0 \\ 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} r_1 & r_2 & t \end{bmatrix} \begin{bmatrix} x_w \\ y_w \\ 1 \end{bmatrix}$$

记内参矩阵为A,投影矩阵为H(3x3),则有:

$$z\begin{bmatrix}x_u\\y_u\\1\end{bmatrix}=A\begin{bmatrix}r_1&r_2&t\end{bmatrix}\begin{bmatrix}x_w\\y_w\\1\end{bmatrix}=H\begin{bmatrix}x_w\\y_w\\1\end{bmatrix}=\begin{bmatrix}h_1&h_2&h_3\end{bmatrix}\begin{bmatrix}x_w\\y_w\\1\end{bmatrix}$$

其中z为尺度因子,为简化计算提取出来。

求解内外参方法

求解H矩阵

根据多视几何的结论,可以令$x=\begin{bmatrix}h_1^{-T}\\h_2^{-T}\\h_3^{-T}\end{bmatrix}$,$M=\begin{bmatrix}x_w\\y_w\\1\end{bmatrix}$,则上式可化为:

$$\begin{bmatrix}M^T & 0^T & -x_u M^T\\0^T & M^T & -y_u M^T \end{bmatrix}x=0$$

设靶标上由n个可以确定位置的点,则有n组这样的方程组,组合起来为2n×9的矩阵,因为H为齐次矩阵,9个未知数只有8个是独立的,因此至少只需要4个点即可进行求解,可由奇异值分解求出x,即可解出投影矩阵H。

求解内外参

求出H矩阵后,根据R的正交性,有$r_1•r_2=0$$和$$r_1^Tr_1=r_2^Tr_2$,即有方程组:

$$\begin{cases} h_1^TA^{-T}A^{-1}h_2=0 \\ h_1^TA^{-T}A^{-1}h_1=h_2^TA^{-T}A^{-1}h_2 \end{cases}$$

令$B_{(3×3)}=A^{-T}A^{-1}$共有6个未知量,用b表示为:

$$b=\begin{bmatrix}1/f_x^2 & 0 & 1/f_y^2 & -u_0/f_x^2 & -v_0/f_y^2 & {u_0^2/f_x^2+v_0^2/f_y^2+1}\end{bmatrix}^T$$

可推出:

$$h_i^TBh_j=v_{ij}^Tb$$

$$v_{ij}=\begin{bmatrix} h_{i1}h{j1} & h_{i1}h_{j2} + h_{i2}h_{j1} & h_{i2}h_{j2} & h_{i3}h_{j1} + h_{i1}h_{j3} & h_{i3}h_{j2} + h_{i2}h_{j3} & h_{i3}h_{j3} \end{bmatrix}^T$$

因此有:

$$\begin{bmatrix} v_{12}^T \\ (v_{11}-v_{22})^T \end{bmatrix}b=0$$

设有N幅图像,则有N组这样的方程组,组合起来为2N×6的矩阵,可通过奇异值分解求出b,再通过Cholesky分解即可得到内参矩阵A。

A求出后根据$A\begin{bmatrix}r_1&r_2&t\end{bmatrix}=H$和$r_3=r_1×r_2$即可求出外参。

什么是递归

递归算法是一种直接或者间接调用自身函数或者方法的算法。

写代码也大半年了,平时在写代码时大多数情况下都用的循环,循环非常符合人的正向思维,先把整个循环过程构思好,给出初值、循环体、循环结束的条件再开始跑;最近看了一下递归,大惊,递归这种操作不正是我这种懒狗的福音吗,把一个复杂的问题抽象成只考虑解决其中的某一环和当这个问题被剥离成很简单的问题时的求解,如果说有什么其他的形式与递归相似的话,那就是数学归纳法了,不需要顺着从1推到n使等式成立,而是只需弄清n如何推导到n+1和n=1时成立就可以直接得出结论。

递归算法经典例子

汉诺塔问题

传说越南河内某间寺院有三根银棒,上串 64 个金盘。寺院里的僧侣依照一个古老的预言,以下述规则移动这些盘子;预言说当这些盘子移动完毕,世界就会灭亡。

有三根杆子A,B,C。A杆上有穿孔圆盘,盘的尺寸由下到上依次变小。要求按下列规则将所有圆盘移至 C 杆:

  1. 每次只能移动一个圆盘;
  2. 大盘不能叠在小盘上面。

大概在我上小学的时候,手机还不是很普及,玩过的前几个手机游戏就有汉诺塔,当时觉得这游戏巨难,根本不懂怎么移才能达成目标,就算把其他俩游戏玩腻了都不玩这个(当然现在也还不会)。

其实这个问题想要直接解决推出每个步骤是非常困难的,根据蒙的经验,我们先写几行看看规律

  • n=1时,直接把盘从A移到C,解决!
  • n=2时,把1盘从A移到B,再把2盘从A移到C,再把1盘从B移到C,解决!
  • n=3时,根据n=2的结论,先把1和2从A移到B,再把3从A移到C,再把1和2从B移到C,解决!

通过n=2和n=3的相似性大概已经可以看出操作的规律了

  • n=n时,根据n-1的结论,先把1到n-1从A移到B,再把n从A移到C,再把1到n-1从B移到C,解决!
1
2
3
4
5
6
7
8
void hannoi (int n, char from, char buffer, char to)
{
if (n == 0)
return;
hannoi (n - 1, from, to, buffer);
Console.WriteLine("Move disk " + n + " from " + from + " to " + to);
hannoi (n - 1, buffer, from, to);
}

通过递归的代码可以看出,通过递归实现的代码量是非常小的,汉诺塔问题如果要我通过循环解决,我还真不知道循环体和循环条件该怎么设置,递归简直就是救星,不过递归虽然简化了人的思考过程,但对于程序来说调用方法的次数太多了,对于栈空间的消耗非常大,入栈出栈的操作也非常多,代码的执行效率可能会大大降低。