伯克利的马毅教授在线上开展了为期 2 周的暑期课程,课程主讲 3D 视觉,课程涉及内容十分丰富,受限于版权原因可能不会公开,所有内容都可以在马老师的 «An invitation to 3D vision» 一书中进行深入了解。本篇博客重点解读 Two View Geometry 的部分内容,这也是马老师重点强调的内容。 其实这部分内容在大多数课程和教材中都有涉及,很多人可能也觉得很简单,有一定的套路可言,但是如标题所说,你真的理解Two View Geometry吗?笔者曾面试过 DJI 以及 Nreal 两家很棒的公司,面试时都问到了这一部分,当时还觉得自己答得不错,但是听过马老师的课程之后发现,其实我也并不是很了解 Two View Geometry. 接下来我会依据马老师的课件以及教材详细介绍 Two View Geometry, 在这之后的下一篇博客我会介绍一篇 CVPR 2021 的工作 Deep Two-View Structure-from-Motion Revisited,下面进入正式内容。 02 点击可查看大图 大致历程是:从双视图,到三视图四视图,再到统一的多视图。 内容我们只涉及双视图的,按照书中的标题来说就是:Reconstruction from Two Calibrated Views. 所要做的事情就是,给定两张同一场景不同视角下拍摄到的图像,恢复出相机的位姿以及场景的结构。 点击可查看大图 我们假设先前的预备工作已经准备充分,相机已经标定完成,correspondence 也已经匹配完成,那么不失一般性的可以用下面这个等式来进行表述: 这里 (1)式在有多次观测时, 我们把 正如推导过程所阐述的,(2)式只与相机的运动有关, right? 并且因为(2)式右边为 0,说明这是一个齐次等式,乘以任意的常数依然正确,而 对极几何还表达了一个重要的属性就是 3 点共面, 对极几何的表达十分简洁,并且有许多有趣的性质: 点击可查看大图 我们来稍加解读。按照上面的说法,对极几何其实表达的是三点共面, 类似的你可以了解 Epipolar line 的定义。具体的性质可以参考马老师的书,这里我简单描述一下, 接下来难度会提升一些。对极几何很美,如何解呢?这个方程是 homo- geneous 的,因此E的自由度为最多为 8,事实上我们知道实际自由度是 5(旋转矩阵的自由度为 3,不考虑尺度因素,平移向量的自由度为 2),但是暂且不考虑这个。因此如果给定8对 correspondence(这里我们不考虑共线共面以及其他的corner case),至少E可以解出。接下来会面对两个问题: 1.R,T怎么解呢? 2. 假设你知道怎么解出R, T,而实际应用中,我们的correspondence都是很 noise 的,这样得到的解也是带噪声的,那么如何把噪声去掉,得到一个干干净净的Essential Matrix呢? 带着这些问题继续往下走。 我们通过 8 个点对,解出的矩阵记作F,首先有一点你要了解,不是任何 3 × 3 的矩阵都能分解为 在后文我们提到的E 不特殊说明都指的是 normalized essential matrix. 我们希望能在 essential space 中找到一个距离F最“近”的解,然后将F投影到这个解上,如下图所示: 定理一描述了一个矩阵为 Essential Matrix 的充要条件。 定理二描述了如何从 Essential Matrix 恢复到旋转矩阵以及平移方向向量。这里需要注意的是,normalized essential matrix 可以消除尺度的干扰,但是不能消除符号的干扰,代数角度而言,E 和 −E 都满足Epipolar Constraint,因此实际我们能得到四组解。 ![]() 定理三给出了投影的方法,我们选择F-norm作为投影距离的度量指标。这里需要注意的是,F的SVD分解得到的U, V只满足正交性,不能满足行列式为+1的条件,当得到的UV行列式为−1时,我们会对其取负,在后面我们会用代码具体解释。 以上三个定理在马老师的书里都有详细证明,出于易读性的考虑后续 会单独的整理到我的知乎上分享。 在有了这三个定理之后,整个算法也就明朗了,流程如下: 以上就是著名的八点法,你可以在许多资料上看到这个过程,本文的主要目的是梳理八点法的一些思路。我们引用一段 colmap 中的源码来解读上述过程: void DecomposeEssentialMatrix ( const Eigen : : Matrix3d& E, Eigen : : Matrix3d∗ R1, Eigen : : Matrix3d∗ R2, Eigen : : Vector3d∗ t ) { // 根据对极约束得到的带噪声的E做SVD分解 Eigen::JacobiSVD<Eigen::Matrix3d> svd( E, Eigen : : ComputeFullU | Eigen : : ComputeFullV ) ; Eigen::Matrix3d U = svd.matrixU(); Eigen::Matrix3d V = svd.matrixV().transpose(); // 保证行列式符号为正 if (U.determinant() < 0) { U ∗= −1; } if (V.determinant() < 0) { V ∗= −1; } Eigen : : Matrix3d W; W<< 0, 1, 0, −1, 0, 0, 0, 0, 1; ∗R1 = U ∗ W ∗ V; ∗R2=U∗W.transpose() ∗V; ∗t = U. col (2). normalized (); }
void PoseFromEssentialMatrix ( const Eigen : : Matrix3d& E, const std : : vector<Eigen : : Vector2d>& points1 , const std : : vector<Eigen : : Vector2d>& points2 , Eigen : : Matrix3d∗ R, Eigen : : Vector3d∗ t , std : : vector<Eigen : : Vector3d>∗ points3D ) { 7 CHECK_EQ(points1 . size () , points2 . size ());
Eigen::Matrix3d R1; Eigen::Matrix3d R2; DecomposeEssentialMatrix(E, &R1, &R2, t );
// Generate all possible projection matrix combinations. const std : : array<Eigen : : Matrix3d , 4> R_cmbs{{R1, R2, R1, R2}}; const std : : array<Eigen : : Vector3d , 4> t_cmbs{{∗t , ∗t , −∗t , −∗t }}; ... } 这里为什么 t 取 U 的最后一行可以留给读者作为一个思考题,提示是 八点法十分简洁 (当然证明过程比较复杂),但是在实际使用过程中,还是会遇到许多问题的,我们在以下简要列举: 1.Number of points. 由于 Normalized Essential Matrix 的自由度为 5,在比较 general 的情况下,最少选取的 correspondence 点对为 5(Kruppa在 1913 年的时候给出了五点法,类似八点法会产生 4 个满足对极约束的解,五点法会产生 10 个解),因此选取多少点是一个需要实际使用中考虑的问题。 2.Number of solutions and positive depth constraint. 虽然八点法给出了四对解,但是实际上只有一个正确解,那么其他三个解怎么排除呢?首先从代数层面,不要忘了最原始的表达式 点击可查看大图 3.Structure requirement: general position.当观测到的空间点满足某些导致退化的条件时 (called critical surfaces),使用八点法会遇到解不唯一的情况。一个典型的例子就是观测点共面的情况,这种时候我们需要使用homography 来解决。 4.Motion requirement: sufficient parallax.也就是说,平移量不能为0(为0时也要使用 homography)。需要十分小心的是,在没有平移移动且匹配十分 noise 的时候,八点法依旧会得到一个很奇怪的平移部分的解,而这个解是毫无意义的。 |
|