这部分的代码着实很难,一方面论文中几乎没有提这个事情,另一方面这部分的参考资料也确实是比较少,网上能搜索到的基本都是讨论FEJ对零空间的保持问题,所以笔者在看这一部分的时候,着实很艰难,写这篇笔记的时候也是因为自己的觉得理论和公式推导至少能说服我自己了,因此才写出来,不对的地方也希望小伙伴们能够指出来,大家一起进步。
这次先把自己参考的文章列出来,自己平时是不太喜欢写参考的 ,也算是逼自己能在一篇笔记中把自己想讲的事情讲清楚,这次确实自己也不是那么清楚,所以列出来大家一起分析,一起进步;
这部分就是简介一下SLAM中的零空间,我们都知道对于纯视觉SLAM而言,其零空间有7维——3维旋转+3维位置+1维尺度;对于VINS系统而言,充分的加速度计激励可以为系统提供尺度信息,加速度计同时可以提供重力方向,使得pitch和roll可观,因此其不客观的维度为4维——3维位置+1维yaw方向。
通常举例子最常用的就是尺度,也就是当地图的规模扩大一个倍数的时候,整个SLAM的优化问题是不变的,也就是我们所说的不客观。进而这里明确一个点:SLAM中的零空间其实是整个优化问题的零空间,而不是说是优化中某个节点的零空间。就是说整个优化问题存在不客观的维度,这个不客观的维度会通过优化问题进而影响到某个节点的优化,导致那个节点出现问题,常见的比如说纯视觉SLAM在转弯的时候,尺度会突然变化。
根据前面的论文和源码分析,DSO在零空间维度的保持上,使用了FEJ去维持,但是在整个优化过程中,作者同样使用了零空间的正交化去避免零空间对最终的增量产生的影响,盗用参考[1]中的图示:
其中红色的箭头表示增量方程中求解出来的增量,黑色的虚线表示零空间在这个节点上可能产生的漂移,而蓝色的箭头表示最终我们正交化之后的增量结果,当正交化之后,相机最终的位置会到蓝色三角显示的位置。
下面就来进行一下这个部分详细的推导,不对的地方还请指出:
Notation:
- 下面的所有推导中, x x x表示优化节点的值, Δ x \Delta{x} Δx表示求解的增量, δ x \delta{x} δx表示整个优化问题零空间的漂移;
- 其实下面所说的李代数大家自行理解为向量空间就好;
首先一步,祭出SLAM中的优化函数:
E = ∑ i = 1 N ∣ ∣ z i − h ( x ) ∣ ∣ 2 (1) E=\sum_{i=1}^{N}||z_i-h(x)||^{2} \tag{1} E=i=1∑N∣∣zi−h(x)∣∣2(1)
其中i表示观测的index,这里只写了一个节点的优化目标函数,多个的类似,分析一个就已经可以说明问题了。
然后明确的一个点:零空间的带来的优化漂移主要产生在增量方程的计算上,也就是:
Δ x T H Δ x = 2 Δ x T b (2) \Delta{x}^{T}H\Delta{x}=2\Delta{x}^{T}b \tag{2} ΔxTHΔx=2ΔxTb(2)
因为增量方程跟优化问题息息相关,优化问题受到零空间不可观的影响,导致了最终求解出来的增量产生了漂移,也就是说,根据公式(2)求解出来的增量可能是 Δ x 1 \Delta{x_1} Δx1,也可能是 Δ x 2 \Delta{x_2} Δx2,不管是哪一个,都满足优化目标函数(注意不是都满足增量方程)。所以,笔者认为其实零空间对于优化问题的影响其实是影响到了我们求解的增量——李代数。
这里就比较有意思的一点是:零空间是如何影响李代数的?我的第一感觉当然是线性影响,即 Δ x ^ = Δ x + δ x \hat{\Delta{x}}= \Delta{x}+\delta{x} Δx^=Δx+δx,但是实际上并不是线性关系,很简单的例子,当优化问题的尺度发生了变化,在增量中根本找不到一个量来线性的增加(直观上理解应该是:当尺度变化的时候,表征位移的增量 Δ x p \Delta{x_p} Δxp应该同时扩大或者缩小,旋转的增量可能不变),所以引出下面的公式:
Δ x ^ = s e ( Δ x ^ ) = s e ( Δ x + δ x ) = s e ( Δ x ) + N δ x = Δ x + N δ x (3) \hat{\Delta{x}}=se(\hat{\Delta{x}})=se(\Delta{x}+\delta{x})=se(\Delta{x})+N\delta{x}=\Delta{x}+N\delta{x} \tag{3} Δx^=se(Δx^)=se(Δx+δx)=se(Δx)+Nδx=Δx+Nδx(3)
其中 N N N表示李代数对于零空间变量的求导,这个地方对应于DSO代码中FrameHessian::setStateZero函数,作者采用了最原始的数值推导的方法。
这部分主要基于公式(1)进行,因为零空间的漂移不会影响整个系统的目标函数,所以假设得到的增量为 Δ x \Delta{x} Δx,那么有如下公式:
E = ∣ ∣ z − h ( x + Δ x − N δ x ) ∣ ∣ 2 = ∣ ∣ z − h ( x + Δ x ) ∣ ∣ 2 (4) E=||z-h(x+\Delta{x}-N\delta{x})||^2=||z-h(x+\Delta{x})||^2 \tag{4} E=∣∣z−h(x+Δx−Nδx)∣∣2=∣∣z−h(x+Δx)∣∣2(4)
其中 Δ x \Delta{x} Δx是优化问题求解出来的,减去零空间的漂移意味着公式希望求解没有漂移的增量。上式的意思是说:当零空间引起增量值变化时,整个优化问题的输出是不变的!这符合我们的预期:还是那个例子,当尺度变化的时候,整个优化问题还是“按部就班”的在求解,只不过得到的尺度变化了而已,但是这个量没有观测,所以无法得到反馈进而去校正。
继续对公式(4)进行化简,这里分为前部分和后部分化简:
E = ∣ ∣ z − h ( x + Δ x − N δ x ) ∣ ∣ 2 where e = z − h ( x ) J = ∂ e ∂ x ∣ x = x = ( e + J ( Δ x − N δ x ) ) T ( e + J ( Δ x − N δ x ) ) = e T e + e T J ( Δ x − N δ x ) + ( Δ x − N δ x ) T J T e + ( Δ x − N δ x ) T J T J ( Δ x − N δ x ) = e T e + e T J Δ x − e T J N δ x + Δ x J T e − ( N δ x ) T J T e + Δ x T J T J Δ x − ( N δ x ) T J T J Δ x − Δ x J T J ( N δ x ) + ( N δ x ) T J T J ( N δ x ) = e T e + e T J Δ x + Δ x J T e + Δ x T J T J Δ x ⏟ ∣ ∣ z − h ( x + Δ x ) ∣ ∣ 2 − e T J N δ x − ( N δ x ) T J T e − ( N δ x ) T J T J Δ x − Δ x J T J ( N δ x ) + ( N δ x ) T J T J ( N δ x ) ⏟ δ x part = ∣ ∣ z − h ( x + Δ x ) ∣ ∣ 2 (5) \begin{aligned} E&=||z-h(x+\Delta{x}-N\delta{x})||^2 \text{ where } \quad e=z-h(x) \quad J=\frac{\partial{e}}{\partial{x}}|_{x=x} \\ &=(e+J(\Delta{x}-N\delta{x}))^T(e+J(\Delta{x}-N\delta{x})) \\ &=e^Te+e^TJ(\Delta{x}-N\delta{x})+(\Delta{x}-N\delta{x})^TJ^Te+(\Delta{x}-N\delta{x})^TJ^TJ(\Delta{x}-N\delta{x}) \\ &=e^Te+e^TJ\Delta{x}-e^TJN\delta{x}+\Delta{x}J^Te-(N\delta{x})^TJ^Te+\Delta{x}^TJ^TJ\Delta{x}-(N\delta{x})^TJ^TJ\Delta{x}-\Delta{x}J^TJ(N\delta{x})+(N\delta{x})^TJ^TJ(N\delta{x}) \\ &=\underbrace{e^Te+e^TJ\Delta{x}+\Delta{x}J^Te+\Delta{x}^TJ^TJ\Delta{x}}_{||z-h(x+\Delta{x})||^2} \underbrace{-e^TJN\delta{x}-(N\delta{x})^TJ^Te-(N\delta{x})^TJ^TJ\Delta{x}-\Delta{x}J^TJ(N\delta{x})+(N\delta{x})^TJ^TJ(N\delta{x})}_{\delta{x} \text{ part}} \\ &=||z-h(x+\Delta{x})||^2 \end{aligned} \tag{5} E=∣∣z−h(x+Δx−Nδx)∣∣2 where e=z−h(x)J=∂x∂e∣x=x=(e+J(Δx−Nδx))T(e+J(Δx−Nδx))=eTe+eTJ(Δx−Nδx)+(Δx−Nδx)TJTe+(Δx−Nδx)TJTJ(Δx−Nδx)=eTe+eTJΔx−eTJNδx+ΔxJTe−(Nδx)TJTe+ΔxTJTJΔx−(Nδx)TJTJΔx−ΔxJTJ(Nδx)+(Nδx)TJTJ(Nδx)=∣∣z−h(x+Δx)∣∣2 eTe+eTJΔx+ΔxJTe+ΔxTJTJΔxδx part −eTJNδx−(Nδx)TJTe−(Nδx)TJTJΔx−ΔxJTJ(Nδx)+(Nδx)TJTJ(Nδx)=∣∣z−h(x+Δx)∣∣2(5)
可以看到,公式(4)前半部分化简之后刚好就是公式(4)的后半部分,所以直接消去,那么这时候相当于在求解后面仅仅与零空间相关的部分,写作公式(6):
( N δ x ) T J T J ( N δ x ) = e T J N δ x + ( N δ x ) T J T e + ( N δ x ) T J T J Δ x + Δ x J T J ( N δ x ) (6) (N\delta{x})^TJ^TJ(N\delta{x})=e^TJN\delta{x}+(N\delta{x})^TJ^Te+(N\delta{x})^TJ^TJ\Delta{x}+\Delta{x}J^TJ(N\delta{x}) \tag{6} (Nδx)TJTJ(Nδx)=eTJNδx+(Nδx)TJTe+(Nδx)TJTJΔx+ΔxJTJ(Nδx)(6)
这时候一个很重要的部分(这部分笔者没有想到理论上的推导,只能是臆测了,有大神懂的话希望可以多加指点),公式(6)中的 ( N δ x ) T J T e (N\delta{x})^TJ^Te (Nδx)TJTe其实是等于0的:显然 J T e J^Te JTe是优化问题的梯度,而零空间之所以能够肆意变化而不被优化察觉,就是因为零空间产生的影响与优化的梯度方向是正交的,就像一个平面上的点有可能是三维空间中的一条线一样,这条线长度再怎么变化,对于这个面而言始终是一个点。
那么回到公式(6)中,把这部分置零之后并对 δ x \delta{x} δx进行求导,我们就得到下面的结论:
N δ x = Δ x (7) N\delta{x}=\Delta{x} \tag{7} Nδx=Δx(7)
参考[1]中对这个公式的说法是这两个值大概率是不会相等的,但是笔者个人认为他们不相等(数值和方向)是因为上面的原因:即零空间产生的影响( N δ x N\delta{x} Nδx)与优化问题的梯度是正交的,但是对于通过GN或者LM法得到的增量,他们之间是有一个夹角的,就像最开始的图中显示的一样。
说回来,既然他们不相等,那么在求解公式(7)的时候,就必然要使用最小二乘法了,这部分就不赘述了,DSO作者使用了SVD来求解N的伪逆,之后使用公式(8)得到去除零空间影响的增量:
Δ x ^ = Δ x − N N † Δ x (8) \hat{\Delta{x}}=\Delta{x}-NN^{\dagger}\Delta{x} \tag{8} Δx^=Δx−NN†Δx(8)
这部分代码比较少,笔者就直接贴出来了:
void EnergyFunctional::orthogonalize(VecX *b, MatXX *H) {
std::vector ns;
ns.insert(ns.end(), lastNullspaces_pose.begin(), lastNullspaces_pose.end());
ns.insert(ns.end(), lastNullspaces_scale.begin(), lastNullspaces_scale.end());
// make Nullspaces matrix
MatXX N(ns[0].rows(), ns.size());
for (unsigned int i = 0; i < ns.size(); i++)
N.col(i) = ns[i].normalized();
// compute Npi := N * (N' * N)^-1 = pseudo inverse of N.
Eigen::JacobiSVD svdNN(N, Eigen::ComputeThinU | Eigen::ComputeThinV);
VecX SNN = svdNN.singularValues();
double minSv = 1e10, maxSv = 0;
for (int i = 0; i < SNN.size(); i++) {
if (SNN[i] < minSv) minSv = SNN[i];
if (SNN[i] > maxSv) maxSv = SNN[i];
}
for (int i = 0; i < SNN.size(); i++) {
if (SNN[i] > setting_solverModeDelta * maxSv)
SNN[i] = 1.0 / SNN[i];
else SNN[i] = 0;
}
MatXX Npi = svdNN.matrixU() * SNN.asDiagonal() * svdNN.matrixV().transpose(); // [dim] x 9.
MatXX NNpiT = N * Npi.transpose(); // [dim] x [dim].
MatXX NNpiTS = 0.5 * (NNpiT + NNpiT.transpose()); // = N * (N' * N)^-1 * N'.
if (b != 0) *b -= NNpiTS * *b;
if (H != 0) *H -= NNpiTS * *H * NNpiTS;
}
DSO作者在求解完整个优化问题之后,对求得的增量进行了正交化:
if ((setting_solverMode & SOLVER_ORTHOGONALIZE_X) ||
(iteration >= 2 && (setting_solverMode & SOLVER_ORTHOGONALIZE_X_LATER))) {
VecX xOld = x;
orthogonalize(&x, 0);
}
那么这部分的推导与代码也对上了。
在上面的正交化代码中,可以看到其实作者最开始是想对增量方程进行正交化的,那么之所以笔者先写了对增量进行正交化是因为这部分也要用到那一部分的结论,:
Δ x T H ^ Δ x = Δ x T H Δ x − δ x T H ‾ δ x where H ‾ = J ‾ T J ‾ J ‾ = ∂ e ∂ δ x = ∂ e ∂ x ∂ x δ x = J N = Δ x T H Δ x − ( N † Δ x ) T N T J T J N ( N † Δ x ) = Δ x T H Δ x − Δ x T ( N N † ) T H ( N N † ) Δ x (9) \begin{aligned} \Delta{x}^T\hat{H}\Delta{x}&=\Delta{x}^TH\Delta{x}-\delta{x}^T\overline{H}\delta{x} \quad \text{ where } \quad \overline{H}=\overline{J}^T\overline{J} \quad \overline{J}=\frac{\partial{e}}{\partial{\delta{x}}}=\frac{\partial{e}}{\partial{x}}\frac{\partial{x}}{\delta{x}}=JN \\ &=\Delta{x}^TH\Delta{x}-(N^{\dagger}\Delta{x})^TN^TJ^TJN(N^{\dagger}\Delta{x}) \\ &=\Delta{x}^TH\Delta{x}-\Delta{x}^T(NN^{\dagger})^TH(NN^{\dagger})\Delta{x} \end{aligned} \tag{9} ΔxTH^Δx=ΔxTHΔx−δxTHδx where H=JTJJ=∂δx∂e=∂x∂eδx∂x=JN=ΔxTHΔx−(N†Δx)TNTJTJN(N†Δx)=ΔxTHΔx−ΔxT(NN†)TH(NN†)Δx(9)
所以可以得到 H ^ = H − ( N N † ) T H ( N N † ) \hat{H}=H-(NN^\dagger)^TH(NN^{\dagger}) H^=H−(NN†)TH(NN†),在代码中,作者对 N N † NN^{\dagger} NN†进行了处理,使得其转置与自身相等,因此在计算的时候并没有取转置。
增量方程中的 b b b部分也是同理,如下:
Δ x T b ^ = Δ x T b − δ x T b ‾ where b ‾ = J ‾ T e J ‾ = ∂ e ∂ δ x = ∂ e ∂ x ∂ x δ x = J N = Δ x T b − ( N † Δ x ) T N T J T e = Δ x T b − Δ x T ( N N † ) T b (10) \begin{aligned} \Delta{x}^T\hat{b}&=\Delta{x}^Tb-\delta{x}^T\overline{b}\quad \text{ where } \quad \overline{b}=\overline{J}^Te \quad \overline{J}=\frac{\partial{e}}{\partial{\delta{x}}}=\frac{\partial{e}}{\partial{x}}\frac{\partial{x}}{\delta{x}}=JN \\ &=\Delta{x}^{T}b-(N^{\dagger}\Delta{x})^TN^TJ^Te \\ &=\Delta{x}^{T}b-\Delta{x}^T(NN^{\dagger})^Tb \end{aligned} \tag{10} ΔxTb^=ΔxTb−δxTb where b=JTeJ=∂δx∂e=∂x∂eδx∂x=JN=ΔxTb−(N†Δx)TNTJTe=ΔxTb−ΔxT(NN†)Tb(10)
根据上面的推导易得:
H ^ = H − ( N N † ) T H ( N N † ) b ^ = b − ( N N † ) T b (11) \begin{aligned} \hat{H} &= H-(NN^{\dagger})^TH(NN^{\dagger}) \\ \hat{b} &= b-(NN^{\dagger})^Tb \end{aligned} \tag{11} H^b^=H−(NN†)TH(NN†)=b−(NN†)Tb(11)
这部分与代码中的也一致。