VIO第6讲:投影模型与三角化
文章目录
- VIO第6讲:投影模型与三角化
- 3 三角化
- 3.1 坐标变换与投影模型
- 3.2 单目三角化
- 3.2.1 三角化推导
- 3.2.2 三角化求解
- 3.2.3 求解注意点
- 3.2.4 测量值噪声
- 3.2.5 特征点数量对结果的影响
- 4 SVD分解
3 三角化
在三角化之前,复习一遍视觉投影模型与坐标变换,这对于求雅可比和三角化都是非常重要的。
3.1 坐标变换与投影模型
① 世界系->相机系 X w Y w Z w > > X c Y c Z c X_wY_wZ_w>>X_cY_cZ_c XwYwZw>>XcYcZc
已知某点P在世界坐标系中的坐标 P w = ( X w , Y w , Z w ) T P_{w}=(X_{w},Y_{w},Z_{w})^{T} Pw=(Xw,Yw,Zw)T, 求其在相机坐标系中的坐标 P c = ( X c , Y c , Z c ) T P_c=(X_c,Y_c,Z_c)^T Pc=(Xc,Yc,Zc)T.
第一种方式
Pc = Rcw*Pw + tcw
[ X c Y c Z c ] = R c w ⋅ [ X w Y w Z w ] + t c w \begin{bmatrix}Xc\\Yc\\Zc\end{bmatrix}=R_{cw}\cdot\begin{bmatrix}Xw\\Yw\\Zw\end{bmatrix}+t_{cw} XcYcZc =Rcw⋅ XwYwZw +tcw
合并为齐次矩阵(投影矩阵)表达如下
[ X c Y c Z c 1 ] = [ R c w t c w → O 1 ] ⋅ [ X w Y w Z w 1 ] \begin{bmatrix}Xc\\Yc\\Zc\\1\end{bmatrix}=\begin{bmatrix}R_{cw}&t_{cw}\\\rightarrow\\O&1\end{bmatrix}\cdot\begin{bmatrix}Xw\\Yw\\Zw\\1\end{bmatrix} XcYcZc1 = Rcw→Otcw1 ⋅ XwYwZw1
第二种方式
Pc = Rcw*(Pw - twc) = Rcw*Pw - Rcw*twc
实际中使用第一种方式中的投影矩阵(
3*4
),其中的tcw
往往由第二种方式推出tcw = -Rcw*twc
相机系坐标除以深度Z后,就是归一化坐标
② 相机->图像 X c Y c Z c > > x y X_cY_cZ_c>>xy XcYcZc>>xy
实际上图像系坐标与归一化坐标差一个焦距f的缩放!
引入图像坐标系,即先求出相机坐标系下的点 P c = ( X c , Y c , Z c ) T P_c=(X_c,Y_c,Z_c)^T Pc=(Xc,Yc,Zc)T在图像坐标系下的表达 p ( x , y ) p(x,y) p(x,y)。利用相似三角形以及比例的原理,有以下推导及公式:
x f = X c Z c y f = Y c Z c \begin{aligned}\frac xf&=\frac{X_c}{Z_c}\\\frac yf&=\frac{Y_c}{Z_c}\end{aligned} fxfy=ZcXc=ZcYc
所以,我们就能够推出上面结论:图像系坐标与归一化坐标差一个焦距f的缩放
x = f X c Z c and y = f Y c Z c x=\frac{fX_c}{Z_c}\text{and}y=\frac{fY_c}{Z_c} x=ZcfXcandy=ZcfYc
转换为矩阵乘法,其中 ( X c , Y c , Z c , 1 ) T (X_c,Y_c,Z_c,1)^T (Xc,Yc,Zc,1)T是空间点P
在相机坐标系的齐次坐标, ( x , y , 1 ) T (x,y,1)^T (x,y,1)T是图像点p
在图像坐标系中的齐次坐标。
Z c ⋅ [ x y 1 ] = [ f 0 0 0 0 f 0 0 0 0 1 0 ] ⋅ [ X c Y c Z c 1 ] Z_c\cdot\begin{bmatrix}x\\y\\1\end{bmatrix}=\begin{bmatrix}f&0&0&0\\0&f&0&0\\0&0&1&0\end{bmatrix}\cdot\begin{bmatrix}X_c\\Y_c\\Z_c\\1\end{bmatrix} Zc⋅ xy1 = f000f0001000 ⋅ XcYcZc1
③ 图像->像素 x y > > u v xy>>uv xy>>uv
设图像坐标系中心在像素坐标系的表达为 u 0 , v 0 u_0,v_0 u0,v0,图像上的投影点是 p ( x , y ) p(x,y) p(x,y)
d x d_{x} dx像素宽度, d y d_{y} dy像素高度分别表示每个像素在水平u和竖直v方向上的实际物理尺寸(单位mm)。
u = x d x + u 0 v = y d y + v 0 \begin{aligned}u&=\frac x{dx}+u_0\\\\v&=\frac y{dy}+v_0\end{aligned} uv=dxx+u0=dyy+v0
x d x \frac x{dx} dxx就表示在u方向占了多少像素,再加上中心坐标,就是实际的像素坐标。写成矩阵形式:
[ u v 1 ] = [ 1 d x o u 0 0 1 d y v 0 0 0 1 ] ⋅ [ x y 1 ] \begin{bmatrix}u\\v\\1\end{bmatrix}=\begin{bmatrix}\dfrac{1}{dx}&o&u_0\\0&\dfrac{1}{dy}&v_0\\0&0&1\end{bmatrix}\cdot\begin{bmatrix}x\\y\\1\end{bmatrix} uv1 = dx100ody10u0v01 ⋅ xy1
④ 世界->像素
把上面几个变换结合起来
Z c [ u v 1 ] = [ 1 d x o u 0 0 1 d y v 0 0 0 1 ] [ f 0 0 0 0 f 0 0 0 0 1 0 ] [ R t O → 1 ] [ X w Y w Z w 1 ] Z_c\begin{bmatrix}u\\v\\1\end{bmatrix}=\begin{bmatrix}\dfrac{1}{dx}&o&u_0\\0&\dfrac{1}{dy}&v_0\\0&0&1\end{bmatrix}\begin{bmatrix}f&0&0&0\\0&f&0&0\\0&0&1&0\end{bmatrix}\begin{bmatrix}R&t\\\overrightarrow{O}&1\end{bmatrix}\begin{bmatrix}Xw\\Yw\\Zw\\1\end{bmatrix} Zc uv1 = dx100ody10u0v01 f000f0001000 [ROt1] XwYwZw1
合并左边两个矩阵,即常见的内参矩阵 K 3 ∗ 4 K_{3*4} K3∗4,外参矩阵 T 3 ∗ 4 T_{3*4} T3∗4。其中, f x = f d x , f y = f d y f_x=\frac f{d_x},f_y=\frac f{d_y} fx=dxf,fy=dyf可以理解为焦距f
分别在x,y
轴方向等价的像素个数(每个像素的物理尺寸为dx×dy
,单位为mm
)
Z c [ u v 1 ] = [ f x 0 u 0 0 0 f y v 0 0 0 0 1 0 ] [ R t O → 1 ] [ X w Y w Z w 1 ] Z_c\begin{bmatrix}u\\v\\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\\\overrightarrow{O}&1\end{bmatrix}\begin{bmatrix}Xw\\Yw\\Zw\\1\end{bmatrix} Zc uv1 = fx000fy0u0v01000 [ROt1] XwYwZw1
⑤ 问题
问题:
已知一款相机佳能80D及镜头参数如下:最高分辨率6000×4000,传感器尺寸22.3×14.9mm,焦距f=35mm,根据相机内参定义,估算相机的内参fx,fy,u0,v0
答案:
fx = f / dx = 35 / (22.3/6000) = 9417.040358744
fy = f / dy = 35 / (14.9/4000) = 9395.973154362
u0= 6000 / 2 = 3000
v0= 4000 / 2 = 2000
由于制造工艺等因素,相机像素坐标系中心未必与光轴经过的图像坐标系中心重合,即(u0,v0)并非是像素坐标系的中心,而是在中心附近某个位置,焦距及像素物理尺寸也非绝对精准,故而需要通过实际的内参标定过程,确定相机的内参矩阵。
参考链接
3.2 单目三角化
3.2.1 三角化推导
某路标点L在若干个关键帧 k = 1,··· ,n
中看到,取齐次坐标 y ∈ R 4 \mathbf{y}\in\mathbb{R}^4 y∈R4,观测取归一化坐标 ( x , y , 1 ) (x,y,1) (x,y,1),这样可以忽略相机内参,只利用相机系和世界系坐标。
λ [ x y 1 ] = [ R c w t c w ] ⋅ [ X w Y w Z w 1 ] λ\begin{bmatrix}x\\y\\1\end{bmatrix}=\begin{bmatrix}R_{cw}&t_{cw}\\\end{bmatrix}\cdot\begin{bmatrix}Xw\\Yw\\Zw\\1\end{bmatrix} λ xy1 =[Rcwtcw]⋅ XwYwZw1
利用投影矩阵P第三行就能代入矩阵上面两行,即一个特征点通过投影矩阵P可以提供两个约束方程!
对于n个匹配特征点, D ∈ R 2 n × 4 \mathbf{D}\in\mathbb{R}^{2n\times4} D∈R2n×4。
[ x 1 P 1 , 3 ⊤ − P 1 , 1 ⊤ y 1 P 1 , 3 ⊤ − P 1 , 2 ⊤ ⋮ x n P n , 3 ⊤ − P n , 1 ⊤ y n P n , 3 ⊤ − P n , 2 ⊤ ] [ X w Y w Z w 1 ] = 0 → D L = 0 \left.\left[\begin{array}{c}x_1\mathbf{P}_{1,3}^\top-\mathbf{P}_{1,1}^\top\\y_1\mathbf{P}_{1,3}^\top-\mathbf{P}_{1,2}^\top\\\vdots\\x_n\mathbf{P}_{n,3}^\top-\mathbf{P}_{n,1}^\top\\y_n\mathbf{P}_{n,3}^\top-\mathbf{P}_{n,2}^\top\end{array}\right.\right]\begin{bmatrix}Xw\\Yw\\Zw\\1\end{bmatrix}=\mathbf{0}\to\mathbf{D}\mathbf{L}=\mathbf{0} x1P1,3⊤−P1,1⊤y1P1,3⊤−P1,2⊤⋮xnPn,3⊤−Pn,1⊤ynPn,3⊤−Pn,2⊤ XwYwZw1 =0→DL=0
三角化数学推导具体推导参考链接,同ORB-SLAM。构建矩阵D的代码如下
int D_dim_row = 2 * (end_frame_id - start_frame_id);int D_dim_col = 4;Eigen::MatrixXd D = Eigen::MatrixXd::Zero(D_dim_row, D_dim_col);// 每一个特征点可以提供两行约束for(int i = start_frame_id; i < end_frame_id; i++){Eigen::MatrixXd P = Eigen::MatrixXd::Zero(3, 4);// 构建投影矩阵// P.block<3,3>(0,0) = camera_pose[i].Rwc.transpose();P.block(0, 0, 3, 3) = camera_pose[i].Rwc.transpose(); // RcwP.block(0, 3, 3, 1) = -camera_pose[i].Rwc.transpose() * camera_pose[i].twc; // tcw// 归一化坐标点(u,v,1)----当前帧-----在Eigen库中,可以使用( )操作符或者[ ]操作符---或uv.x() uv.ydouble x = camera_pose[i].uv[0];double y = camera_pose[i].uv[1];// 代入公式----每一个特征点可以提供两行约束D.block(2 * (i - start_frame_id), 0, 1 , 4) = x * P.row(2) - P.row(0); D.block(2 * (i - start_frame_id) + 1, 0, 1 , 4) = y * P.row(2) - P.row(1);}
3.2.2 三角化求解
在观测次于大于等于两次时,很可能 D
满秩,无零空间,这样就只有0解,但这是不对的。所以,寻求最小二乘解来替代:
min y ∥ D L ∥ 2 2 , \min_\mathbf{y}\left\|\mathbf{DL}\right\|_2^2, ymin∥DL∥22,
然后,利用SVD分解进行求解(关于SVD,可以参考博客,这里就是要知道SVD分解中的D和V都是可以由被分解矩阵与其转置的乘积的特征向量组成,所以下面最小奇异值对应最小特征向量!在第4部分给出代码测试,还有不要和下面的D混淆)
D ⊤ D = ∑ i = 1 4 σ i 2 u i u j ⊤ \mathbf{D}^\top\mathbf{D}=\sum_{i=1}^4\sigma_i^2\mathbf{u}_i\mathbf{u}_j^\top D⊤D=i=1∑4σi2uiuj⊤
最后,右奇异矩阵的最后一列就是最终的解。至于原有,曾经在ORB-SLAM2中推导过
还看到一个利用拉格朗日算子推理的
代码实现
Eigen::JacobiSVD<Eigen::MatrixXd> svd(D.transpose()*D, Eigen::ComputeThinU | Eigen::ComputeThinV);// 调用svd对象的singularValues()成员函数,矩阵D的奇异值Eigen::Vector4d delta = svd.singularValues();// 取最小奇异值对应的右奇异向量----注意上面是对D^T*D进行了SVD分解,相当于对一个对称矩阵进行特征分解,两边UV都是一样的,所以这里取U也可以。但如果是对D进行SVD分解,只能是取V!Eigen::Vector4d u4 = svd.matrixU().col(3);
3.2.3 求解注意点
① 判断该解有效性的条件 σ 4 ≪ σ 3 \sigma_{4}\ll\sigma_{3} σ4≪σ3
if(delta(3) * 1e-3 > delta(2)){ // 一般来讲奇异值都是从小到大,只要视差合理,这种极端情况不会出现std::cout << "The parallax is not enough" << std::endl;return -1;}
② σ 4 ! = 0 \sigma_{4}!= 0 σ4!=0 且深度>0
if(u4(3) != 0 && u4(2) / u4(3) > 0){P_est = u4.head<3>() / u4(3);}
③ Rescale
在某此场合(比如我们使用 UTM
坐标的平移),D
的数值大小差异明显,导致解在数值上不稳定。此时对 D
做一个缩放
D y = D S ⏟ D ~ S − 1 y ⏟ y ~ = 0 \mathrm{Dy=\underbrace{DS}_{\tilde{D}}\underbrace{S^{-1}y}_{\tilde{y}}=0} Dy=D~ DSy~ S−1y=0
其中 S 为一个对角阵,取 D 最大元素之逆即可。
3.2.4 测量值噪声
这里仿真的位姿以及地图点都是没有误差的,实际中路标点投影到相机帧中不可能是没有误差的,所以这里可以加一个测量噪声,来验证算法的有效性!
double x = Pc.x();
double y = Pc.y();
double z = Pc.z();double x_noise = x/z + noise(generator);
double y_noise = y/z + noise(generator);// camera_pose[i].uv = Eigen::Vector2d(x/z,y/z);
camera_pose[i].uv = Eigen::Vector2d(x_noise,y_noise);
3.2.5 特征点数量对结果的影响
固定噪声方差参数,将观测图像帧扩成多帧(如3, 4 ,5,6,7,8等),观察最小奇异值和第二小奇异值之间的比例变化,并绘制比例值的变化曲线。
这个就更简单了,把那个end_frame_id
改了。
4 SVD分解
A = D*S*V^T
,例子来源维基百科
验证了A^T*A的特征向量组成了A奇异值分解中的矩阵V
同理A*A^T的特征向量组成了A奇异值分解中的矩阵U
代码,其余理论可参考博客