Last updated on November 26, 2023 pm
[TOC]
极线搜索
If we are using only the left camera, we can’t find the 3D point corresponding to the point $x$ in image because every point on the line $OX$ projects to the same point on the image plane. But consider the right image also. Now different points on the line $OX$ projects to different points ( $x^{\prime}$) in right plane. So with these two images, we can triangulate the correct 3D point.
The projection of the different points on $OX$ form a line on right plane (line $l^{\prime}$). We call it epiline corresponding to the point $x$. It means, to find the point $x$ on the right image, search along this epiline . It should be somewhere on this line (Think of it this way, to find the matching point in other image, you need not search the whole image, just search along the epiline. So it provides better performance and accuracy). This is called Epipolar Constraint . Similarly all points will have its corresponding epilines in the other image.
epiline : $l$, $l^{\prime}$
epipole : $e$, $e^{\prime}$
epipolar plane : $XOO^{\prime}$
求解空间点坐标 ORB-SLAM2 已知,两个视图下匹配点的 图像坐标 $\boldsymbol{p}_1$ 和 $\boldsymbol{p}_2$,两个相机的相对位姿 $\boldsymbol{T}$ ,相机内参矩阵 $\boldsymbol{K}$,求 对应的三维点坐标 $\boldsymbol{P}$,其齐次坐标为 $\tilde{\boldsymbol{P}}$。
相对位姿
两个视图的 投影矩阵 分别为
由于是齐次坐标的表示形式,使用叉乘消去齐次因子
把 $\boldsymbol{P}_1$ 和 $\boldsymbol{P}_2$ 按行展开 (上标代表行索引)代入,对于第一视图有
即
可见第三个式子可以由上两个式子线性表示,所以只需要取前连个式子即可
同样的,对于第二视图
组合起来
求解 $\boldsymbol{P}$ 相当于解一个 线性最小二乘问题 。
SVD分解 $\boldsymbol{A}$
方程的解为 $\boldsymbol{A}$ 的 最小奇异值 对应的 奇异矢量 ,即 齐次坐标
最终,$\boldsymbol{P}$(第一视图坐标系下三维坐标)为
示例代码1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 void Initializer::Triangulate ( const cv::KeyPoint &kp1, const cv::KeyPoint &kp2, const cv::Mat &P1, const cv::Mat &P2, cv::Mat &x3D) { cv::Mat A (4 ,4 ,CV_32F) ; A.row (0 ) = kp1.pt.x*P1.row (2 )-P1.row (0 ); A.row (1 ) = kp1.pt.y*P1.row (2 )-P1.row (1 ); A.row (2 ) = kp2.pt.x*P2.row (2 )-P2.row (0 ); A.row (3 ) = kp2.pt.y*P2.row (2 )-P2.row (1 ); cv::Mat u,w,vt; cv::SVD::compute (A,w,u,vt,cv::SVD::MODIFY_A| cv::SVD::FULL_UV); x3D = vt.row (3 ).t (); x3D = x3D.rowRange (0 ,3 )/x3D.at <float >(3 ); }
示例代码2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 const cv::KeyPoint &kp1 = mpCurrentKeyFrame->mvKeysUn[idx1];const cv::KeyPoint &kp2 = pKF2->mvKeysUn[idx2]; cv::Mat xn1 = (cv::Mat_ <float >(3 ,1 ) << (kp1.pt.x-cx1)*invfx1, (kp1.pt.y-cy1)*invfy1, 1.0 ); cv::Mat xn2 = (cv::Mat_ <float >(3 ,1 ) << (kp2.pt.x-cx2)*invfx2, (kp2.pt.y-cy2)*invfy2, 1.0 );cv::Mat A (4 ,4 ,CV_32F) ; A.row (0 ) = xn1.at <float >(0 )*Tcw1.row (2 )-Tcw1.row (0 ); A.row (1 ) = xn1.at <float >(1 )*Tcw1.row (2 )-Tcw1.row (1 ); A.row (2 ) = xn2.at <float >(0 )*Tcw2.row (2 )-Tcw2.row (0 ); A.row (3 ) = xn2.at <float >(1 )*Tcw2.row (2 )-Tcw2.row (1 ); cv::Mat w,u,vt; cv::SVD::compute (A,w,u,vt,cv::SVD::MODIFY_A| cv::SVD::FULL_UV); x3D = vt.row (3 ).t ();if (x3D.at <float >(3 )==0 ) continue ; x3D = x3D.rowRange (0 ,3 )/x3D.at <float >(3 );
Note:
示例2代码与PTAM原理一致,相关理论分析见下方PTAM部分
PTAM 已知,两个视图下匹配点的 归一化平面(z=1) (去畸变后) 齐次坐标 $\boldsymbol{p}_1$ 和 $\boldsymbol{p}_2$,两个相机的相对位姿 $\boldsymbol{T}$,求 对应的三维点坐标 $\boldsymbol{P}$(第一视图坐标系下三维坐标),其齐次坐标为 $\tilde{\boldsymbol{P}}$。
方程
矩阵形式
求解 $\boldsymbol{P}$ 相当于解一个 线性最小二乘问题 。
SVD分解 $\boldsymbol{A}$
方程的解为 $\boldsymbol{A}$ 的 最小奇异值 对应的 奇异矢量 ,即 齐次坐标
最终,$\boldsymbol{P}$(第一视图坐标系下三维坐标)为
ptam_cg中示例代码Triangulate :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 Vector<3> MapMaker::Triangulate ( SE3<> se3AfromB, const Vector<2 > &v2A, const Vector<2 > &v2B) { Matrix<3 ,4 > PDash; PDash.slice <0 ,0 ,3 ,3 >() = se3AfromB.get_rotation ().get_matrix (); PDash.slice <0 ,3 ,3 ,1 >() = se3AfromB.get_translation ().as_col (); Matrix<4 > A; A[0 ][0 ] = -1.0 ; A[0 ][1 ] = 0.0 ; A[0 ][2 ] = v2B[0 ]; A[0 ][3 ] = 0.0 ; A[1 ][0 ] = 0.0 ; A[1 ][1 ] = -1.0 ; A[1 ][2 ] = v2B[1 ]; A[1 ][3 ] = 0.0 ; A[2 ] = v2A[0 ] * PDash[2 ] - PDash[0 ]; A[3 ] = v2A[1 ] * PDash[2 ] - PDash[1 ]; SVD<4,4> svd (A) ; Vector<4 > v4Smallest = svd.get_VT ()[3 ]; if (v4Smallest[3 ] == 0.0 ) v4Smallest[3 ] = 0.00001 ; return project (v4Smallest); }
ptam_cg中示例代码TriangulateNew :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 Vector<3> MapMaker::TriangulateNew ( SE3<> se3AfromB, const Vector<2 > &v2A, const Vector<2 > &v2B) { Vector<3 > v3A = unproject (v2A); Vector<3 > v3B = unproject (v2B); Matrix<3 > m3A = TooN::Zeros; m3A[0 ][1 ] = -v3A[2 ]; m3A[0 ][2 ] = v3A[1 ]; m3A[1 ][2 ] = -v3A[0 ]; m3A[1 ][0 ] = -m3A[0 ][1 ]; m3A[2 ][0 ] = -m3A[0 ][2 ]; m3A[2 ][1 ] = -m3A[1 ][2 ]; Matrix<3 > m3B = TooN::Zeros; m3B[0 ][1 ] = -v3B[2 ]; m3B[0 ][2 ] = v3B[1 ]; m3B[1 ][2 ] = -v3B[0 ]; m3B[1 ][0 ] = -m3B[0 ][1 ]; m3B[2 ][0 ] = -m3B[0 ][2 ]; m3B[2 ][1 ] = -m3B[1 ][2 ]; Matrix<3 ,4 > m34AB; m34AB.slice <0 ,0 ,3 ,3 >() = se3AfromB.get_rotation ().get_matrix (); m34AB.slice <0 ,3 ,3 ,1 >() = se3AfromB.get_translation ().as_col (); SE3<> se3I; Matrix<3 ,4 > m34I; m34I.slice <0 ,0 ,3 ,3 >() = se3I.get_rotation ().get_matrix (); m34I.slice <0 ,3 ,3 ,1 >() = se3I.get_translation ().as_col (); Matrix<3 ,4 > PDashA = m3A * m34AB; Matrix<3 ,4 > PDashB = m3B * m34I; Matrix<6 ,4 > A; A.slice <0 ,0 ,3 ,4 >() = PDashA; A.slice <3 ,0 ,3 ,4 >() = PDashB; SVD<6,4> svd (A) ; Vector<4 > v4Smallest = svd.get_VT ()[3 ]; if (v4Smallest[3 ] == 0.0 ) v4Smallest[3 ] = 0.00001 ; return project (v4Smallest); }
求解空间点深度 已知,两个视图下匹配点的 归一化平面(z=1) (去畸变后) 齐次坐标 $\boldsymbol{p}_1$ 和 $\boldsymbol{p}_2$,两个相机的相对位姿 $\boldsymbol{T}$,求解空间点深度 $Z_1$ 和 $Z_2$
矩阵形式
或
SVO 上式即 $Ax=-t$ 的形式,解该方程可以用 正规方程
解得
svo_cg中示例代码 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 bool depthFromTriangulation ( const SE3& T_search_ref, const Vector3d& f_ref, const Vector3d& f_cur, double & depth) { Matrix<double ,3 ,2 > A; A << T_search_ref.rotation_matrix () * f_ref, f_cur; const Matrix2d AtA = A.transpose () * A; if (AtA.determinant () < 0.000001 ) return false ; const Vector2d depth2 = - AtA.inverse ()* A.transpose () * T_search_ref.translation (); depth = fabs (depth2[0 ]); return true ; }
REMODE 由于解向量是二维的,对上式采用 克莱默法则 求解:
REMODE中示例代码 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 __device__ __forceinline__ float3 triangulatenNonLin ( const float3 &bearing_vector_ref, const float3 &bearing_vector_curr, const SE3<float > &T_ref_curr) { const float3 t = T_ref_curr.getTranslation (); float3 f2 = T_ref_curr.rotate (bearing_vector_curr); const float2 b = make_float2 (dot (t, bearing_vector_ref), dot (t, f2)); float A[2 *2 ]; A[0 ] = dot (bearing_vector_ref, bearing_vector_ref); A[2 ] = dot (bearing_vector_ref, f2); A[1 ] = -A[2 ]; A[3 ] = dot (-f2, f2); const float2 lambdavec = make_float2 (A[3 ] * b.x - A[1 ] * b.y, -A[2 ] * b.x + A[0 ] * b.y) / (A[0 ] * A[3 ] - A[1 ] * A[2 ]); const float3 xm = lambdavec.x * bearing_vector_ref; const float3 xn = t + lambdavec.y * f2; return (xm + xn)/2.0f ; }
注意