三角化Triangulation
相机已标定,同一个点在两张(或更多)图里的像素也匹配上了,那么这个点在三维空间的哪里?这一步叫三角化,它把特征匹配变成三维路标,是双目测距、SfM 和视觉 SLAM 的共同核心。难处在于:像素有噪声,两条视线几乎永远不相交,所以"交点"必须重新定义;而答案有多准,几乎完全取决于相机之间隔了多远。本节把三种估计方法(中点、线性 DLT、最小化重投影误差)放到同一组数据上比较,再用不确定性椭球看清深度误差为什么与 Z² 成正比,为什么直行的机器人看不准前方,以及 SLAM 为什么要等"足够的视差"再初始化路标。 The cameras are calibrated and the same point has been matched in two or more images: where is it in 3D? That step is triangulation, which turns feature matches into 3D landmarks and sits at the core of stereo ranging, SfM and visual SLAM. The difficulty: pixels are noisy, so the two viewing rays almost never meet and "intersection" has to be redefined, and how accurate the answer is depends almost entirely on how far apart the cameras are. This lesson runs three estimators (midpoint, linear DLT, reprojection-optimal) on the same data, then uses the uncertainty ellipsoid to show why depth error scales as Z², why a robot driving straight cannot see what is ahead, and why SLAM waits for "enough parallax" before it initializes a landmark.
1为什么机器人需要它Why robots need this
一张图只告诉你一个三维点在哪个方向上;深度在投影时丢掉了(10.1)。要把像素变回三维,至少要从两个不同的位置看同一个点:每个视角给出一条穿过光心和像素的射线,点就在射线的交会处。这就是三角化(triangulation),机器人里到处在用它:
- 双目 / 多目深度:每个匹配像素的深度。11.2 的 \(Z=fB/d\) 就是共面(校正过的)双目的特例。
- SfM 与视觉 SLAM:把关键帧之间的特征匹配变成三维路标,供 PnP(11.4)定位、供光束法平差(11.6)联合优化。
- 多相机系统:动捕的反光点、AprilTag 的角点、遥操作采集数据时的人手 / 人体关键点,都是先在每台相机里检测二维点,再三角化成三维。
但真实的射线从来不相交:像素有噪声,空间中两条直线通常是异面的。于是出现三个问题:没有交点时"交点"指什么?这个答案可信到什么程度?相机该怎么放才好?本节依次回答。
An image tells you only the direction of a 3D point; depth is lost in the projection (10.1). To turn a pixel back into 3D you must look at the same point from at least two different places: each view gives a ray through the camera center and the pixel, and the point sits where the rays meet. That is triangulation, and robots use it everywhere:
- Stereo / multi-camera depth: the depth of every matched pixel. Lesson 11.2's \(Z=fB/d\) is the special case of a rectified pair.
- SfM and visual SLAM: turning feature matches between keyframes into 3D landmarks, which PnP (11.4) localizes against and bundle adjustment (11.6) refines jointly.
- Multi-camera systems: mocap markers, AprilTag corners, and the hand / body keypoints collected during teleoperation are detected in 2D in every camera and then triangulated to 3D.
But real rays never intersect: pixels are noisy and two lines in space are generically skew. Three questions follow. With no intersection, what does "the intersection" mean? How far can the answer be trusted? Where should the cameras be placed? This lesson answers them in turn.
2直觉:两条几乎相交的射线Intuition: two rays that (almost) meet
从相机 \(i\) 的光心 \(\mathbf o_i\) 出发,穿过观测到的像素,得到射线 \(\mathbf o_i + s\,\mathbf d_i\)(\(s>0\),\(\mathbf d_i\) 为单位方向;像素 → 方向见 10.3)。没有噪声时,两条射线恰好交于真实点 \(\mathbf X\)。一旦像素偏了几个像素,两条射线就擦肩而过。我们取两条射线最短的连线(公垂线)的中点作为估计:
From the center \(\mathbf o_i\) of camera \(i\), pass through the observed pixel to get the ray \(\mathbf o_i + s\,\mathbf d_i\) (\(s>0\), \(\mathbf d_i\) a unit direction; pixel → direction is in 10.3). Without noise the two rays meet exactly at the true point \(\mathbf X\). Once the pixels are off by a few pixels, the rays pass each other. We take the midpoint of their shortest connecting segment (the common perpendicular) as the estimate:
下图左边是俯视图(可以拖动两台相机 \(C_1,C_2\) 和真实点 \(X\)),右边是同一场景的三维视图(可以旋转,也可以切到"放大"看公垂线)。相机始终朝向场景中心。噪声被刻意放大了(默认 6 像素,真实角点检测通常在 1 像素以内),否则公垂线短得看不见。两条射线夹的角 \(\alpha\) 叫三角化角(视差角)。
滑块的第二个功能是模拟一次误匹配:把相机 2 里的像素沿(或垂直于)对极线平移。猜一猜:哪一种误匹配更容易被发现?
The left panel is a top view (drag the two cameras \(C_1,C_2\) and the true point \(X\)); the right is the same scene in 3D (orbit it, or switch to "zoom" to see the common perpendicular). The cameras always aim at the scene center. The noise is deliberately exaggerated (6 px by default; real corner detectors are usually within 1 px), otherwise the perpendicular would be too short to see. The angle \(\alpha\) between the two rays is the triangulation angle (parallax angle).
The second slider simulates a wrong match: it slides the pixel in camera 2 along (or across) the epipolar line. Guess which kind of mismatch is easier to catch.
- 把像素噪声调到 0:公垂线缩成一个点(\(d_{\min}<1\) mm),射线恰好相交。这是它们唯一相交的时候。Set the pixel noise to 0: the perpendicular shrinks to a point (\(d_{\min}<1\) mm) and the rays intersect exactly. That is the only time they do.
- 把两台相机拖近,使 \(\alpha<10^\circ\),并让估计误差 \(\|\hat X-X\|\) 超过 0.5 m。同样的像素噪声,为什么误差变大了?Drag the cameras together until \(\alpha<10^\circ\) and the error \(\|\hat X-X\|\) exceeds 0.5 m. With the same pixel noise, why did the error grow?
- 选"沿对极线",把误匹配平移至少 40 像素,使一致性检验仍然通过、而点已偏离真值 0.4 m 以上。Choose "along the line" and shift the mismatch by at least 40 px so that the consistency test still passes while the point is more than 0.4 m off.
- 选"垂直于对极线",平移至少 40 像素,让检验失败。Choose "across the line" and shift by at least 40 px to make the test fail.
三角化 = 求一组本来不相交的射线的"交点"。所以每个算法都是对"最接近"的一种选择(什么度量?在哪个空间?),再加上一份说明:这个答案有多不确定。沿对极线的误匹配会给出一个完美自洽却错误的三维点,单靠两个视角的几何检验抓不到它,需要第三个视角或先验。
Triangulation means finding the "intersection" of rays that do not intersect. Every algorithm is therefore a choice of what "closest" means (which metric, in which space), plus an account of how uncertain the answer is. A mismatch along the epipolar line produces a perfectly consistent but wrong 3D point that two-view geometry cannot catch; you need a third view or a prior.
3动手玩:三种估计与更多视角Play: three estimators and more views
"最接近"有不止一种答案。本节在同一批带噪声的像素上运行三个真正的算法(它们都在这个页面里实时运行):
- 中点:使三维空间中到各条射线的距离平方和最小(\(N=2\) 时就是上一节的公垂线中点)。
- 线性 DLT:把每个视角写成两个线性方程,堆成 \(A\tilde{\mathbf X}=0\),取 SVD 最小奇异值的右奇异向量(1.12)。
- 最优(重投影):最小化 \(\sum_i\|\mathbf x_i-\pi(P_i\mathbf X)\|^2\),即像素空间中的平方误差,用 DLT 作初值做 Gauss–Newton(6.6)。
左图是几何(相机沿一条路径排开,第一个最近、最后一个最远),右图把真值附近放大:小点是 150 次独立噪声实验各自的结果,大点是第 1 次实验。右侧表格给出 150 次实验的统计。
"Closest" has more than one answer. Here three real algorithms (all running live in this page) are applied to the same noisy pixels:
- Midpoint: minimizes the sum of squared 3D distances to the rays (for \(N=2\) it is the midpoint of the previous section).
- Linear DLT: write each view as two linear equations, stack them into \(A\tilde{\mathbf X}=0\), take the right singular vector of the smallest singular value (1.12).
- Optimal (reprojection): minimize \(\sum_i\|\mathbf x_i-\pi(P_i\mathbf X)\|^2\), the squared error in pixel space, by Gauss–Newton (6.6) started from the DLT.
The left panel shows the geometry (cameras spread along a path, the first one nearest and the last farthest); the right panel magnifies the neighborhood of the truth: small dots are the results of 150 independent noise draws, the large dots are draw 1. The table summarizes the 150 draws.
- 用 \(N=2\) 并把最远相机拉远("远端距离"约 8 m 以上),使 DLT 的重投影 RMS 达到最优估计的 2 倍以上,但两者的三维误差只差几个百分点。With \(N=2\) and the far camera pulled out (far distance 8 m or more), make the DLT's reprojection RMS at least 2× the optimum's, while their 3D errors differ by only a few percent.
三个观察。(1)中点和 DLT 几乎重合:DLT 每个方程都等于"点到射线的距离"乘一个常数,它其实是中点法的代数版本(第 5 节推导)。(2)最优估计的三维误差只比它们小几个到几十个百分点,但重投影误差却小得多,因为前两种方法"不在乎"像素:近处相机的 1 像素对应的米数少,远处相机的多,而它们对两者一视同仁。(3)视角越多越好,误差大致按 \(1/\sqrt N\) 下降,并且最优估计逼近理论界。
Three observations. (1) The midpoint and the DLT nearly coincide: each DLT equation is a point-to-ray distance times a constant, so the DLT is an algebraic version of the midpoint (derived in Section 5). (2) The optimum's 3D error is only a few to a few tens of percent lower, but its reprojection error is much lower, because the first two do not care about pixels: one pixel at the near camera is fewer meters than one pixel at the far camera, and they treat both alike. (3) More views help, with the error falling roughly like \(1/\sqrt N\), and the optimum approaches the theoretical bound.
几何的:找离所有射线最近的点(中点)。代数的:找 \(A\tilde{\mathbf X}=0\) 的近似零空间向量(DLT,1.12)。统计的:假设像素噪声是高斯的,则最大似然估计就是最小化重投影误差(4.6、6.9),并且噪声传播给出答案的协方差(4.5)。三者在无噪声时给出同一个点;有噪声时它们的区别,正是"在哪个空间里度量误差"。
Geometric: find the point closest to all rays (midpoint). Algebraic: find the approximate null vector of \(A\tilde{\mathbf X}=0\) (DLT, 1.12). Statistical: if pixel noise is Gaussian, the maximum-likelihood estimate minimizes the reprojection error (4.6, 6.9), and noise propagation gives the covariance of the answer (4.5). All three return the same point without noise; with noise, the difference is exactly "in which space the error is measured".
4第二个视角:不确定性椭球Second view: the uncertainty ellipsoid
只看一次估计的好坏意义不大。更有用的是问:同样的相机、同样的噪声,重复很多次,估计点会散成什么形状?答案是一个细长的椭球:它沿着观测方向很长,垂直于观测方向很短。原因很直观:一个像素误差只让射线在横向偏几毫米(\(\approx Z\sigma/f\)),而两条射线夹角很小时,横向的小偏移会让交点沿射线方向滑出很远。
下图用蒙特卡洛实验(500 次,种子固定)画出这个云团,并叠加由重投影雅可比算出的理论协方差 \(\Sigma_X=\sigma^2(\sum_i J_i^\top J_i)^{-1}\)(推导见第 5 节,用到 4.5 与 6.9)。两台相机平行朝前(+y 方向),点在前方 \(Z\) 处。
Judging a single estimate is not very informative. It is more useful to ask: with the same cameras and the same noise, repeated many times, what shape do the estimates scatter into? The answer is a long thin ellipsoid, long along the viewing direction and short across it. The reason is intuitive: one pixel of error shifts a ray sideways by only a few millimeters (\(\approx Z\sigma/f\)), but when the two rays subtend a small angle, a small sideways shift slides their intersection far along the ray.
The figure draws that cloud by Monte Carlo (500 draws, fixed seed) and overlays the theoretical covariance \(\Sigma_X=\sigma^2(\sum_i J_i^\top J_i)^{-1}\) computed from the reprojection Jacobians (derived in Section 5, using 4.5 and 6.9). Both cameras look straight ahead (the +y direction), and the point is \(Z\) in front.
- 点"设为参考",然后把基线 \(B\) 加倍(其他不变):沿视线方向的误差 \(\sigma_1\) 变成原来的多少?Press "Set reference", then double the baseline \(B\) (everything else unchanged): what fraction of the old range error \(\sigma_1\) is left?
- 重新设参考,把深度 \(Z\) 加倍:\(\sigma_1\) 变成原来的多少倍?Set a new reference and double the depth \(Z\): by what factor does \(\sigma_1\) grow?
- 把运动方向换成"向前":同样的 \(B\),\(\sigma_1\) 比横向时大 5 倍以上。这就是直行的机器人看不准正前方的原因。Switch the motion direction to "forward": for the same \(B\), \(\sigma_1\) is more than 5× that of lateral motion. This is why a robot driving straight cannot see well straight ahead.
注意读数里两处对照:理论值与蒙特卡洛值在横向运动时吻合到百分之几(样本只有 500 个,本身就有约 3% 的抽样误差);而在"向前"或很小基线时,云团变成弯曲的、有长尾的"香蕉",线性化的协方差低估了真实的散布,而且估计还带有偏差。这提醒我们:协方差只是一阶近似。
Compare the two columns in the readout: for lateral motion the theoretical and Monte-Carlo values agree to a few percent (500 samples carry about 3% sampling error themselves); for "forward" motion or a tiny baseline the cloud turns into a curved, long-tailed "banana", the linearized covariance underestimates the true scatter, and the estimates are biased. The covariance is only a first-order approximation.
(1)深度精度:\(\Delta Z\approx Z^2\Delta d/(fB)\),基线加倍误差减半,深度加倍误差变四倍(11.2 的结果,这里是它的一般形式)。(2)椭球的长宽比约为 \(2/\alpha\)(\(\alpha\) 为三角化角,弧度):夹角 6.8° 时约 17:1。(3)向前运动:\(\alpha\to0\),椭球沿视线无限拉长;点正好在光轴上时深度完全不可观测。
(1) Depth precision: \(\Delta Z\approx Z^2\Delta d/(fB)\): double the baseline and the error halves, double the depth and it quadruples (the result of 11.2, here in its general form). (2) The aspect ratio of the ellipsoid is about \(2/\alpha\) (\(\alpha\) the triangulation angle in radians): about 17:1 at 6.8°. (3) Forward motion: \(\alpha\to0\) and the ellipsoid stretches without bound along the line of sight; a point exactly on the optical axis has no observable depth at all.
5再看数学The math
提示:把鼠标悬停在公式中带底色的符号上,FIG 1、3、4、5 中对应的元素会被高亮。Tip: hover the highlighted symbols in the formulas; the matching element in FIGs 1, 3, 4 and 5 lights up.
每个视角给出什么What each view gives you
相机 \(i\) 的外参 \({}^{c}T_{w}=(R_i,\mathbf t_i)\)(世界 → 相机,OpenCV 的 \([R|t]\)),投影矩阵 \(P_i=K_i[R_i\,|\,\mathbf t_i]\)(10.3)。点 \(\tilde{\mathbf X}=(\mathbf X,1)\) 的像素满足 \(\lambda_i\tilde{\mathbf x}_i=P_i\tilde{\mathbf X}\),其中 \(\lambda_i\) 恰是点在相机 \(i\) 里的深度 \(z_i\)。反过来,光心是 \(\mathbf o_i=-R_i^\top\mathbf t_i\)(相机位姿 \({}^{w}T_{c}\) 的平移),像素 \(\mathbf x_i\) 的射线方向是 \(\mathbf d_i\propto R_i^\top K_i^{-1}\tilde{\mathbf x}_i\)。
Camera \(i\) has extrinsics \({}^{c}T_{w}=(R_i,\mathbf t_i)\) (world → camera, OpenCV's \([R|t]\)) and projection matrix \(P_i=K_i[R_i\,|\,\mathbf t_i]\) (10.3). The pixel of a point \(\tilde{\mathbf X}=(\mathbf X,1)\) satisfies \(\lambda_i\tilde{\mathbf x}_i=P_i\tilde{\mathbf X}\), where \(\lambda_i\) is exactly the depth \(z_i\) of the point in camera \(i\). Conversely, the optical center is \(\mathbf o_i=-R_i^\top\mathbf t_i\) (the translation of the camera pose \({}^{w}T_{c}\)) and the ray direction of pixel \(\mathbf x_i\) is \(\mathbf d_i\propto R_i^\top K_i^{-1}\tilde{\mathbf x}_i\).
中点:二次函数的极小The midpoint: minimizing a quadratic
令 \(\mathbf w=\mathbf o_1-\mathbf o_2\),\(b=\mathbf d_1\!\cdot\!\mathbf d_2=\cos\hl{alpha}{\alpha}\),\(c_1=\mathbf d_1\!\cdot\!\mathbf w\),\(c_2=\mathbf d_2\!\cdot\!\mathbf w\)。对 \(\|\mathbf w+s\mathbf d_1-t\mathbf d_2\|^2\) 分别对 \(s,t\) 求导并令其为零,得到 \(s-bt=-c_1\),\(-bs+t=c_2\),因此
Let \(\mathbf w=\mathbf o_1-\mathbf o_2\), \(b=\mathbf d_1\!\cdot\!\mathbf d_2=\cos\hl{alpha}{\alpha}\), \(c_1=\mathbf d_1\!\cdot\!\mathbf w\), \(c_2=\mathbf d_2\!\cdot\!\mathbf w\). Setting the derivatives of \(\|\mathbf w+s\mathbf d_1-t\mathbf d_2\|^2\) in \(s\) and \(t\) to zero gives \(s-bt=-c_1\), \(-bs+t=c_2\), hence
分母 \(\sin^2\alpha\):射线平行(\(\alpha\to0\))时公式失效,这是第一次遇到"小基线灾难"。\(N\) 条射线的推广是 \(\hat{\mathbf X}=\big(\sum_i(I-\mathbf d_i\mathbf d_i^\top)\big)^{-1}\sum_i(I-\mathbf d_i\mathbf d_i^\top)\mathbf o_i\),其中 \(I-\mathbf d_i\mathbf d_i^\top\) 把向量投影到与射线垂直的平面;它是线性最小二乘(1.13)。注意它最小化的是米,不是像素。
The denominator is \(\sin^2\alpha\): the formula breaks down for parallel rays (\(\alpha\to0\)), our first encounter with the "small-baseline disaster". For \(N\) rays the generalization is \(\hat{\mathbf X}=\big(\sum_i(I-\mathbf d_i\mathbf d_i^\top)\big)^{-1}\sum_i(I-\mathbf d_i\mathbf d_i^\top)\mathbf o_i\), where \(I-\mathbf d_i\mathbf d_i^\top\) projects onto the plane perpendicular to the ray; it is linear least squares (1.13). Note that it minimizes meters, not pixels.
线性 DLT 与代数误差Linear DLT and the algebraic error
\(\lambda\tilde{\mathbf x}=P\tilde{\mathbf X}\) 里未知的 \(\lambda\) 可以用叉积消去:\(\tilde{\mathbf x}\times(P\tilde{\mathbf X})=\mathbf 0\)。记 \(\mathbf p_k^{\top}\) 为 \(P\) 的第 \(k\) 行,其中两个独立方程是 \((u\,\mathbf p_3^\top-\mathbf p_1^\top)\tilde{\mathbf X}=0\) 和 \((v\,\mathbf p_3^\top-\mathbf p_2^\top)\tilde{\mathbf X}=0\)(第三个是它们的线性组合)。\(N\) 个视角堆成 \(2N\times4\) 的矩阵:
The unknown \(\lambda\) in \(\lambda\tilde{\mathbf x}=P\tilde{\mathbf X}\) is eliminated with a cross product: \(\tilde{\mathbf x}\times(P\tilde{\mathbf X})=\mathbf 0\). With \(\mathbf p_k^{\top}\) the \(k\)-th row of \(P\), two independent equations are \((u\,\mathbf p_3^\top-\mathbf p_1^\top)\tilde{\mathbf X}=0\) and \((v\,\mathbf p_3^\top-\mathbf p_2^\top)\tilde{\mathbf X}=0\) (the third is a combination of them). \(N\) views stack into a \(2N\times4\) matrix:
\(\mathbf v_4\) 是 \(A\) 的 SVD(1.12)中最小奇异值对应的右奇异向量。看看它在最小化什么:每一行等于 \((\mathbf p_3^\top\tilde{\mathbf X})(u-\hat u)=z_i\,(u_i-\hat u_i)\),即深度乘像素残差。它与点到射线的距离也有关系:这两行恰是叉积 \(\mathbf X_c\times\mathbf m\) 的两个分量(再乘以焦距;\(\mathbf X_c\) 是点在相机坐标系里的坐标,\(\mathbf m=K^{-1}\tilde{\mathbf x}\) 是射线方向),而这个叉积的模等于 \(\|\mathbf m\|\) 乘以点到射线的距离,所以 DLT 是中点法的代数版本(FIG 2 里两者几乎重合)。因此 DLT 最小化的不是像素误差,它让远处的相机占更大的权重;并且条件 \(\|\tilde{\mathbf X}\|=1\) 依赖于坐标系的选择,把世界原点平移一下答案就会变(FIG 2 的最后一项)。实现时应先对像素做归一化(用 \(K^{-1}\) 变成归一化坐标)并把世界坐标平移缩放到原点附近。
\(\mathbf v_4\) is the right singular vector of the smallest singular value in the SVD (1.12) of \(A\). Look at what it minimizes: each row equals \((\mathbf p_3^\top\tilde{\mathbf X})(u-\hat u)=z_i\,(u_i-\hat u_i)\), the depth times the pixel residual. It is also tied to the point-to-ray distance: the two rows are two components of the cross product \(\mathbf X_c\times\mathbf m\), times the focal length (\(\mathbf X_c\) the point in camera coordinates, \(\mathbf m=K^{-1}\tilde{\mathbf x}\) the ray direction), and that cross product has norm \(\|\mathbf m\|\) times the point-to-ray distance, so the DLT is an algebraic version of the midpoint method (the two nearly coincide in FIG 2). So the DLT does not minimize pixel error; it weights far cameras more heavily; and the constraint \(\|\tilde{\mathbf X}\|=1\) depends on the choice of coordinates, so moving the world origin changes the answer (the last item of FIG 2). In practice, normalize the pixels first (to normalized coordinates with \(K^{-1}\)) and translate and scale the world coordinates to be near the origin.
最优:最小化重投影误差The optimum: minimizing the reprojection error
其中 \((x_c,y_c,z)=R_i\mathbf X+\mathbf t_i\) 是点在相机 \(i\) 坐标系里的坐标,\(\mathbf r_i=\mathbf x_i-\pi(\cdot)\) 是残差(3.3 的雅可比,6.6 的 Gauss–Newton)。像素噪声是独立同方差的高斯时,这就是最大似然估计(4.6)。问题只有 3 个未知数,用 DLT 作初值,3 到 5 次迭代就收敛;页面里的实现还加了步长折半以保证代价不上升。两个视角时还有闭式的全局最优解:Hartley–Sturm 把问题化为一个 6 次多项式求根;Lindstrom 给出更快的近似。
Here \((x_c,y_c,z)=R_i\mathbf X+\mathbf t_i\) is the point in camera \(i\)'s frame and \(\mathbf r_i=\mathbf x_i-\pi(\cdot)\) the residual (the Jacobian of 3.3, Gauss–Newton of 6.6). With independent, equal-variance Gaussian pixel noise this is the maximum-likelihood estimate (4.6). The problem has only 3 unknowns; started from the DLT it converges in 3 to 5 iterations (the page's implementation also halves the step to keep the cost from rising). For two views there is even a closed-form global optimum: Hartley–Sturm reduces it to finding the roots of a degree-6 polynomial, and Lindstrom gives a faster approximation.
协方差与椭球的形状Covariance and the shape of the ellipsoid
把像素噪声 \(\mathrm{cov}(\mathbf x_i)=\sigma^2I\) 通过 Gauss–Newton 的解传播(4.5):线性化后 \(\delta\mathbf X=(\sum J_i^\top J_i)^{-1}\sum J_i^\top\delta\mathbf x_i\),所以
Propagating the pixel noise \(\mathrm{cov}(\mathbf x_i)=\sigma^2I\) through the Gauss–Newton solution (4.5): after linearization \(\delta\mathbf X=(\sum J_i^\top J_i)^{-1}\sum J_i^\top\delta\mathbf x_i\), so
这是 6.9 里"高斯–牛顿的 Hessian 的逆",也是 Cramér–Rao 界,信息 \(\sum J_i^\top J_i\) 在视角之间相加。每个 \(J_i^\top J_i\approx(f/r_i)^2(I-\mathbf u_i\mathbf u_i^\top)\)(\(\mathbf u_i\) 是单位视线,\(r_i\) 是点到相机的距离):一个视角只在垂直于视线的两个方向上提供信息,沿视线的方向一点也没有。取两个等距(\(r_1=r_2=r\))的相机,三角化角为 \(\alpha\),三个主轴上的标准差为
This is the "inverse of the Gauss–Newton Hessian" of 6.9 and the Cramér–Rao bound; the information \(\sum J_i^\top J_i\) adds across views. Each \(J_i^\top J_i\approx(f/r_i)^2(I-\mathbf u_i\mathbf u_i^\top)\) (\(\mathbf u_i\) the unit line of sight, \(r_i\) the distance to the camera): a view informs only the two directions perpendicular to its line of sight and says nothing along it. For two equidistant cameras (\(r_1=r_2=r\)) with triangulation angle \(\alpha\), the standard deviations on the three principal axes are
范围方向(沿角平分线)的信息只有 \(2\sin^2(\alpha/2)\,(f/r)^2\),随 \(\alpha\) 平方消失。对校正过的双目,\(\sin(\alpha/2)\approx B/(2Z)\),代入得 \(\sigma_Z=\sqrt2\,\sigma Z^2/(fB)\),恰是 11.2 里 \(\Delta Z=Z^2\Delta d/(fB)\) 配上视差噪声 \(\sigma_d=\sqrt2\sigma\)(两张图各有独立噪声)。一般地,\(\alpha\approx B\sin\gamma/r\),其中 \(\gamma\) 是基线与视线的夹角:基线与视线平行(\(\gamma=0\),向前运动)时没有视差。
The information along the range direction (the bisector) is only \(2\sin^2(\alpha/2)\,(f/r)^2\), vanishing like \(\alpha^2\). For a rectified pair \(\sin(\alpha/2)\approx B/(2Z)\), which gives \(\sigma_Z=\sqrt2\,\sigma Z^2/(fB)\): exactly Lesson 11.2's \(\Delta Z=Z^2\Delta d/(fB)\) with disparity noise \(\sigma_d=\sqrt2\sigma\) (independent noise in the two images). In general \(\alpha\approx B\sin\gamma/r\) with \(\gamma\) the angle between the baseline and the line of sight: when the baseline is parallel to the line of sight (\(\gamma=0\), forward motion) there is no parallax.
逆深度Inverse depth
校正过的双目里 \(Z=fB/d\),所以 \(\rho=1/Z=d/(fB)\) 是视差的线性函数。视差噪声是高斯的,\(\rho\) 就是高斯的,且 \(\sigma_\rho=\sqrt2\sigma/(fB)\) 与深度无关;而 \(Z=1/\rho\) 的分布是歪的、有长尾,当 \(\rho\) 的误差棒跨过 0 时,\(Z\) 甚至会变成负的或无穷大(FIG 5)。一般的单目情形把路标写成"锚点相机光心 \(\mathbf c\) + 方向 \(\mathbf m(\theta,\phi)\) + 逆深度 \(\rho\)":\(\mathbf X=\mathbf c+\mathbf m/\rho\)。另一台相机 \(j\) 看到它的像素是 \(\pi\big(R_j(\rho(\mathbf c-\mathbf o_j)+\mathbf m)\big)\)(投影对整体缩放不变,乘以 \(\rho\)),\(\rho=0\) 时依然有定义:无穷远的点只约束相机的旋转(Civera–Davison–Montiel)。
For a rectified pair \(Z=fB/d\), so \(\rho=1/Z=d/(fB)\) is a linear function of disparity. Gaussian disparity noise makes \(\rho\) Gaussian, with \(\sigma_\rho=\sqrt2\sigma/(fB)\) independent of depth, whereas \(Z=1/\rho\) is skewed and long-tailed, and when the error bar of \(\rho\) crosses 0, \(Z\) can even become negative or infinite (FIG 5). The general monocular case writes a landmark as "anchor camera center \(\mathbf c\) + direction \(\mathbf m(\theta,\phi)\) + inverse depth \(\rho\)": \(\mathbf X=\mathbf c+\mathbf m/\rho\). Camera \(j\) sees it at \(\pi\big(R_j(\rho(\mathbf c-\mathbf o_j)+\mathbf m)\big)\) (projection is invariant to overall scale, so multiply by \(\rho\)), which remains well defined at \(\rho=0\): a point at infinity still constrains the camera's rotation (Civera–Davison–Montiel).
6机器人中的它:SLAM 里的视差门槛In robotics: the parallax threshold in SLAM
单目 SLAM 的机器人在路上走,每个新特征第一次被看到时只有一条射线,深度未知。随着机器人移动,同一特征在后续帧里被再次观测,才有可能三角化。这时就要回答一个很实际的问题:什么时候可以把它当作路标放进地图?太早(视差不足),深度几乎是猜的,坏路标会拖垮位姿估计;太晚,又浪费了观测。
FIG 4 把这件事做成实验。机器人沿 x 轴直线行驶,距离首帧 \(b\);路标在 8 m 远,相对行驶方向的方位角 \(\gamma\) 可选:"侧视"(90°)、"斜前方"(30°)、"正前方"(8°)。右图是路标沿视线方向的相对不确定性 \(\sigma_r/r\)(用首帧与当前帧的两视角协方差计算,对数坐标)与行驶距离的关系。常见的两种准入规则分别画成阈值:不确定性小于 \(\varepsilon\),或者三角化角大于 \(\alpha_{\min}\)。
A monocular-SLAM robot driving along sees a new feature for the first time through a single ray with unknown depth. Only after it has moved and re-observed the feature can it be triangulated, and a very practical question follows: when may it enter the map as a landmark? Too early (too little parallax) and the depth is nearly a guess, and bad landmarks drag down the pose estimate; too late and observations are wasted.
FIG 4 turns that into an experiment. The robot drives straight along the x axis, a distance \(b\) from the first frame; the landmark is 8 m away at a bearing \(\gamma\) from the direction of travel: "side-looking" (90°), "oblique" (30°) or "straight ahead" (8°). The right panel plots the landmark's relative uncertainty along the line of sight, \(\sigma_r/r\) (from the two-view covariance of the first and current frames, log scale) against the distance driven. The two usual admission rules appear as thresholds: uncertainty below \(\varepsilon\), or triangulation angle above \(\alpha_{\min}\).
- 先在"侧视"下找到满足不确定性规则(\(\sigma_r/r<\varepsilon\))的最小 \(b\);再选"正前方",把 \(b\) 拖到同样满足规则。需要多行驶几倍?Under "side-looking" find the smallest \(b\) that satisfies the uncertainty rule (\(\sigma_r/r<\varepsilon\)); then choose "straight ahead" and drag \(b\) until it satisfies the rule too. How many times farther must the robot drive?
读图的要点:\(\sigma_r/r\approx\sqrt2\,(\sigma/f)\,r/(b\sin\gamma)\),与 \(b\sin\gamma\)(垂直于视线的位移,即有效基线)成反比。侧视时 \(b\) 就是有效基线;正前方时 \(\sin\gamma\) 很小,必须多走约七倍(\(1/\sin8^\circ\))才能达到同样的精度。这正是视觉 SLAM 在"直行靠近一面墙"时尺度和深度容易漂的原因。
下面的 FIG 5 再看另一件事:就算已经过了门槛,深度的分布也不是高斯的。
How to read it: \(\sigma_r/r\approx\sqrt2\,(\sigma/f)\,r/(b\sin\gamma)\), inversely proportional to \(b\sin\gamma\), the displacement perpendicular to the line of sight (the effective baseline). Side-looking, \(b\) itself is the effective baseline; straight ahead \(\sin\gamma\) is tiny and the robot must drive about seven times farther (\(1/\sin8^\circ\)) to reach the same precision. This is why visual SLAM tends to drift in scale and depth when driving straight toward a wall.
FIG 5 below looks at something else: even past the threshold, the depth distribution is not Gaussian.
- 拖动深度或基线,让 \(Z\) 的偏度超过 2,而 \(\rho\) 的偏度仍在 \(\pm0.3\) 以内。Move the depth or the baseline until the skewness of \(Z\) exceeds 2 while that of \(\rho\) stays within \(\pm0.3\).
ORB-SLAM 系列:新地图点由共视关键帧之间的匹配三角化,并且拒绝视差角过小(量级约 1°)、重投影误差过大或深度为负的点;VINS-Mono 等视觉惯性系统在滑窗里用逆深度表示特征,并按平均视差挑选关键帧;DSO 等直接法同样使用逆深度。COLMAP(SfM)在增量重建时对每个轨迹做多视图三角化,有最小三角化角(默认约 1.5°)和最大重投影误差的过滤,并用 RANSAC 抵抗误匹配。双目 / RGB-D 与立体相机:校正后每个像素就是一个共面三角化,即 \(Z=fB/d\)。动捕系统(如 Vicon、OptiTrack)把标定好的多台相机看到的反光球三角化成三维点;多相机 AprilTag 与人体 / 手部关键点(遥操作数据采集)则是在每台相机里检测二维点,再加权三角化。(具体阈值与默认值随版本不同,使用前请查阅文档。)
The ORB-SLAM family: new map points are triangulated from matches between co-visible keyframes, rejecting points with too small a parallax angle (on the order of 1°), a large reprojection error or negative depth. VINS-Mono and similar visual-inertial systems represent features by inverse depth in a sliding window and pick keyframes by average parallax; direct methods such as DSO also use inverse depth. COLMAP (SfM) triangulates every track from multiple views during incremental reconstruction, filtering by a minimum triangulation angle (about 1.5° by default) and a maximum reprojection error, and uses RANSAC against mismatches. Stereo / RGB-D cameras: after rectification each pixel is a rectified triangulation, \(Z=fB/d\). Motion-capture systems (e.g. Vicon, OptiTrack) triangulate retro-reflective markers seen by several calibrated cameras; multi-camera AprilTag setups and body / hand keypoints for teleoperation data collection detect 2D points in every camera and then triangulate them with weights. (Exact thresholds and defaults vary between versions; check the documentation before relying on them.)
7常见误区Common pitfalls
代码不会报错:DLT 总会给你一个点,只不过它的深度误差可能是几十米。机器人原地转动(纯旋转)时基线为 0,根本没有深度信息,此时两帧之间的关系是单应(10.5),不是对极几何。工程上要检查三角化角(或基线 / 深度比),并把"深度未定"的点保留为只有方向的特征。FIG 3 的"向前"挡直接展示了这一点。
The code will not complain: the DLT always returns a point, just with a depth error that may be tens of meters. When the robot rotates in place (pure rotation) the baseline is zero and there is no depth information at all; the relation between the two frames is a homography (10.5), not epipolar geometry. In practice, check the triangulation angle (or the baseline-to-depth ratio) and keep "depth-unknown" points as direction-only features. The "forward" setting of FIG 3 shows exactly this.
"Z = 20 ± 5 m"听起来对称,真实分布却是歪的:偏近的一侧很窄,偏远的一侧拖着长尾,甚至包含无穷远(FIG 5)。把 \(Z\) 当作卡尔曼滤波的状态,线性化会在视差小的时候失效;用逆深度,噪声的分布就接近高斯,滤波和优化都稳得多。
"Z = 20 ± 5 m" sounds symmetric, but the true distribution is skewed: narrow on the near side, with a long tail on the far side that even includes infinity (FIG 5). If you put \(Z\) in a Kalman filter's state, the linearization fails at small parallax; with inverse depth the noise is close to Gaussian and both filtering and optimization behave far better.
好的三角化管线要做三个检验:(a)手性(cheirality):点在每台相机前面(\(z_i>0\),并且射线参数 \(s,t>0\)),否则射线是发散的;(b)重投影误差在每张图里都小于阈值;(c)视差角足够大。但 FIG 1 说明,沿对极线的误匹配通过 (a)(b),因为它恰好对应射线 1 上的另一个点;只有第三个视角、描述子距离或深度先验才能抓住它。因此 SfM 和 SLAM 才依赖多视图轨迹和 RANSAC,而不是两两检验。
A good triangulation pipeline runs three tests: (a) cheirality: the point is in front of every camera (\(z_i>0\), and the ray parameters \(s,t>0\)), otherwise the rays diverge; (b) the reprojection error is below a threshold in every image; (c) the parallax angle is large enough. But FIG 1 shows that a mismatch along the epipolar line passes (a) and (b), because it corresponds to another point on ray 1; only a third view, the descriptor distance, or a depth prior can catch it. That is why SfM and SLAM rely on multi-view tracks and RANSAC, not on pairwise tests.
DLT 最小化的是代数误差 \(\|A\tilde{\mathbf X}\|\)(深度 × 像素残差),既不是像素误差,也不具有射影不变性。三维误差也许只差几个百分点,但它的重投影残差明显更大,会误导后面的外点检验。DLT 只适合作为初值,之后做几步 Gauss–Newton。如果不归一化(像素除以 \(K\),世界坐标靠近原点),数值条件也会变差。
The DLT minimizes the algebraic error \(\|A\tilde{\mathbf X}\|\) (depth × pixel residual), which is neither the pixel error nor projectively invariant. Its 3D error may be only a few percent worse, but its reprojection residuals are noticeably larger and will mislead later outlier tests. Use the DLT only as an initial guess and follow it with a few Gauss–Newton steps. Without normalization (pixels through \(K^{-1}\), world coordinates near the origin) the numerical conditioning also suffers.
最常见的 bug:把相机位姿 \({}^{w}T_{c}\) 当作外参 \({}^{c}T_{w}\) 塞进 \(P=K[R|t]\)(二者互逆);用带畸变的像素直接三角化而忘了先去畸变(10.3);基线用厘米、焦距用像素、深度用米混着算;左右图的顺序反了。每一种都会得到"看起来合理"的点云,只是位置或尺度错了。最可靠的自检:把得到的三维点用原相机重投影,看误差是否在噪声量级。
The most common bugs: feeding the camera pose \({}^{w}T_{c}\) as the extrinsics \({}^{c}T_{w}\) into \(P=K[R|t]\) (they are inverses); triangulating distorted pixels without undistorting them first (10.3); mixing centimeters of baseline with pixels of focal length and meters of depth; swapping the left and right images. Each yields a "plausible" point cloud with the wrong position or scale. The most reliable self-check: reproject the 3D points with the original cameras and see whether the error is at the noise level.
8小练习Check yourself
9延伸阅读Further reading
- R. Hartley & A. Zisserman, Multiple View Geometry in Computer Vision, 2nd ed., Cambridge University Press, 2004, Ch. 12 — 结构计算:线性与几何(重投影)三角化,最优解,以及估计点的概率分布Structure computation: linear and geometric (reprojection) triangulation, the optimal solution, and the probability distribution of the estimated point
- R. I. Hartley & P. Sturm, Triangulation, Computer Vision and Image Understanding 68(2), 1997, 146–157 — 两视图的最优三角化:化为 6 次多项式Optimal two-view triangulation via a degree-6 polynomial
- P. Lindstrom, Triangulation Made Easy, CVPR 2010 — 快速、非迭代、接近最优的两视图三角化Fast, non-iterative, near-optimal two-view triangulation
- R. Szeliski, Computer Vision: Algorithms and Applications (1st ed., 2010, §7.1; in the 2nd ed., 2022, the chapter on structure from motion and SLAM) — 三角化与 SfM 的整体流程Triangulation within the whole SfM pipeline
- J. Civera, A. J. Davison & J. M. M. Montiel, Inverse Depth Parametrization for Monocular SLAM, IEEE Transactions on Robotics 24(5), 2008 — 逆深度:为什么它能让无穷远的路标进入滤波器Inverse depth: how it lets landmarks at infinity enter the filter
- J. L. Schönberger & J.-M. Frahm, Structure-from-Motion Revisited, CVPR 2016 — COLMAP 的增量重建,包含多视图三角化与过滤的工程细节COLMAP's incremental reconstruction, including the engineering details of multi-view triangulation and filtering