<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>SSL的个人Blog - DSO</title><link href="https://sunshanlu.github.io/sunshanlu/" rel="alternate"></link><link href="https://sunshanlu.github.io/sunshanlu/feeds/dso.atom.xml" rel="self"></link><id>https://sunshanlu.github.io/sunshanlu/</id><updated>2025-02-20T00:00:00+08:00</updated><subtitle>记录开发随笔</subtitle><entry><title>DSO后端建图（滑窗优化）</title><link href="https://sunshanlu.github.io/sunshanlu/SWF-in-DSO" rel="alternate"></link><published>2025-02-20T00:00:00+08:00</published><updated>2025-02-20T00:00:00+08:00</updated><author><name>孙善路-github</name></author><id>tag:sunshanlu.github.io,2025-02-20:/sunshanlu/SWF-in-DSO</id><summary type="html">
&lt;p&gt;当前端跟踪完成后，前端线程会判断是否需要添加关键帧操作，并向后端发送跟踪完成的当前帧，如果不需要添加关键帧 …&lt;/p&gt;</summary><content type="html">
&lt;p&gt;当前端跟踪完成后，前端线程会判断是否需要添加关键帧操作，并向后端发送跟踪完成的当前帧，如果不需要添加关键帧，那么可以根据当前帧的位姿信息对滑窗中的&lt;strong&gt;未成熟&lt;/strong&gt;点进行&lt;strong&gt;优化更新&lt;/strong&gt;；当然如果需要添加关键帧的话也需要根据位姿信息对滑窗中的&lt;strong&gt;未成熟&lt;/strong&gt;点进行优化更新，除此之外还需要根据当前滑窗中的状态&lt;strong&gt;激活相当一部分的未成熟点&lt;code&gt;ImmaturePoint&lt;/code&gt;&lt;/strong&gt;，然后根据滑窗中的成熟点&lt;code&gt;PointHessian&lt;/code&gt;、关键帧、先验信息以及由系统之前&lt;strong&gt;边缘化&lt;/strong&gt;&lt;code&gt;marg&lt;/code&gt;的&lt;code&gt;fill-in&lt;/code&gt;信息构建最小二乘的&lt;strong&gt;图优化&lt;/strong&gt;问题，当然这里会涉及到一些优化理论知识，比如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;DSO&lt;/code&gt;使用&lt;strong&gt;First Estimate Jacobian &lt;code&gt;FEJ&lt;/code&gt;&lt;/strong&gt;，解决由于不同优化初值导致的系统&lt;strong&gt;不一致性&lt;/strong&gt;问题。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DSO&lt;/code&gt;使用 &lt;strong&gt;&lt;code&gt;SVD&lt;/code&gt;分解&lt;/strong&gt; 求正规方程或者使用向&lt;strong&gt;零空间投影&lt;/strong&gt;的方式求解最小二乘问题，来防止解在零空间中漂移的问题。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DSO&lt;/code&gt;使用矩阵伴随的性质，构建&lt;span class="math"&gt;\(\frac{\partial \delta \xi_{ji}}{\partial \delta \xi_j}\)&lt;/span&gt;和&lt;span class="math"&gt;\(\frac{\partial \delta \xi_{ji}}{\partial \delta \xi_i}\)&lt;/span&gt;以此来解决相对增量到绝对增量的转换问题（这是由于残差定义仅包含相对位姿导致的问题）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;最后，&lt;code&gt;DSO&lt;/code&gt;定义了一些高效的帧管理和点管理策略，使用某些条件筛选需要被边缘化掉的帧和点，以此构建滑动窗口的边缘化信息&lt;code&gt;HM&lt;/code&gt;和&lt;code&gt;bM&lt;/code&gt;。&lt;/p&gt;
&lt;h2 id="1"&gt;1. 滑窗优化里面到底在做什么？&lt;/h2&gt;
&lt;p&gt;首先，当前端线程完成对&lt;code&gt;Frame&lt;/code&gt;的位姿估计后，需要后端线程根据估计的&lt;code&gt;Frame&lt;/code&gt;位姿进行滑动窗口中未成熟点&lt;code&gt;ImmaturePoint&lt;/code&gt;的逆深度更新，具体更新逆深度的手段，我会在第2小节中进行说明。如果前端线程判断滑动窗口中不需要添加关键帧，那么后端线程结束；否则，后端线程进入滑动窗口优化阶段。为了保证滑窗中&lt;strong&gt;激活点&lt;code&gt;PointHessian&lt;/code&gt;&lt;/strong&gt; 的密度问题，需要对滑动窗口中作为备选力量的&lt;strong&gt;未成熟点&lt;code&gt;ImmaturePoint&lt;/code&gt;&lt;/strong&gt; 进行筛选激活，而筛选激活的策略，我会在第3小节中进行说明。当滑动窗口中激活点达到数量要求后，构建残差（根据&lt;a href="https://sunshanlu.github.io/dso_ssl/Optimization-Model-DSO"&gt;DSO中的优化模型&lt;/a&gt;文章描述的残差公式构建），然后在考虑&lt;strong&gt;残差作用&lt;/strong&gt;、&lt;strong&gt;先验作用&lt;/strong&gt;、&lt;strong&gt;&lt;code&gt;marg&lt;/code&gt;作用&lt;/strong&gt;后构建并迭代求解优化的正规方程&lt;code&gt;H&lt;/code&gt;和&lt;code&gt;b&lt;/code&gt;，滑窗正规方程的构建过程以及构建求解过程中可能出现的问题，我会在第4小节中进行详细描述；最后为了保证求解的实时性，需要严格控制滑动窗口中的规模，&lt;code&gt;DSO&lt;/code&gt;使用边缘化的策略来实现，其中会涉及边缘化判断和实施边缘化操作这两个过程来构建边缘化的&lt;span class="math"&gt;\(H_M\)&lt;/span&gt;和&lt;span class="math"&gt;\(b_M\)&lt;/span&gt;，以供后续滑窗优化使用，我会在第5小节中对边缘化相关内容进行详细描述。整个后端的运行逻辑如下图所示：&lt;/p&gt;
&lt;div align="center"&gt;
&lt;img src="https://sunshanlu.github.io/sunshanlu/images/swt.png" width="70%"/&gt;
&lt;/div&gt;
&lt;h2 id="2"&gt;2 普通帧和关键帧都要做的未成熟点优化&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;DSO&lt;/code&gt;中的未成熟点优化过程分两个步骤进行，首先需要通过未成熟点的逆深度范围确定的&lt;strong&gt;极线线段上搜索&lt;/strong&gt;一个能量最小的像素点；接下来根据线搜过程以及残差公式&lt;strong&gt;构建优化模型&lt;/strong&gt;，因为残差和待优化量都是标量，因此优化过程可以非常快，整个未成熟点优化过程如下图所示：&lt;/p&gt;
&lt;div align="center"&gt;
&lt;img src="https://sunshanlu.github.io/sunshanlu/images/polar-search.png" width="80%"/&gt;
&lt;/div&gt;
&lt;h3 id="21"&gt;2.1 极线搜索&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;DSO&lt;/code&gt;中的极线搜索与&lt;strong&gt;视觉SLAM十四讲p309&lt;/strong&gt;中讲解极线搜索内容基本一致，只不过是块匹配策略不一致而已，在SLAM十四讲中列举了&lt;code&gt;SAD&lt;/code&gt;、&lt;code&gt;SSD&lt;/code&gt;和&lt;code&gt;NCC&lt;/code&gt;块匹配评价方法，而&lt;code&gt;DSO&lt;/code&gt;使用的是其&lt;code&gt;pattern&lt;/code&gt;根据残差构建能量值来作为极线搜索的块匹配评价方法，&lt;code&gt;DSO&lt;/code&gt;使用构建残差能量值的方式也会为&lt;strong&gt;基于极线搜索优化&lt;/strong&gt;过程提供方便。&lt;/p&gt;
&lt;div class="admonition important"&gt;
&lt;p class="admonition-title"&gt;&lt;code&gt;DSO&lt;/code&gt;极线搜索中的小问题&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;pattern&lt;/code&gt; 在线搜过程如何保证？ 在SLAM十四讲中，提到了块匹配技术，也就是说以投影点和被投影点为中心，与周围的某些固定点之间构建两个图像块，以此来进行极线搜索过程中的匹配区域。但是&lt;code&gt;DSO&lt;/code&gt;有固定的&lt;code&gt;pattern&lt;/code&gt;，但是根据投影公式分析可知&lt;span class="math"&gt;\(p_j=KRK^{-1}p_i + Ktd_{pi}\)&lt;/span&gt;，在搜索过程中，&lt;span class="math"&gt;\(d_{pi}\)&lt;/span&gt;是未知的，所以投影点&lt;span class="math"&gt;\(p_i\)&lt;/span&gt;和被投影点&lt;span class="math"&gt;\(p_j\)&lt;/span&gt;没有明确的&lt;code&gt;pattern&lt;/code&gt;对应。&lt;code&gt;DSO&lt;/code&gt;使用了&lt;span class="math"&gt;\(KRK^{-1}\)&lt;/span&gt;矩阵的左上&lt;span class="math"&gt;\(2\times2\)&lt;/span&gt;矩阵和&lt;code&gt;pattern&lt;/code&gt;相对量的乘积来近似代表&lt;code&gt;pattern&lt;/code&gt;与&lt;span class="math"&gt;\(p_j\)&lt;/span&gt;点的相对位置。&lt;/li&gt;
&lt;li&gt;线搜起止点如何确定？&lt;code&gt;DSO&lt;/code&gt;使用了未成熟点的&lt;code&gt;idepth_min&lt;/code&gt; 和 &lt;code&gt;idepth_max&lt;/code&gt; 对应的点作为搜索的起止点，当然&lt;code&gt;idepth_min&lt;/code&gt;被初始化为0，&lt;code&gt;idepth_max&lt;/code&gt;被初始化为&lt;code&gt;NAN&lt;/code&gt;，如果&lt;code&gt;idepth_max&lt;/code&gt;为&lt;code&gt;NAN&lt;/code&gt;,则通过图像的(&lt;code&gt;width&lt;/code&gt; + &lt;code&gt;height&lt;/code&gt;) * &lt;code&gt;super_param&lt;/code&gt;来确定一个&lt;strong&gt;搜索长度&lt;/strong&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;p&gt;除此之外，&lt;code&gt;DSO&lt;/code&gt;还讨论了极线搜索和梯度的关系，&lt;strong&gt;我在网络上找到了相关的解释&lt;/strong&gt;，根据下面的图像进行分析，图中红色实线代表的是根据当前帧位姿得到的极线，而红色虚线代表真实位姿得到的极线，蓝色的线代表的是像素值等值线（垂直于像素梯度），这里假设虚线上浅蓝色点为真实的匹配点，而实线上浅蓝色点是在误差极线上搜索到的匹配点。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;左图是极线和梯度平行时，可能会导致的像素误差。可以发现，总体的像素误差主要由极线误差导致。&lt;/li&gt;
&lt;li&gt;右图是极线和梯度之间存在角度差时，可能会导致的像素误差，可以发现，整体像素误差除了有一部分来自极线误差外，还有一部分来自梯度的误差。&lt;/li&gt;
&lt;/ul&gt;
&lt;div align="center"&gt;
&lt;img src="https://sunshanlu.github.io/sunshanlu/images/pixel-error.png" width="80%"/&gt;
&lt;/div&gt;
&lt;p&gt;&lt;code&gt;DSO&lt;/code&gt;源码中使用了一个经验公式，来计算像素误差的大小，经验公式如下：&lt;/p&gt;
&lt;div class="math"&gt;$$
e_p = 0.2 + 0.2 \times \frac{a + b}{a}\\
a = (lx \times dx + ly \times dy)^2\\
b = (lx \times dx - ly \times dy)^2
$$&lt;/div&gt;
&lt;p&gt;其中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;span class="math"&gt;\(lx\)&lt;/span&gt;为极线在&lt;span class="math"&gt;\(x\)&lt;/span&gt;方向上的分量&lt;/li&gt;
&lt;li&gt;&lt;span class="math"&gt;\(ly\)&lt;/span&gt;为极线在&lt;span class="math"&gt;\(y\)&lt;/span&gt;方向上的分量&lt;/li&gt;
&lt;li&gt;&lt;span class="math"&gt;\(dx\)&lt;/span&gt;为梯度在&lt;span class="math"&gt;\(x\)&lt;/span&gt;方向上的向量&lt;/li&gt;
&lt;li&gt;&lt;span class="math"&gt;\(dy\)&lt;/span&gt;为梯度在&lt;span class="math"&gt;\(y\)&lt;/span&gt;方向上的向量&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;经过推导不难发现，&lt;span class="math"&gt;\(e_p=0.2 + \frac{0.2}{\cos^2&amp;lt;l,d&amp;gt;}\)&lt;/span&gt;，其中&lt;span class="math"&gt;\(cos&amp;lt;l,d&amp;gt;\)&lt;/span&gt;为极线与梯度之间的夹角的余弦值，也就是说极线和梯度之间的夹角越大就会导致像素误差越大，并且最小的像素误差为&lt;code&gt;0.4px&lt;/code&gt;。这与我在上面的定性分析结果一致。&lt;/p&gt;
&lt;div class="admonition note"&gt;
&lt;p class="admonition-title"&gt;极线搜索与梯度关系，定性分析存在的问题&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;位姿误差是如何影响极线的呢？在图像上看来，误差极线和真实极线之间是平行的。通过视觉SLAM十四讲中的推导可以知道，极线搜索的线段参数可以使用&lt;span class="math"&gt;\(Fp_1\)&lt;/span&gt;来表示，其中F为基础矩阵，&lt;span class="math"&gt;\(p_1\)&lt;/span&gt;为host帧上的像素点。而&lt;span class="math"&gt;\(F\)&lt;/span&gt;矩阵可以使用&lt;span class="math"&gt;\(F=K^{-T}t^{\wedge}RK^{-1}\)&lt;/span&gt;表示，我从这个公式上很难判断出位姿误差会导致平行的极线误差。&lt;/li&gt;
&lt;li&gt;以真实匹配点为起点的图像等值线与误差极线的交点是否能保证与误差极线上搜索的点一致？我认为在极线误差较小的情况下，这个是可以成立的，因为真实匹配点能近似代表误差最小点处，而图像等值线在较小的像素范围内可以看做近似成立，因此误差极线上的点也能近似代表误差最小点处。&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;h3 id="22"&gt;2.2 基于极线搜索优化&lt;/h3&gt;
&lt;p&gt;线搜结束后，可以得到一个粗略的最优值（因为线搜会有步长，以至于找不到全局最优值），因此需要优化过程来逼近极线上的最优值对应的&lt;span class="math"&gt;\(p_j\)&lt;/span&gt;点。线搜过程和残差可以通过下面的公式进行表示：&lt;/p&gt;
&lt;div class="math"&gt;$$
r_k=I_j[p_j]- a_{ji} I_i[p_i] - b_{ji}\\
p_j=l*\delta + p_{j0}
$$&lt;/div&gt;
&lt;p&gt;其中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;span class="math"&gt;\(p_j\)&lt;/span&gt;为极线搜索的目标点；&lt;/li&gt;
&lt;li&gt;&lt;span class="math"&gt;\(l\)&lt;/span&gt;为极线方向；&lt;/li&gt;
&lt;li&gt;&lt;span class="math"&gt;\(p_{j0}\)&lt;/span&gt;为极线搜索结束后确定的&lt;span class="math"&gt;\(p_j\)&lt;/span&gt;点；&lt;/li&gt;
&lt;li&gt;&lt;span class="math"&gt;\(\delta\)&lt;/span&gt;为极线上最优点的&lt;span class="math"&gt;\(p_j\)&lt;/span&gt;到当前&lt;span class="math"&gt;\(p_{j0}\)&lt;/span&gt;之间的步长（待优化量）；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;那么不难推导，残差相对步长&lt;span class="math"&gt;\(\delta\)&lt;/span&gt;的雅可比矩阵如下：&lt;/p&gt;
&lt;div class="math"&gt;$$
\frac{\partial r_k}{\partial \delta}=g^T l
$$&lt;/div&gt;
&lt;p&gt;其中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;span class="math"&gt;\(g\)&lt;/span&gt;为&lt;span class="math"&gt;\(p_j\)&lt;/span&gt;点的梯度值；&lt;/li&gt;
&lt;li&gt;&lt;span class="math"&gt;\(l\)&lt;/span&gt;为极线方向；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;使用GN或者LM方法优化完成后，考虑最优化后的线搜点&lt;span class="math"&gt;\(p_j=[u,v]^T\)&lt;/span&gt;，由具体的投影公式推导可以得到逆深度点：&lt;/p&gt;
&lt;div class="math"&gt;$$
p_r = KR_{ji}K^{-1}[p_i^T, 1]^T\\
{d_{pi}}_u=\frac{p_r[2]\times u - p_r[0]}{Kt_{ji}[0]-Kt_{ji}[2]\times u}\\
{d_{pi}}_v=\frac{p_r[2]\times v - p_r[1]}{Kt_{ji}[1]-Kt_{ji}[2]\times v}
$$&lt;/div&gt;
&lt;p&gt;除此之外，&lt;code&gt;DSO&lt;/code&gt;还需要考虑由于极线误差和像素梯度造成的像素误差&lt;code&gt;errorPixel&lt;/code&gt;造成逆深度不确定性的影响，得到逆深度点的&lt;code&gt;min&lt;/code&gt;和&lt;code&gt;max&lt;/code&gt;如下：&lt;/p&gt;
&lt;div class="math"&gt;$$
{{d_{pi}}_u}_{min}=\frac{p_r[2]\times (u - l_x\times e_p) - p_r[0]}{Kt_{ji}[0]-Kt_{ji}[2]\times (u - l_x \times e_p)}\\
{{d_{pi}}_v}_{min}=\frac{p_r[2]\times (v - l_y\times e_p) - p_r[1]}{Kt_{ji}[1]-Kt_{ji}[2]\times (v - l_y\times e_p)}\\
{{d_{pi}}_u}_{max}=\frac{p_r[2]\times (u + l_x\times e_p) - p_r[0]}{Kt_{ji}[0]-Kt_{ji}[2]\times (u + l_x \times e_p)}\\
{{d_{pi}}_v}_{max}=\frac{p_r[2]\times (v + l_y\times e_p) - p_r[1]}{Kt_{ji}[1]-Kt_{ji}[2]\times (v + l_y\times e_p)}
$$&lt;/div&gt;
&lt;p&gt;其中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;span class="math"&gt;\(u\)&lt;/span&gt;为&lt;span class="math"&gt;\(p_j\)&lt;/span&gt;的x坐标值；&lt;/li&gt;
&lt;li&gt;&lt;span class="math"&gt;\(v\)&lt;/span&gt;为&lt;span class="math"&gt;\(p_j\)&lt;/span&gt;的y坐标值；&lt;/li&gt;
&lt;li&gt;&lt;span class="math"&gt;\(e_p\)&lt;/span&gt;为由经验公式求得的像素误差；&lt;/li&gt;
&lt;li&gt;&lt;span class="math"&gt;\(l_x\)&lt;/span&gt;为单位极线方向的x值；&lt;/li&gt;
&lt;li&gt;&lt;span class="math"&gt;\(l_y\)&lt;/span&gt;为单位极线方向的y值；&lt;/li&gt;
&lt;li&gt;最后，从两个&lt;code&gt;min&lt;/code&gt;值和两个&lt;code&gt;max&lt;/code&gt;值中选择一个最小值和一个最大值来更新未激活点的&lt;code&gt;idepth_min&lt;/code&gt;和&lt;code&gt;idepth_max&lt;/code&gt;即可。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="3"&gt;3 未成熟点如何激活？&lt;/h2&gt;
&lt;p&gt;未成熟点激活部分主要涵盖三方面的内容：构建距离地图，激活条件判断、激活点逆深度值优化。以最新关键帧&lt;code&gt;kf&lt;/code&gt;的金字塔第一层作为滑窗中现存激活点的投影位置。当某一个激活点投影到&lt;code&gt;kf&lt;/code&gt;的第一层后，更新相对像素距离到距离地图上。我想下面的图里描述的应该非常清楚（&lt;code&gt;#&lt;/code&gt;代表的是滑窗中的激活点投影位置），值得注意的是，针对某个投影点进行距离地图更新时，需要考虑其他投影点的位置，即某个像素位置&lt;code&gt;A&lt;/code&gt;距离投影点&lt;code&gt;p1&lt;/code&gt;的位置为5，而计算出距离另一个投影点为4时，就需要更新距离地图上的&lt;code&gt;A&lt;/code&gt;位置为4，而不是5。&lt;/p&gt;
&lt;div align="center"&gt;
&lt;img src="https://sunshanlu.github.io/sunshanlu/images/depth-map.png" width="80%"/&gt;
&lt;/div&gt;
&lt;p&gt;激活条件的判断主要考虑了下面四种情况：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;未成熟点在最新的极线搜索中，它的线搜距离在8个像素以内，代表逆深度值达到一个收敛的范围；&lt;/li&gt;
&lt;li&gt;未成熟点在最新的极线搜索中，要求搜索点的质量&lt;code&gt;quality&lt;/code&gt;大于3，质量被定义为次优线搜能量值 / 最优线搜能量值；&lt;/li&gt;
&lt;li&gt;要求未成熟点的逆深度平均值（&lt;code&gt;idepth_min&lt;/code&gt; + &lt;code&gt;idepth_max&lt;/code&gt;）大于0；&lt;/li&gt;
&lt;li&gt;要求未成熟点可以投影到最新关键帧上，并且要满足距离地图的约束（&lt;code&gt;DSO&lt;/code&gt;源码里面做了动态阈值调整，应该是根据工程实践调整出来的超参数）；&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;对于那些被判断为可以激活的点，&lt;code&gt;DSO&lt;/code&gt;将其分别投影到滑动窗口中的关键帧上，构建正规方程，并进行优化迭代求解，其残差构建和雅可比矩阵的求解我在&lt;a href="https://sunshanlu.github.io/dso_ssl/Optimization-Model-DSO"&gt;DSO中的优化模型&lt;/a&gt;文章中做了详细的推导，这里直接写结论：&lt;/p&gt;
&lt;div class="math"&gt;$$
r_k = I_j[p_j] - a_{ji}I_i[p_i] - b_{ji}\\
\begin{align*}
    \frac{\partial{r_k}}{\partial{d_{pi}}}&amp;amp;=
    \frac{1}{P_Z'} 
    \begin{bmatrix} d_x&amp;amp;d_y \end{bmatrix} 
    \begin{bmatrix} f_x &amp;amp; 0 \\ 0 &amp;amp; f_y \end{bmatrix}
    \begin{bmatrix}
        1 &amp;amp; 0 &amp;amp; -\frac{P_X'}{P_Z'} \\
        0 &amp;amp; 1 &amp;amp; -\frac{P_Y'}{P_Z'} \\
    \end{bmatrix}
    \begin{bmatrix}
        t_{ji}^X\\t_{ji}^Y\\t_{ji}^Z
    \end{bmatrix}\\ &amp;amp;=
    \frac{1}{P_Z'}[d_xf_x(t_X^{ji}-\frac{P_X'}{P_Z'}t^Z_{ji})+d_yf_y(t_Y^{ji}-\frac{P_Y'}{P_Z'}t^Z_{ji})]
\end{align*}\\
H_{dd}=\sum_{j\in \Omega}^{j!=i}{\sum_{p_i \in \mathcal{N(p)}}{\frac{\partial{r_k}}{\partial{d_{pi}}}^T\frac{\partial{r_k}}{\partial{d_{pi}}}}}\\
b_d=\sum_{j\in \Omega}^{j\neq i}{\sum_{p_i \in \mathcal{N(p)}}{\frac{\partial{r_k}}{\partial{d_{pi}}}^Tr_k}}
$$&lt;/div&gt;
&lt;p&gt;其中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;span class="math"&gt;\(\Omega\)&lt;/span&gt;为滑动窗口中的关键帧索引集合；&lt;/li&gt;
&lt;li&gt;&lt;span class="math"&gt;\(i\)&lt;/span&gt;为&lt;span class="math"&gt;\(p_i\)&lt;/span&gt;点对应的host帧索引；&lt;/li&gt;
&lt;li&gt;&lt;span class="math"&gt;\(j\)&lt;/span&gt;为&lt;span class="math"&gt;\(p_i\)&lt;/span&gt;点投影的target帧索引；&lt;/li&gt;
&lt;li&gt;&lt;span class="math"&gt;\(\mathcal{N(p)}\)&lt;/span&gt;代表的是以&lt;span class="math"&gt;\(p\)&lt;/span&gt;为中心的&lt;code&gt;pattern&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;从残差对&lt;span class="math"&gt;\(p_i\)&lt;/span&gt;点逆深度的雅可比矩阵来看，其优化过程应该相当迅速，因为正规方程中涉及到的内容全是标量。&lt;/p&gt;
&lt;h2 id="4"&gt;4 如何根据关键帧和路标点构建优化的正规方程？&lt;/h2&gt;
&lt;div align="center"&gt;
&lt;img src="https://sunshanlu.github.io/sunshanlu/images/swt-graph.png" width="80%"/&gt;
&lt;/div&gt;
&lt;p&gt;这里我们假设地图点激活后，滑动窗口中有3个关键帧和5个地图点，上图表示了帧与帧之间的可视关系，以及帧与激活点之间的可视关系。其中红色线代表某个点的&lt;code&gt;host&lt;/code&gt;帧，黑色线代表着某个帧可以看到某个点。&lt;/p&gt;
&lt;p&gt;根据&lt;a href="https://sunshanlu.github.io/dso_ssl/Optimization-Model-DSO"&gt;DSO中的优化模型&lt;/a&gt;文章中的残差模型推导，后端的优化能量函数，残差公式和残差的雅可比矩阵如下：&lt;/p&gt;
&lt;div class="math"&gt;$$
E_{fpC}=\sum_{i\in \Omega, j \in \Omega}^{j \neq i} \sum_{p \in C(I_i)}\sum_{p_i \in \mathcal{N(p)}} {||r_k||_{\gamma}}\\
r_k=I_j[p_j]-\frac{t_je^{a_i}}{t_ie^{a_j}}I_i[p_i]-(b_j-\frac{t_je^{a_i}}{t_ie^{a_j}}b_i)\\
\frac{\partial{r_k}}{\partial{d_{pi}}}=
\frac{1}{P_Z'}[d_xf_x(t_X^{ji}-\frac{P_X'}{P_Z'}t^Z_{ji})+d_yf_y(t_Y^{ji}-\frac{P_Y'}{P_Z'}t^Z_{ji})]\\
\frac{\partial{r_k}}{\partial{\xi_{ji}}}=
\begin{bmatrix} d_xf_x&amp;amp;d_yf_y\end{bmatrix}
\begin{bmatrix}
    \frac{d_{pi}}{P_{Z}'} &amp;amp; 0 &amp;amp; -\frac{d_{pi}}{P_{Z}'}\frac{P_{X}'}{P_{Z}'} &amp;amp; -\frac{P_{X}'P_{Y}'}{P_{Z}'^2} &amp;amp; 1+\frac{P_{X}^{2}}{P_{Z}^{2}} &amp;amp; -\frac{P_{Y}'}{P_{Z}'} 
    \\
    0 &amp;amp; \frac{d_{pi}}{P_{Z}'} &amp;amp; -\frac{d_{pi}}{P_{Z}'}\frac{P_{Y}'}{P_{Z}'} &amp;amp; -1-\frac{P_{Y}^{2}}{P_{Z}^{2}} &amp;amp; \frac{P_{X}'P_{Y}'}{P_{Z}^{2}} &amp;amp; \frac{P_{X}'}{P_{Z}'}
\end{bmatrix}\\
\frac{\partial r_k}{\partial a_{ji}}=-(I_i[p_i]-b_i)\\
\frac{\partial r_k}{\partial b_{ji}}=-1
$$&lt;/div&gt;
&lt;p&gt;其中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;span class="math"&gt;\(E_{fpC}\)&lt;/span&gt;为所有残差组成的能量部分，每个残差都由三个顶点组成，分别是&lt;span class="math"&gt;\(ij\)&lt;/span&gt;帧之间的相对参数、点&lt;span class="math"&gt;\(p\)&lt;/span&gt;的逆深度&lt;span class="math"&gt;\(d_{pi}\)&lt;/span&gt;和相机内参&lt;span class="math"&gt;\(C\)&lt;/span&gt;，因为&lt;code&gt;DSO&lt;/code&gt;的相机内参是由算法计算出来的，因此需要进行在线标定内参，我在&lt;a href="https://sunshanlu.github.io/dso_ssl/De-distortion-in-DSO"&gt;DSO中的去畸变操作&lt;/a&gt;中详细描述了&lt;code&gt;DSO&lt;/code&gt;相机内参的计算过程；&lt;/li&gt;
&lt;li&gt;后端滑窗中使用的&lt;span class="math"&gt;\(a_{ji}=\frac{t_je^a_{i}}{t_ie^{a_j}}\)&lt;/span&gt;，与初始化过程的&lt;span class="math"&gt;\(a_{ji}\)&lt;/span&gt;不同，初始化过程中是为了保证&lt;span class="math"&gt;\(e^{a_{ji}}&amp;gt;0\)&lt;/span&gt;，而后端部分不需要这个保证，因为&lt;span class="math"&gt;\(a_{ji}\)&lt;/span&gt;的计算只是做一个雅可比&lt;strong&gt;中转&lt;/strong&gt;的作用，并且使用这种中转方式可以简化整个雅可比的推导过程（相比使用&lt;span class="math"&gt;\(e^{a_ji}\)&lt;/span&gt;作为中间量）；&lt;/li&gt;
&lt;li&gt;&lt;span class="math"&gt;\(\frac{\partial r_k}{\partial d_{pi}}\)&lt;/span&gt;和&lt;span class="math"&gt;\(\frac{\partial r_k}{\partial \delta \xi_{ji}}\)&lt;/span&gt;部分直接使用的我在&lt;a href="https://sunshanlu.github.io/dso_ssl/Optimization-Model-DSO"&gt;DSO中的优化模型&lt;/a&gt;文章中推导的结论；&lt;/li&gt;
&lt;li&gt;残差对仿射参数的求导相对简单，都是通过&lt;span class="math"&gt;\(a_{ji}=\frac{t_je^{a_{i}}}{t_ie^{a_{j}}}\)&lt;/span&gt;和&lt;span class="math"&gt;\(b_{ji}=b_j - a_{ji} b_i\)&lt;/span&gt;作为中间变量进行推导即可，整个推导过程比较简单，这里就不展开说明了；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;由于&lt;code&gt;DSO&lt;/code&gt;使用算法的方式计算出了一个虚拟的相机内参，因此&lt;code&gt;DSO&lt;/code&gt;对相机内参做了在线标定，在滑窗优化过程中，将相机内参作为优化变量，求解残差对相机内参的雅可比矩阵，这个推导过程比较繁琐，涉及正向投影和反向投影两个部分，下面对残差相对内参的雅可比矩阵的详细推导：&lt;/p&gt;
&lt;p&gt;反向投影过程，代表&lt;span class="math"&gt;\(p_i\)&lt;/span&gt;点向&lt;span class="math"&gt;\(i\)&lt;/span&gt;帧的归一化坐标系上进行反向投影：
&lt;/p&gt;
&lt;div class="math"&gt;$$
X_i^n=\frac{1}{f_x}u_i-\frac{c_x}{f_x}\\
Y_i^n=\frac{1}{f_y}v_i-\frac{c_y}{f_y}\\
\frac{\partial P_i^n}{\partial C}=\begin{bmatrix}
    -\frac{u_i}{f_x^2} &amp;amp; 0 &amp;amp; -\frac{1}{f_x} &amp;amp; 0 \\
    0 &amp;amp; -\frac{v_i}{f_y^2} &amp;amp; 0 &amp;amp; -\frac{1}{f_y} \\
    0 &amp;amp; 0 &amp;amp; 0 &amp;amp; 0
\end{bmatrix}
$$&lt;/div&gt;
&lt;p&gt;正向投影过程，代表由&lt;span class="math"&gt;\(i\)&lt;/span&gt;帧归一化坐标系&lt;span class="math"&gt;\(P_i^{n}\)&lt;/span&gt;向&lt;span class="math"&gt;\(j\)&lt;/span&gt;帧像素坐标系的投影过程，包含一个坐标变换和投影过程：
&lt;/p&gt;
&lt;div class="math"&gt;$$
P_j' = R_{ji}P_i^{n}+t_{ji}d_{p_i}\\
u_j = f_x \frac{X_j'}{Z_j'} + c_x\\
v_j = f_y \frac{Y_j'}{Z_j'} + c_y\\
\frac{\partial p_j}{\partial C} = \begin{bmatrix}
    \frac{X_j'}{Z_j'} &amp;amp; 0 &amp;amp; 1 &amp;amp; 0 \\
    0 &amp;amp; \frac{Y_j'}{Z_j'} &amp;amp; 0 &amp;amp; 1
\end{bmatrix}\\
\frac{\partial p_j}{\partial P_i^n}=\frac{\partial p_j}{\partial P_j'} \times \frac{\partial P_j'}{\partial P_i^n}=\begin{bmatrix}
    \frac{f_x}{Z_j'} &amp;amp; 0 &amp;amp; -\frac{f_xX_j'}{Z_j'^2}\\
    0 &amp;amp; \frac{f_y}{Z_j'} &amp;amp; -\frac{f_yY_j'}{Z_j'^2}
\end{bmatrix}\times R_{ji}
$$&lt;/div&gt;
&lt;p&gt;结合正向和反向投影过程，可得残差对相机内参&lt;span class="math"&gt;\(C\)&lt;/span&gt;的雅可比矩阵：
&lt;/p&gt;
&lt;div class="math"&gt;$$
\begin{align*}
    \frac{\partial r_k[p_j(C,P_i^n(C))]}{\partial C} &amp;amp;= \frac{\partial r_k}{\partial p_j}\times (\frac{\partial p_j}{\partial C} + \frac{\partial p_j}{\partial P_i^n}\times \frac{\partial P_i^n}{\partial C})\\
    &amp;amp;=\begin{bmatrix}d_x &amp;amp; d_y\end{bmatrix}
    (
        \begin{bmatrix}
            \frac{X_j'}{Z_j'} &amp;amp; 0 &amp;amp; 1 &amp;amp; 0 \\
            0 &amp;amp; \frac{Y_j'}{Z_j'} &amp;amp; 0 &amp;amp; 1
        \end{bmatrix}+
        \begin{bmatrix}
            \frac{f_x}{Z_j'} &amp;amp; 0 &amp;amp; -\frac{f_xX_j'}{Z_j'^2}\\
            0 &amp;amp; \frac{f_y}{Z_j'} &amp;amp; -\frac{f_yY_j'}{Z_j'^2}
        \end{bmatrix}\times R_{ji}\times
        \begin{bmatrix}
            -\frac{u_i}{f_x^2} &amp;amp; 0 &amp;amp; -\frac{1}{f_x} &amp;amp; 0 \\
            0 &amp;amp; -\frac{v_i}{f_y^2} &amp;amp; 0 &amp;amp; -\frac{1}{f_y} \\
            0 &amp;amp; 0 &amp;amp; 0 &amp;amp; 0
        \end{bmatrix}
    )
\end{align*}
$$&lt;/div&gt;
&lt;p&gt;根据&lt;code&gt;DSO&lt;/code&gt;的残差模型，可以绘制图优化的优化图如下图所示，其中红色线连接三个顶点，分别为两帧之间的相对量顶点和激活点逆深度顶点和相机内参顶点。&lt;/p&gt;
&lt;div align="center"&gt;
&lt;img src="https://sunshanlu.github.io/sunshanlu/images/swt-graph-rel.png" width="80%"/&gt;
&lt;/div&gt;
&lt;p&gt;根据上图所示的优化图，可以绘制由这些残差构建的Hessian矩阵如下图所示，但是这里会出现一些问题，如果去解这一个由两帧相对量和激活点构建的Hessian矩阵的话，得到的结果应该都是相对量增量，这样解出来的结果只是求解出了滑动窗口帧之间的&lt;strong&gt;相对量&lt;/strong&gt;&lt;code&gt;Tth&lt;/code&gt;、&lt;code&gt;a_th&lt;/code&gt;和&lt;code&gt;b_th&lt;/code&gt;，而不是相对于世界坐标系的&lt;strong&gt;绝对量&lt;/strong&gt;。&lt;/p&gt;
&lt;div align="center"&gt;
&lt;img src="https://sunshanlu.github.io/sunshanlu/images/H-rel.png" width="70%"/&gt;
&lt;/div&gt;
&lt;div class="admonition note"&gt;
&lt;p class="admonition-title"&gt;讨论仅求帧相对量的可行性&lt;/p&gt;
&lt;p&gt;我认为，仅求帧相对量不可行，我主要是通过一下三个方面来考虑的。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;相对量求解完成后，需要更新为绝对量，也就是相对于世界坐标系的参数量，在更新绝对量过程中会出现冲突问题，假设有三个关键帧，那么相对位姿就会有&lt;code&gt;T12&lt;/code&gt;、&lt;code&gt;T13&lt;/code&gt;和&lt;code&gt;T23&lt;/code&gt;三个，假设&lt;code&gt;Tw1&lt;/code&gt;已知的情况下,&lt;code&gt;Tw3&lt;/code&gt;可以通过两种方式表示，分别是&lt;code&gt;Tw1&lt;/code&gt; * &lt;code&gt;T13&lt;/code&gt;和&lt;code&gt;Tw1&lt;/code&gt; * &lt;code&gt;T12&lt;/code&gt; * &lt;code&gt;T23&lt;/code&gt;。这两种表示方法势必会产生冲突，这个冲突问题怎么解决呢？&lt;/li&gt;
&lt;li&gt;就算可以通过某种方式，解决1中说明的更新冲突问题，那么当滑动窗口优化达到某个规模上限后，为了实时性势必会进行激活点和关键帧的边缘化操作，假设系统判定，当前需要边缘化&lt;span class="math"&gt;\(f_1\)&lt;/span&gt;这个关键帧，对于绝对量的表示来讲，其边缘化所表达的概率模型为 &lt;span class="math"&gt;\(p(f_1,f_2,f_3,p_1,p_2,p_3,p_4,p_5)=p(f_2,f_3,p_1,p_2,p_3,p_4,p_5|f_1) * p(f_1)\)&lt;/span&gt;，其中边缘化完成后&lt;span class="math"&gt;\(p(f_1)\)&lt;/span&gt;为常量概率。但是对相对量来讲，如何进行边缘化操作呢？不能把&lt;span class="math"&gt;\(f_1\)&lt;/span&gt;涉及的所有相对量都进行边缘化吧，这样的话肯定会造成信息丢失。&lt;/li&gt;
&lt;li&gt;最后，即便不考虑相对量边缘化操作造成的信息丢失，在求解正规方程上，相对量的Hessian矩阵的规模也要比绝对量表示的Hessian矩阵大得多。因为&lt;span class="math"&gt;\(C_n^2 \geq n,n\geq 2\)&lt;/span&gt;总是成立的。因此使用&lt;code&gt;schur&lt;/code&gt;分解加速正规方程求解时，相对量表示的分解后得到的稠密矩阵H，要比绝对量表示的稠密矩阵H大得多。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;从上面三方面的分析来看，从&lt;strong&gt;可行性&lt;/strong&gt;，&lt;strong&gt;信息留存率&lt;/strong&gt;和&lt;strong&gt;效率&lt;/strong&gt;三方面来讲，绝对量表示的正规方程都优于相对量表示的正规方程。&lt;/p&gt;
&lt;/div&gt;
&lt;h3 id="41"&gt;4.1 残差构建仅包含关键帧的相对量，怎么办？&lt;/h3&gt;
&lt;p&gt;从上面的分析中可知，绝对量表示的正规方程要优于相对量表示的正规方程，&lt;code&gt;DSO&lt;/code&gt;使用位姿伴随的性质，求解&lt;span class="math"&gt;\(\frac{\delta\xi_{ji}}{\delta\xi_{i}}\)&lt;/span&gt;和&lt;span class="math"&gt;\(\frac{\delta \xi_{ji}}{\delta \xi_{j}}\)&lt;/span&gt;实现位姿相对量到绝对量的变换。而仿射参数的&lt;span class="math"&gt;\(\frac{\partial a_{ji}}{\partial a_i}\)&lt;/span&gt;、&lt;span class="math"&gt;\(\frac{\partial a_{ji}}{\partial a_j}\)&lt;/span&gt;、&lt;span class="math"&gt;\(\frac{\partial b_{ji}}{\partial b_i}\)&lt;/span&gt;、&lt;span class="math"&gt;\(\frac{\partial b_{ji}}{\partial b_j}\)&lt;/span&gt;可以根据残差方程直接推导出来，下面是推导流程。&lt;/p&gt;
&lt;p&gt;首先，需要说明一个位姿矩阵的伴随性质，&lt;span class="math"&gt;\(Exp(Ad_T \times \xi)=TExp(\xi)T^{-1}\)&lt;/span&gt;，其中&lt;span class="math"&gt;\(Ad_T\)&lt;/span&gt;为位姿矩阵&lt;span class="math"&gt;\(T\)&lt;/span&gt;的伴随矩阵，&lt;span class="math"&gt;\(\xi\)&lt;/span&gt;为一个李代数上的小扰动。现在，考虑一个相对量位姿&lt;span class="math"&gt;\(T_{ji}\)&lt;/span&gt;的左扰动&lt;span class="math"&gt;\(\delta \xi_{ji}\)&lt;/span&gt;，它势必会造成&lt;span class="math"&gt;\(T_{jw}\)&lt;/span&gt;和&lt;span class="math"&gt;\(T_{iw}\)&lt;/span&gt;的小的左扰动，假设它们分别是&lt;span class="math"&gt;\(\delta \xi_j\)&lt;/span&gt;和&lt;span class="math"&gt;\(\delta \xi_i\)&lt;/span&gt;，它们的公式描述如下：&lt;/p&gt;
&lt;div class="math"&gt;$$
\begin{align*}
    Exp(\delta \xi_{ji})T_{ji}&amp;amp;=Exp(\delta \xi_j) T_{jw} T_{iw}^{-1} \\ &amp;amp;\to Exp(\delta \xi_{ji})=Exp(\delta \xi_j) \to \frac{ \partial \delta \xi_{ji}}{\partial \delta \xi_j}=I\\
    Exp(\delta \xi_{ji})T_{ji}&amp;amp;=T_{jw} (Exp(\delta \xi_i)T_{iw})^{-1}=T_{jw}T_{iw}^{-1}Exp(-\delta \xi_i)\\
    &amp;amp;\to Exp(\delta \xi_{ji})=T_{ji}Exp(-\delta \xi_i)T_{ji}^{-1}=-Ad_{T_{ji}} \times \delta \xi_i \to \frac{ \partial \delta \xi_{ji}}{\partial \delta \xi_i}=-Ad_{T_{ji}}
\end{align*}
$$&lt;/div&gt;
&lt;p&gt;相对仿射参数到绝对仿射参数之间的转换比较简单，可以根据残差公式直接推导：&lt;/p&gt;
&lt;div class="math"&gt;$$
r_k=I_j[p_j]-\frac{t_je^{a_i}}{t_ie^{a_j}}I_i[p_i]-(b_j-\frac{t_je^{a_i}}{t_ie^{a_j}}b_i)\\
a_{ji}=\frac{t_je^{a_i}}{t_ie^{a_j}} \quad b_{ji}=(b_j-\frac{t_je^{a_i}}{t_ie^{a_j}}b_i)\\
\frac{\partial a_{ji}}{\partial a_i}=\frac{t_je^{a_j}}{t_ie^{a_i}}\\
\frac{\partial a_{ji}}{\partial a_j} = -\frac{t_je^{a_j}}{t_ie^{a_i}} \\
\frac{\partial b_{ji}}{\partial b_i}=-\frac{t_je^{a_j}}{t_ie^{a_i}}\\
\frac{\partial b_{ji}}{\partial b_j}= 1\\
$$&lt;/div&gt;
&lt;p&gt;上面通过推导的形式，构建了&lt;span class="math"&gt;\(\frac{\partial \delta \xi_{ji}}{\partial \xi_j}\)&lt;/span&gt;、&lt;span class="math"&gt;\(\frac{\partial \delta \xi_{ji}}{\partial \xi_i}\)&lt;/span&gt;、&lt;span class="math"&gt;\(\frac{\partial a_{ji}}{\partial a_i}\)&lt;/span&gt;、&lt;span class="math"&gt;\(\frac{\partial a_{ji}}{\partial a_j}\)&lt;/span&gt;、&lt;span class="math"&gt;\(\frac{\partial b_{ji}}{\partial b_i}\)&lt;/span&gt;、&lt;span class="math"&gt;\(\frac{\partial b_{ji}}{\partial b_j}\)&lt;/span&gt;，因此可以计算残差相对于绝对量的雅可比矩阵，也就能完成基于相对量的&lt;code&gt;Hessian&lt;/code&gt;矩阵到绝对量&lt;code&gt;Hessian&lt;/code&gt;矩阵的转换。&lt;/p&gt;
&lt;div align="center"&gt;
&lt;img src="https://sunshanlu.github.io/sunshanlu/images/Hlocal-Hglobal.png" width="80%"/&gt;
&lt;/div&gt;
&lt;h3 id="42"&gt;4.2 为什么会有不一致性问题，该怎么解决？&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;DSO&lt;/code&gt;的后端使用的是滑动窗口的优化策略，因此&lt;code&gt;DSO&lt;/code&gt;使用边缘化策略来控制整个后端滑动窗口中的规模。当然这个边缘化的操作是在后端优化完成后做的，其详细的策略我会在第5小节中描述，这里我们只需要知道，每次进行后端的滑动窗口优化时，都需要考虑最新边缘化操作得到的&lt;code&gt;HM&lt;/code&gt;和&lt;code&gt;bM&lt;/code&gt;矩阵。这代表着边缘化后剩余的信息，以最大似然估计的角度看，&lt;code&gt;HM&lt;/code&gt;和&lt;code&gt;bM&lt;/code&gt;中的信息代表着系统的条件概率，由于&lt;code&gt;HM&lt;/code&gt;和&lt;code&gt;bM&lt;/code&gt;会提供一个边缘化先验信息，它们所表达的约束为&lt;span class="math"&gt;\(H_M \delta x = -b_M\)&lt;/span&gt;，这个约束可以为后续的优化操作提供方向。&lt;/p&gt;
&lt;p&gt;与想象中不同的是，我们&lt;strong&gt;不能&lt;/strong&gt;以下面这种方式使用边缘化得到的&lt;span class="math"&gt;\(H_M\)&lt;/span&gt;和&lt;span class="math"&gt;\(b_M\)&lt;/span&gt;：&lt;/p&gt;
&lt;div class="math"&gt;$$
(H_A + H_M + H_P) \delta x = -(b_A + b_M + b_P)
$$&lt;/div&gt;
&lt;p&gt;其中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;span class="math"&gt;\(H_A\)&lt;/span&gt;，&lt;span class="math"&gt;\(b_A\)&lt;/span&gt;代表由滑动窗口中所有残差构建的H矩阵和b矩阵；&lt;/li&gt;
&lt;li&gt;&lt;span class="math"&gt;\(H_M\)&lt;/span&gt;，&lt;span class="math"&gt;\(b_M\)&lt;/span&gt;代表边缘化后得到的H矩阵和b矩阵；&lt;/li&gt;
&lt;li&gt;&lt;span class="math"&gt;\(H_P\)&lt;/span&gt;，&lt;span class="math"&gt;\(b_P\)&lt;/span&gt;代表先验信息的H矩阵和b矩阵；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;不能直接进行相加的原因主要有两点，首先&lt;span class="math"&gt;\(H_A\)&lt;/span&gt;和&lt;span class="math"&gt;\(H_M\)&lt;/span&gt;线性化点的位置不同，这样直接相加会导致系统出现&lt;strong&gt;不一致性问题&lt;/strong&gt;，其次&lt;span class="math"&gt;\(b_M\)&lt;/span&gt;在优化中代表的是能量在线性化点&lt;span class="math"&gt;\(x_M\)&lt;/span&gt;处的能量梯度（能量对待优化量的雅可比矩阵，在线性化点&lt;span class="math"&gt;\(x_M\)&lt;/span&gt;处），但是随着更新的进行，两个线性化点&lt;span class="math"&gt;\(x_A\)&lt;/span&gt;和&lt;span class="math"&gt;\(x_M\)&lt;/span&gt;的距离可能逐渐变大，这样&lt;span class="math"&gt;\(b_M\)&lt;/span&gt;就不能近似&lt;span class="math"&gt;\(x_A\)&lt;/span&gt;处的能量梯度了，其中&lt;span class="math"&gt;\(x_A\)&lt;/span&gt;代表的是残差的线性化点，&lt;span class="math"&gt;\(x_M\)&lt;/span&gt;代表的是边缘化信息的线性化点，值得注意的是，&lt;span class="math"&gt;\(x_A\)&lt;/span&gt;随着优化的进行，会逐渐改变，而&lt;span class="math"&gt;\(x_M\)&lt;/span&gt;则不能改变。&lt;/p&gt;
&lt;p&gt;对不一致性问题的详细说明：在论文 &lt;strong&gt;[1]&lt;/strong&gt; 中，作者讨论了不同线性化点处的&lt;code&gt;H&lt;/code&gt;矩阵直接相加导致了&lt;span class="math"&gt;\(H\)&lt;/span&gt;矩阵的&lt;strong&gt;秩增加&lt;/strong&gt;的情况（系统的某些状态由不客观变得可观了），也就是说这种不正确的操作&lt;strong&gt;低估&lt;/strong&gt;了系统状态的&lt;strong&gt;不确定性&lt;/strong&gt;，认定这种改变系统状态不确定性的问题为系统&lt;strong&gt;不一致性&lt;/strong&gt;问题。下图清晰也表述了系统的不一致性问题，图中表示，系统的解为&lt;span class="math"&gt;\(xy=1\)&lt;/span&gt;，具有一个状态是不可观的。如果将&lt;span class="math"&gt;\(E_1\)&lt;/span&gt;在&lt;span class="math"&gt;\(x=0.5\)&lt;/span&gt;处线性化，&lt;span class="math"&gt;\(E_2\)&lt;/span&gt;在&lt;span class="math"&gt;\(x=1.2\)&lt;/span&gt;处线性化后求解，导致系统的解坍缩成了一个点，从而失去了系统的不确定性。&lt;/p&gt;
&lt;div align="center"&gt;
&lt;img src="https://sunshanlu.github.io/sunshanlu/images/inconsistencies.png" width="80%"/&gt;
&lt;/div&gt;
&lt;p&gt;&lt;code&gt;DSO&lt;/code&gt;使用了&lt;code&gt;FEJ&lt;/code&gt;（First Estimate Jacobian）来解决不同线性化点导致的问题，&lt;code&gt;FEJ&lt;/code&gt;是这样描述的，由于边缘化提供的&lt;span class="math"&gt;\(H_M\)&lt;/span&gt;和&lt;span class="math"&gt;\(b_M\)&lt;/span&gt;线性化点不能改变了，但是由线性化残差构建的&lt;span class="math"&gt;\(H_A\)&lt;/span&gt;和&lt;span class="math"&gt;\(b_A\)&lt;/span&gt;的线性化点却可以改变，为了保证&lt;code&gt;H&lt;/code&gt;矩阵的一致性，残差构建的&lt;span class="math"&gt;\(H_A\)&lt;/span&gt;也使用&lt;span class="math"&gt;\(x_M\)&lt;/span&gt;进行线性化，不会改变系统的能观性，但是这样做势必会&lt;strong&gt;引入线性误差&lt;/strong&gt;。也就是说，残差&lt;span class="math"&gt;\(r_k\)&lt;/span&gt;值的计算，使用&lt;span class="math"&gt;\(x_A\)&lt;/span&gt;线性化点计算，&lt;span class="math"&gt;\(\frac{\partial r_k}{\partial x}\)&lt;/span&gt;使用&lt;span class="math"&gt;\(x_M\)&lt;/span&gt;线性化点处计算，这样可以构建一个新的&lt;span class="math"&gt;\(H_A\)&lt;/span&gt;和&lt;span class="math"&gt;\(b_A\)&lt;/span&gt;。除此之外，为了避免&lt;span class="math"&gt;\(b_M\)&lt;/span&gt;描述的能量梯度不准确的问题，&lt;code&gt;FEJ&lt;/code&gt;使用一阶泰勒展开对&lt;span class="math"&gt;\(b_M\)&lt;/span&gt;进行&lt;strong&gt;修正&lt;/strong&gt;，&lt;code&gt;FEJ&lt;/code&gt;的公式描述如下：&lt;/p&gt;
&lt;div class="math"&gt;$$
E_A(x) = E(x_A) + b_A(r_k(x_A), \frac{\partial r_k}{\partial x}|_{x=x_M})(x-x_A) + (x-x_A)^TH_A(x_M)(x-x_A) \\
E_M(x) = E(x_M) + b_M(x_M + x_A - x_M)(x-x_A) + (x-x_A)^TH_M(x_M)(x-x_A)\\
\frac{\partial b_M}{\partial x}|_{x=x_M}=H_M(x_M) \\
b_M(x_M + x_A - x_M) = b_M(x_M) + H_M(x_M)(x_A - x_M) \\
$$&lt;/div&gt;
&lt;p&gt;其中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;span class="math"&gt;\(x_A\)&lt;/span&gt;为某次优化中，残差线性化点；&lt;/li&gt;
&lt;li&gt;&lt;span class="math"&gt;\(x_M\)&lt;/span&gt;为&lt;code&gt;marg&lt;/code&gt;的线性化点；&lt;/li&gt;
&lt;li&gt;&lt;span class="math"&gt;\(E_A(x)\)&lt;/span&gt;为残差对应能量值；&lt;/li&gt;
&lt;li&gt;&lt;span class="math"&gt;\(E_M(x)\)&lt;/span&gt;为&lt;code&gt;marg&lt;/code&gt;对应的能量值；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;从&lt;code&gt;FEJ&lt;/code&gt;的公式描述可以发现，为了保证&lt;code&gt;H&lt;/code&gt;矩阵的一致性，&lt;span class="math"&gt;\(H_A(x_M)\)&lt;/span&gt;部分引入了线性化误差，在&lt;span class="math"&gt;\(b_A\)&lt;/span&gt;计算过程中，&lt;span class="math"&gt;\(\frac{\partial r_k}{\partial x}|_{x=x_M}\)&lt;/span&gt;部分引入了线性化误差，而&lt;span class="math"&gt;\(b_M(x_M+x_A - x_M)\)&lt;/span&gt;部分使用泰勒展开进行了修正。&lt;/p&gt;
&lt;div class="admonition warning"&gt;
&lt;p class="admonition-title"&gt;&lt;span class="caps"&gt;FEJ&lt;/span&gt; 非用不可？&lt;/p&gt;
&lt;p&gt;通过上面公式的分析，可以发现，使用&lt;code&gt;FEJ&lt;/code&gt;会不可避免的引入线性化误差（对残差部分来讲），而不引入&lt;code&gt;FEJ&lt;/code&gt;又会导致系统的不一致性问题。貌似使用&lt;code&gt;FEJ&lt;/code&gt;或者不使用&lt;code&gt;FEJ&lt;/code&gt;都做不到完美。我认为&lt;code&gt;FEJ&lt;/code&gt;并不是非使用不可，比如&lt;code&gt;VINS&lt;/code&gt;中就没有使用&lt;code&gt;FEJ&lt;/code&gt;，当然如果不使用&lt;code&gt;FEJ&lt;/code&gt;的话，还是建议使用&lt;code&gt;FEJ&lt;/code&gt;的&lt;span class="math"&gt;\(b_M\)&lt;/span&gt;更新策略来维护&lt;span class="math"&gt;\(b_M\)&lt;/span&gt;。甚至可以&lt;strong&gt;部分使用&lt;/strong&gt;&lt;code&gt;FEJ&lt;/code&gt;，&lt;code&gt;DSO&lt;/code&gt;也是部分使用&lt;code&gt;FEJ&lt;/code&gt;，来规避引入大的&lt;strong&gt;线性误差&lt;/strong&gt;的风险。在&lt;code&gt;DSO&lt;/code&gt;中，激活点的逆深度状态完全没有使用&lt;code&gt;FEJ&lt;/code&gt;，这可能是因为激活点在经过一轮的滑动窗口优化后，可能不太稳定，如果固定逆深度的话，可能会引入较大的线性化误差。除此之外，&lt;code&gt;DSO&lt;/code&gt;对&lt;span class="math"&gt;\(\frac{\partial r_k}{\partial \xi_{ji}}\)&lt;/span&gt;、&lt;span class="math"&gt;\(\frac{\partial r_k}{\partial d_{pi}}\)&lt;/span&gt;和&lt;span class="math"&gt;\(\frac{\partial r_k}{\partial C}\)&lt;/span&gt;分阶段求解，&lt;span class="math"&gt;\(\frac{\partial r_k}{\partial \xi_{ji}}=\frac{\partial r_k}{\partial p_{j}}|_{x=x_A} \times \frac{\partial p_j}{\partial \xi_{ji}}|_{x=x_M} \quad \frac{\partial r_k}{\partial d_{pi}} = \frac{\partial r_k}{\partial p_j}|_{x=x_A} \times \frac{\partial p_j}{\partial d_{pi}}|_{x=x_M} \quad \frac{\partial r_k}{\partial C} = \frac{\partial r_k}{\partial p_j}|_{x=x_A} \times \frac{\partial p_j}{\partial C}|_{x=x_M}\)&lt;/span&gt;，可能考虑到图像为非线性比较强的函数，因此对所有残差雅可比涉及图像梯度的部分都没有使用&lt;code&gt;FEJ&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;DSO&lt;/code&gt;在滑动窗口优化中的残差线性化阶段计算&lt;span class="math"&gt;\(\frac{\partial p_j}{\partial \delta \xi_{ji}}\)&lt;/span&gt;、&lt;span class="math"&gt;\(\frac{\partial p_j}{\partial C}\)&lt;/span&gt;和&lt;span class="math"&gt;\(\frac{\partial p_j}{\partial d_{pi}}\)&lt;/span&gt;时使用了中心点的计算值代替整个&lt;code&gt;pattern&lt;/code&gt;中每一个点的计算值，相当于减少了部分计算量，不过不清楚这么做是否会加重&lt;code&gt;FEJ&lt;/code&gt;引入的线性化误差。&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;使用&lt;code&gt;FEJ&lt;/code&gt;解决了系统不一致性问题后，就需要考虑先验的影响。&lt;code&gt;DSO&lt;/code&gt;的系统求解中，主要有以下几种状态存在先验信息：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;初始化完成后，加入到滑动窗口中的两个关键帧状态有先验。&lt;/li&gt;
&lt;li&gt;初始化完成后，加入到滑动窗口中的激活点逆深度状态有先验。&lt;/li&gt;
&lt;li&gt;由算法计算出的虚拟内参，有先验。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在给定某个状态的先验信息矩阵&lt;span class="math"&gt;\(H_P\)&lt;/span&gt;后，先验信息构建的正规方程可以由下面的公式进行描述：&lt;/p&gt;
&lt;div class="math"&gt;$$
E_P=\frac{1}{2}(x_A+\Delta x-x_P)^TH_P(x_P)(x_A+\Delta x-x_P)\\
b_P(x_P)=H_P(x_P)(x_A+\Delta x-x_P)
$$&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;span class="math"&gt;\(E_P\)&lt;/span&gt;为先验信息所对应的能量值；&lt;/li&gt;
&lt;li&gt;&lt;span class="math"&gt;\(H_P\)&lt;/span&gt;和&lt;span class="math"&gt;\(b_P\)&lt;/span&gt;为先验信息构建的正规方程矩阵；&lt;/li&gt;
&lt;li&gt;&lt;span class="math"&gt;\(x_P\)&lt;/span&gt;为先验状态；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;综上所述，当残差构建完成后，系统会根据提供的先验信息矩阵和最新边缘化得到的边缘化信息构建本次滑动窗口优化的正规方程，并进行迭代求解，构建的正规方程可以由下面的公式进行描述：&lt;/p&gt;
&lt;div class="math"&gt;$$
H = H_A(x_M) + H_P(x_P) + H_M(x_M)\\
b = b_A(r_k(x_A), \frac{\partial r_k}{\partial x}|_{x=x_M}) + b_P(x_P) + b_M(x_M + x_A - x_M)\\
H \Delta x = -b
$$&lt;/div&gt;
&lt;div class="admonition note"&gt;
&lt;p class="admonition-title"&gt;FEJ减少的计算量&lt;/p&gt;
&lt;p&gt;在上面对FEJ的探讨中，我们知道了&lt;code&gt;DSO&lt;/code&gt;中使用了部分&lt;code&gt;FEJ&lt;/code&gt;的方案来解决系统的不一致性问题，又避免了引入大的线性化误差。使用这种方案竟还意外的减少了部分计算量，在4.1节中我们知道&lt;code&gt;DSO&lt;/code&gt;需要将绝对量变换为相对量，需要求解&lt;span class="math"&gt;\(\frac{\partial {\xi_{ji}}}{\partial \xi_j}\)&lt;/span&gt;、&lt;span class="math"&gt;\(\frac{\partial {\xi_{ji}}}{\partial \xi_i}\)&lt;/span&gt;、&lt;span class="math"&gt;\(\frac{\partial {a_{ji}}}{\partial a_j}\)&lt;/span&gt;、&lt;span class="math"&gt;\(\frac{\partial {a_{ji}}}{\partial a_i}\)&lt;/span&gt;、&lt;span class="math"&gt;\(\frac{\partial {b_{ji}}}{\partial b_i}\)&lt;/span&gt;和&lt;span class="math"&gt;\(\frac{\partial {b_{ji}}}{\partial b_j}\)&lt;/span&gt;。而&lt;code&gt;DSO&lt;/code&gt;的部分&lt;code&gt;FEJ&lt;/code&gt;策略又将&lt;span class="math"&gt;\(\frac{\partial p_j}{\partial \xi_{ji}}\)&lt;/span&gt;、&lt;span class="math"&gt;\(\frac{\partial r_k}{\partial \a_{ji}}\)&lt;/span&gt;、&lt;span class="math"&gt;\(\frac{\partial r_k}{\partial \b_{ji}}\)&lt;/span&gt;限制在线性化&lt;span class="math"&gt;\(x_M\)&lt;/span&gt;处，因此相对量到绝对量转换的中间量的求解只需要在线性化点&lt;span class="math"&gt;\(x_M\)&lt;/span&gt;处求解一次即可，在一定程度上可以减少一些计算量。&lt;/p&gt;
&lt;/div&gt;
&lt;h3 id="43"&gt;4.3 在解正规方程时，如何防止解在零空间中漂移？&lt;/h3&gt;
&lt;p&gt;对于某个等式&lt;span class="math"&gt;\(H \Delta x = -b\)&lt;/span&gt;来讲，如果矩阵&lt;span class="math"&gt;\(H\)&lt;/span&gt;不是满秩阵，那么必定存在&lt;span class="math"&gt;\(H x_{ns} = 0, x \ne \overrightarrow{0}\)&lt;/span&gt;。其中&lt;span class="math"&gt;\(x_{ns}\)&lt;/span&gt;为由&lt;span class="math"&gt;\(H\)&lt;/span&gt;矩阵在零空间上的基组成的向量。这个&lt;span class="math"&gt;\(x_{ns}\)&lt;/span&gt;也被称为解在零空间上的漂移。因为&lt;span class="math"&gt;\(H x_{ns} = 0, x \ne \overrightarrow{0}\)&lt;/span&gt;总是成立的，因此不论&lt;span class="math"&gt;\(x_{ns}\)&lt;/span&gt;漂移多少，&lt;span class="math"&gt;\(H (\Delta x + x_{ns}) = -b\)&lt;/span&gt;总是成立的。也就是说在解正规方程时，如果不对零空间的漂移加以限制，那么解与不含漂移的解之间的误差就会很大。然而对于单目的&lt;code&gt;VO&lt;/code&gt;系统来讲，系统总是有7个自由度，也就是说，H矩阵一定非满秩，并且与其行数差7，需要进行一些处理手段来保证正规方程得到的解不漂移。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;DSO&lt;/code&gt;的源码里面定义了两种防止零空间漂移的方法，一种是基于&lt;code&gt;SVD&lt;/code&gt;分解截断小奇异值求矩阵伪逆的方案，另一种是基于位姿矩阵伴随的性质求解世界坐标系零空间到局部坐标系零空间映射，并使用正交基投影的方式去掉零空间漂移的影响。&lt;/p&gt;
&lt;p&gt;首先，我们先看下&lt;code&gt;DSO&lt;/code&gt;求伪逆是如何防止零空间漂移的，假设目前完成了某次线性化，得到正规方程&lt;span class="math"&gt;\(H \Delta x = -b\)&lt;/span&gt;，那么&lt;span class="math"&gt;\(H\)&lt;/span&gt;的伪逆可以由下面的公式表示：&lt;/p&gt;
&lt;div class="math"&gt;$$
H = U \Sigma V^T\\
H^{-1} = V \Sigma^{+}U^T\\
\Delta x^+ = H^{-1}b
$$&lt;/div&gt;
&lt;p&gt;其中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;span class="math"&gt;\(U\)&lt;/span&gt;和&lt;span class="math"&gt;\(V\)&lt;/span&gt;为酉矩阵（代表等距变换，列正交）；&lt;/li&gt;
&lt;li&gt;&lt;span class="math"&gt;\(\Sigma\)&lt;/span&gt;矩阵为对角矩阵，其对角上的值为奇异值；&lt;/li&gt;
&lt;li&gt;奇异值为0的对应的 &lt;strong&gt;&lt;span class="math"&gt;\(V\)&lt;/span&gt;矩阵&lt;/strong&gt;的&lt;strong&gt;列&lt;/strong&gt;为矩阵&lt;span class="math"&gt;\(H\)&lt;/span&gt;的&lt;strong&gt;零空间的基&lt;/strong&gt;，并且是&lt;strong&gt;正交基&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;对&lt;span class="math"&gt;\(\Sigma\)&lt;/span&gt;矩阵中的小奇异值置零后，将其非0奇异值取倒数后，转置得到&lt;span class="math"&gt;\(\Sigma^{+}\)&lt;/span&gt;；&lt;/li&gt;
&lt;li&gt;&lt;span class="math"&gt;\(H^{-1}\)&lt;/span&gt;为&lt;span class="math"&gt;\(H\)&lt;/span&gt;的伪逆；&lt;/li&gt;
&lt;li&gt;&lt;span class="math"&gt;\(\Delta x^+\)&lt;/span&gt;为去掉零空间漂移的正规方程的解；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;通过对&lt;span class="math"&gt;\(\Sigma^+\)&lt;/span&gt;矩阵和&lt;span class="math"&gt;\(V\)&lt;/span&gt;矩阵的描述可知，&lt;span class="math"&gt;\(V\)&lt;/span&gt;矩阵中0奇异值对应的列向量为零空间的一组正交基，然而&lt;span class="math"&gt;\(\Sigma^+\)&lt;/span&gt;矩阵将小奇异值置零，在伪逆计算中，&lt;span class="math"&gt;\(\Sigma ^+\)&lt;/span&gt;作用到了矩阵&lt;span class="math"&gt;\(V\)&lt;/span&gt;的右侧，因此&lt;span class="math"&gt;\(V\)&lt;/span&gt;矩阵0奇异值对应的列向量被&lt;span class="math"&gt;\(\Sigma ^+\)&lt;/span&gt;矩阵作用后置零，因此得到的结果消除了零空间的影响。在&lt;code&gt;DSO&lt;/code&gt;的源码里面有两种截断方式，一种是强制的&lt;strong&gt;7自由度截断&lt;/strong&gt;，还有一种是定&lt;strong&gt;阈值截断法&lt;/strong&gt;（小于这个阈值的奇异值置零）。&lt;/p&gt;
&lt;p&gt;还有一种方式是通过世界坐标系零空间到相机坐标系零空间基转换的方式，直接求解出来相机坐标系对应的零空间的基，然后通过正交投影的方式得到零空间分量，最后减去即可。我们知道&lt;code&gt;VO&lt;/code&gt;系统有7个自由度，即如果在世界坐标系上添加扰动，那么&lt;code&gt;VO&lt;/code&gt;系统的约束仍然成立，因此世界坐标系上的零空间的基是恒定的，而相机坐标系上的零空间的基需要世界坐标系进行转换，下面的公式描述了这种转换关系：&lt;/p&gt;
&lt;div class="math"&gt;$$
Exp(\delta \xi_c) T_{cw} = (Exp(\delta \xi_w) T_{wc})^{-1} \to 
Exp(\delta \xi_c) = T_{cw} Exp(-\delta \xi_w) T_{cw}^{-1} \to
\delta \xi_c = -Ad_{T_{cw}} \times \delta \xi_w \\
$$&lt;/div&gt;
&lt;p&gt;在&lt;code&gt;DSO&lt;/code&gt;中使用世界坐标系零空间基上的正负两个扰动来推导出&lt;code&gt;DSO&lt;/code&gt;相机坐标系对应的零空间基上的扰动，这里以&lt;span class="math"&gt;\(x\)&lt;/span&gt;轴旋转&lt;span class="math"&gt;\(\phi_x\)&lt;/span&gt;和比例因子&lt;span class="math"&gt;\(s\)&lt;/span&gt;为例进行推导：&lt;/p&gt;
&lt;div class="math"&gt;$$
\delta {\xi_{w}^{\phi_x}}_p = \begin{bmatrix}
    10^{-3} &amp;amp; 0 &amp;amp; 0 &amp;amp; 0 &amp;amp; 0 &amp;amp; 0
\end{bmatrix}\\
\delta {\xi_{w}^{\phi_x}}_n = \begin{bmatrix}
    10^{-3} &amp;amp; 0 &amp;amp; 0 &amp;amp; 0 &amp;amp; 0 &amp;amp; 0
\end{bmatrix}\\
\delta \xi_c^{\phi_x} = -\frac{1}{2\times 10^{-3}}Ad_{T_{cw}} \times (\delta {\xi_{w}^{\phi_x}}_p - \delta {\xi_{w}^{\phi_x}}_n)\\
\delta {s_w}_p = 1.0001\\
\delta {s_w}_p = \frac{1}{1.0001}\\
\delta \xi_c^s = \frac{1}{2\times 10^{-4}}Log(\begin{bmatrix}
    R_{cw} &amp;amp; \delta {s_w}_p t_{cw}\\
    0 &amp;amp; 1
\end{bmatrix} T_{cw}^{-1}) - Log(\begin{bmatrix}
    R_{cw} &amp;amp; \delta {s_w}_n t_{cw}\\
    0 &amp;amp; 1
\end{bmatrix} T_{cw}^{-1})
$$&lt;/div&gt;
&lt;p&gt;在得到世界坐标系下的零空间基到相机坐标系下的零空间基转换后，通过正交投影的方式可以求得解在零空间中的分量，然后减去这个分量即可，下图是正交投影的例子，在 &lt;strong&gt;[2]&lt;/strong&gt; 中推导得出&lt;span class="math"&gt;\(P_{\mathcal{M}}=M(M^TM)^{-1}M^T\)&lt;/span&gt;，其中&lt;span class="math"&gt;\(M\)&lt;/span&gt;为零空间中的基组成的矩阵，&lt;span class="math"&gt;\(P_{\mathcal{M}}v\)&lt;/span&gt;代表的是&lt;span class="math"&gt;\(v\)&lt;/span&gt;在零空间中的分量。&lt;/p&gt;
&lt;div align="center"&gt;
&lt;img src="https://sunshanlu.github.io/sunshanlu/images/orth-proj.png" width="60%"/&gt;
&lt;/div&gt;
&lt;h3 id="44-h"&gt;4.4 如何保证H矩阵非病态？&lt;/h3&gt;
&lt;p&gt;文章 &lt;strong&gt;[3]&lt;/strong&gt; 中描述了&lt;strong&gt;病态矩阵&lt;/strong&gt;出现的原因，针对正规方程&lt;span class="math"&gt;\(H\delta x=-b\)&lt;/span&gt;这个公式，病态的&lt;span class="math"&gt;\(H\)&lt;/span&gt;矩阵由于列相关性较高导致条件数过大，从而使得解&lt;span class="math"&gt;\(\delta x\)&lt;/span&gt;收&lt;span class="math"&gt;\(b\)&lt;/span&gt;矩阵的扰动较大。出现病态矩阵的根本原因是&lt;span class="math"&gt;\(H\)&lt;/span&gt;矩阵中存在相关性较高的列，&lt;code&gt;SVD&lt;/code&gt;分解后表现在&lt;span class="math"&gt;\(\Sigma\)&lt;/span&gt;矩阵中存在极小的特征值，这与正规方程的解在零空间漂移存在一些关系，解在零空间漂移出现的原因是系统存在一定的自由度，即可以明确存在一些完全相关的列，而病态矩阵的出现是因为系统存在一些&lt;strong&gt;高度&lt;/strong&gt;相关的列（不完全相关），从而在求解&lt;span class="math"&gt;\(\delta_x -H^{-1}b\)&lt;/span&gt;时，小奇异值作用到&lt;span class="math"&gt;\(H^{-1}\)&lt;/span&gt;上后，会产生&lt;strong&gt;较大的scale作用&lt;/strong&gt;，从而难以应对&lt;span class="math"&gt;\(b\)&lt;/span&gt;矩阵扰动情况。&lt;/p&gt;
&lt;p&gt;普通病态矩阵可以通过截断&lt;code&gt;SVD&lt;/code&gt;的方案来解决，即使用&lt;code&gt;SVD&lt;/code&gt;对矩阵&lt;span class="math"&gt;\(H\)&lt;/span&gt;进行分解，然后求&lt;span class="math"&gt;\(H\)&lt;/span&gt;矩阵的逆时，可以将&lt;span class="math"&gt;\(\Sigma\)&lt;/span&gt;矩阵中的小奇异值置零，将奇异值部分完全看做矩阵的零空间零空间来处理。&lt;code&gt;DSO&lt;/code&gt;中的某个&lt;code&gt;solver&lt;/code&gt;也使用了截断&lt;code&gt;SVD&lt;/code&gt;的方式处理零空间问题，即给定某个阈值，小于这个阈值的奇异值都置零，这样可以&lt;strong&gt;同时解决&lt;/strong&gt;零空间问题和病态矩阵问题。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;DSO&lt;/code&gt;另外使用了一种对角线预处理的方式，来解决病态矩阵问题，对&lt;span class="math"&gt;\(H\)&lt;/span&gt;和&lt;span class="math"&gt;\(b\)&lt;/span&gt;矩阵进行预处理，旨在降低&lt;span class="math"&gt;\(H\)&lt;/span&gt;矩阵的条件数。对角预处理的公式描述如下：&lt;/p&gt;
&lt;div class="math"&gt;$$
W = \sqrt{diag(H) + 10 \times I}\\
H'=WHW\\
b'=Wb\\
x = -WH'^{-1}b'\\
$$&lt;/div&gt;
&lt;p&gt;其中，&lt;span class="math"&gt;\(diag(H)\)&lt;/span&gt;代表取H的对角线组成新的对角矩阵，使用这种对角预处理的手段确实可以减少矩阵H的条件数，但是比较有限。&lt;/p&gt;
&lt;h2 id="5"&gt;5. 滑动窗口中的帧和点超出限定范围怎么办？&lt;/h2&gt;
&lt;p&gt;当滑动窗口的优化完成后，就到了边缘化判断和实施边缘化操作的阶段，我们知道&lt;code&gt;DSO&lt;/code&gt;后端使用的是滑动窗口优化策略，因此为了保证优化的实时性，需要控制后端滑动窗口的规模，在&lt;code&gt;DSO&lt;/code&gt;的源码中规定了理想的后端优化的规模，即&lt;code&gt;5~7&lt;/code&gt;个关键帧和&lt;code&gt;2000&lt;/code&gt;个左右的激活点。也就是说，当后端窗口中的关键帧或者激活点超出这个范围时，&lt;code&gt;DSO&lt;/code&gt;就会使用边缘化的策略来控制滑动窗口的规模，以下是&lt;code&gt;DSO&lt;/code&gt;的点管理和帧管理策略：&lt;/p&gt;
&lt;p&gt;帧管理策略：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;从优化提供的作用角度考虑，当某一帧&lt;span class="math"&gt;\(\frac{n_{i} + n_{p}}{n_{m} + n_{d} + n_i + n_p} &amp;lt; 0.05\)&lt;/span&gt;则判断当前帧需要被边缘化，其中&lt;span class="math"&gt;\(n_{i}\)&lt;/span&gt;代表未成熟点的个数，&lt;span class="math"&gt;\(n_{p}\)&lt;/span&gt;代表该帧中激活点的数目，&lt;span class="math"&gt;\(n_m\)&lt;/span&gt;代表该帧被边缘化点的数目，&lt;span class="math"&gt;\(n_d\)&lt;/span&gt;代表该帧被丢掉点的数目，如果可以对优化起作用的点数目（&lt;span class="math"&gt;\(n_i + n_p\)&lt;/span&gt;）占所有点的比例小于5%时，则判断该帧对优化提供的作用较少，标记为边缘化；&lt;/li&gt;
&lt;li&gt;从曝光参数上考虑，计算某一帧和最新帧之间的光度参数&lt;span class="math"&gt;\(a_{ji}&amp;gt;=0.7\)&lt;/span&gt;（这里的&lt;span class="math"&gt;\(a_{ji}\)&lt;/span&gt;和初始化中的&lt;span class="math"&gt;\(a_{ji}\)&lt;/span&gt;意义相同），则认为该帧和最新帧之间的亮度变化太大（环境光变化大），则标记当前帧为边缘化；&lt;/li&gt;
&lt;li&gt;在时间轴上考虑，保证距离最新帧&lt;code&gt;newFH&lt;/code&gt;较近的3帧不被边缘化；&lt;/li&gt;
&lt;li&gt;在距离轴上考虑，提出启发式距离评价方法，距离评分公式为&lt;span class="math"&gt;\(s(I_i)=-\sqrt{d(i, 1)}\times \sum_{j\in[3,n],j\ne i}{d(i,j)^{-1}}\)&lt;/span&gt;，将评分最小的帧标记为边缘化帧；&lt;/li&gt;
&lt;li&gt;值得注意的是，每次只边缘化一帧，上面的条件从上到下依次判断，如果满足某一个条件则标记边缘化帧后退出。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;点管理策略：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;逆深度小于0或者没有参与优化的点，被丢掉drop&lt;/li&gt;
&lt;li&gt;判断某个点对后续滑动优化的作用：&lt;ul&gt;
&lt;li&gt;某一帧被边缘化后，点残差保留太少，则认定对后续滑窗优化作用不大&lt;/li&gt;
&lt;li&gt;最新帧看不到该点，则认定为对后续滑窗优化作用不大&lt;/li&gt;
&lt;li&gt;针对那些残差比较少的点，是新加入的点，认定对后续滑窗优化的作用较大（源码里面还做了单独的判断，这个判断是否不需要做，仅对作用不大的条件进行判断不就好了？）&lt;/li&gt;
&lt;li&gt;在最近两次滑窗优化中都被判断为是outlier的点，则认定对后续滑窗优化作用不大&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;判断某个点是否是内点：&lt;ul&gt;
&lt;li&gt;要求残差数量要大于某个阈值&lt;/li&gt;
&lt;li&gt;要求被判断为是&lt;code&gt;inlier&lt;/code&gt;的残差数量要大于某个阈值&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;如果判断某个点对后续滑窗优化的作用不大 或者 某个激活点的&lt;code&gt;host&lt;/code&gt;帧为待被边缘化的帧&lt;ul&gt;
&lt;li&gt;如果这个点被判断为内点&lt;ul&gt;
&lt;li&gt;判断该点的&lt;span class="math"&gt;\(H_{dd} &amp;gt; 50\)&lt;/span&gt;，&lt;span class="math"&gt;\(H_{dd}\)&lt;/span&gt;是该点在滑窗优化中计算的&lt;code&gt;Hessian&lt;/code&gt;，如果不满足则标记为丢掉；&lt;/li&gt;
&lt;li&gt;否则标记为边缘化掉；&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;如果这个点被判断为外点，则标记为丢掉。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;假设下图中，点&lt;span class="math"&gt;\(p_1\)&lt;/span&gt;需要被丢掉，点&lt;span class="math"&gt;\(p_2\)&lt;/span&gt;、&lt;span class="math"&gt;\(p_3\)&lt;/span&gt;、帧&lt;span class="math"&gt;\(kf_1\)&lt;/span&gt;需要被边缘化掉。&lt;/p&gt;
&lt;div align="center"&gt;
&lt;img src="https://sunshanlu.github.io/sunshanlu/images/swt-graph.png" width="80%"/&gt;
&lt;/div&gt;
&lt;p&gt;具体执行边缘化的流程可以由下图进行描述，首先根据要被边缘化的残差构建&lt;code&gt;Hessian&lt;/code&gt;和&lt;code&gt;b&lt;/code&gt;，值得注意的是，线性化点为&lt;span class="math"&gt;\(x_M\)&lt;/span&gt;，在实施边缘化操作之前，还需要考虑点的先验信息，需要加上确定被边缘化的状态的先验&lt;code&gt;Hessian&lt;/code&gt;和先验&lt;code&gt;b&lt;/code&gt;，然后先边缘化点，最后边缘化帧。使用&lt;code&gt;schur&lt;/code&gt;分解的方式实现，下面的图描述了这一过程：&lt;/p&gt;
&lt;div align="center"&gt;
&lt;img src="https://sunshanlu.github.io/sunshanlu/images/marg.png" width="80%"/&gt;
&lt;/div&gt;
&lt;div class="admonition important"&gt;
&lt;p class="admonition-title"&gt;为什么先边缘化点再边缘化帧？&lt;/p&gt;
&lt;p&gt;在点管理策略中可以发现，某个帧如果被判断为要被边缘化掉的话，该帧上的所有激活点都要被丢掉或者边缘化掉。并且实施边缘化时需要先边缘化掉点，然后边缘化掉帧。这么做的原因在于，防止边缘化帧时导致点与点之间的Hessian变稠密。如果点与点之间的Hessian不在稀疏，那么整个优化过程将变的极其复杂，&lt;code&gt;schur&lt;/code&gt;分解加速优化将不再成立，求解正规方程将非常缓慢。&lt;/p&gt;
&lt;/div&gt;
&lt;h2 id="reference"&gt;Reference&lt;/h2&gt;
&lt;p&gt;[1] T. -C. Dong-Si and &lt;span class="caps"&gt;A. I.&lt;/span&gt; Mourikis, “Consistency analysis for sliding-window visual odometry,” 2012 &lt;span class="caps"&gt;IEEE&lt;/span&gt; International Conference on Robotics and Automation, Saint Paul, &lt;span class="caps"&gt;MN&lt;/span&gt;, &lt;span class="caps"&gt;USA&lt;/span&gt;, 2012, pp. 5202-5209, doi: 10.1109/&lt;span class="caps"&gt;ICRA&lt;/span&gt;.2012.6225246.&lt;/p&gt;
&lt;p&gt;[2] Meyer C D. Matrix analysis and applied linear algebra[M]. Society for Industrial and Applied Mathematics, 2023.&lt;/p&gt;
&lt;p&gt;[3] &lt;a href="https://www.cnblogs.com/daniel-D/p/3219802.html"&gt;机器学习中的矩阵方法(附录A)： 病态矩阵与条件数&lt;/a&gt;&lt;/p&gt;
&lt;script type="text/javascript"&gt;if (!document.getElementById('mathjaxscript_pelican_#%@#$@#')) {
    var align = "center",
        indent = "0em",
        linebreak = "false";

    if (false) {
        align = (screen.width &lt; 768) ? "left" : align;
        indent = (screen.width &lt; 768) ? "0em" : indent;
        linebreak = (screen.width &lt; 768) ? 'true' : linebreak;
    }

    var mathjaxscript = document.createElement('script');
    mathjaxscript.id = 'mathjaxscript_pelican_#%@#$@#';
    mathjaxscript.type = 'text/javascript';
    mathjaxscript.src = 'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.3/latest.js?config=TeX-AMS-MML_HTMLorMML';

    var configscript = document.createElement('script');
    configscript.type = 'text/x-mathjax-config';
    configscript[(window.opera ? "innerHTML" : "text")] =
        "MathJax.Hub.Config({" +
        "    config: ['MMLorHTML.js']," +
        "    TeX: { extensions: ['AMSmath.js','AMSsymbols.js','noErrors.js','noUndefined.js'], equationNumbers: { autoNumber: 'none' } }," +
        "    jax: ['input/TeX','input/MathML','output/HTML-CSS']," +
        "    extensions: ['tex2jax.js','mml2jax.js','MathMenu.js','MathZoom.js']," +
        "    displayAlign: '"+ align +"'," +
        "    displayIndent: '"+ indent +"'," +
        "    showMathMenu: true," +
        "    messageStyle: 'normal'," +
        "    tex2jax: { " +
        "        inlineMath: [ ['\\\\(','\\\\)'] ], " +
        "        displayMath: [ ['$$','$$'] ]," +
        "        processEscapes: true," +
        "        preview: 'TeX'," +
        "    }, " +
        "    'HTML-CSS': { " +
        "        availableFonts: ['STIX', 'TeX']," +
        "        preferredFont: 'STIX'," +
        "        styles: { '.MathJax_Display, .MathJax .mo, .MathJax .mi, .MathJax .mn': {color: '#145402 ! important'} }," +
        "        linebreaks: { automatic: "+ linebreak +", width: '90% container' }," +
        "    }, " +
        "}); " +
        "if ('default' !== 'default') {" +
            "MathJax.Hub.Register.StartupHook('HTML-CSS Jax Ready',function () {" +
                "var VARIANT = MathJax.OutputJax['HTML-CSS'].FONTDATA.VARIANT;" +
                "VARIANT['normal'].fonts.unshift('MathJax_default');" +
                "VARIANT['bold'].fonts.unshift('MathJax_default-bold');" +
                "VARIANT['italic'].fonts.unshift('MathJax_default-italic');" +
                "VARIANT['-tex-mathit'].fonts.unshift('MathJax_default-italic');" +
            "});" +
            "MathJax.Hub.Register.StartupHook('SVG Jax Ready',function () {" +
                "var VARIANT = MathJax.OutputJax.SVG.FONTDATA.VARIANT;" +
                "VARIANT['normal'].fonts.unshift('MathJax_default');" +
                "VARIANT['bold'].fonts.unshift('MathJax_default-bold');" +
                "VARIANT['italic'].fonts.unshift('MathJax_default-italic');" +
                "VARIANT['-tex-mathit'].fonts.unshift('MathJax_default-italic');" +
            "});" +
        "}";

    (document.body || document.getElementsByTagName('head')[0]).appendChild(configscript);
    (document.body || document.getElementsByTagName('head')[0]).appendChild(mathjaxscript);
}
&lt;/script&gt;</content><category term="DSO"></category><category term="vSLAM"></category><category term="DSO"></category><category term="Robot"></category></entry><entry><title>DSO的前端跟踪</title><link href="https://sunshanlu.github.io/sunshanlu/Track-frame-in-DSO" rel="alternate"></link><published>2025-02-18T00:00:00+08:00</published><updated>2025-02-18T00:00:00+08:00</updated><author><name>孙善路-github</name></author><id>tag:sunshanlu.github.io,2025-02-18:/sunshanlu/Track-frame-in-DSO</id><summary type="html">
&lt;p&gt;DSO中的前端跟踪过程相对简单，其主要涵盖跟踪前处理、跟踪过程和跟踪后处理三部分内容。下面我会对这三部分进行拆解并对其中的细节进行介绍 …&lt;/p&gt;</summary><content type="html">
&lt;p&gt;DSO中的前端跟踪过程相对简单，其主要涵盖跟踪前处理、跟踪过程和跟踪后处理三部分内容。下面我会对这三部分进行拆解并对其中的细节进行介绍。&lt;/p&gt;
&lt;h2 id="1"&gt;1. 参考帧确定和跟踪参考点构建（跟踪前处理）&lt;/h2&gt;
&lt;p&gt;当DSO系统的初始化完成后，目前整个系统中会有&lt;strong&gt;两个关键帧&lt;/strong&gt;和&lt;strong&gt;若干激活地图点&lt;/strong&gt;，当然激活地图点的&lt;code&gt;host&lt;/code&gt;帧为初始化参考帧。和初始化过程类似，由于新来的一帧&lt;code&gt;frame&lt;/code&gt;的位姿不明确，需要一个相对鲁邦的位姿估计方法，和初始化过程一样，&lt;code&gt;DSO&lt;/code&gt;的前端跟踪部分采用的也是基于金字塔由粗至精的跟踪方法，因此需要确定一个跟踪参考帧和若干跟踪地图点。&lt;/p&gt;
&lt;p&gt;在介绍参考点构建之前，我先明确一个问题，就是&lt;code&gt;DSO&lt;/code&gt;系统会维护一个&lt;strong&gt;关键帧队列&lt;/strong&gt;，即&lt;code&gt;DSO&lt;/code&gt;中的关键帧&lt;strong&gt;滑动窗口&lt;/strong&gt;，至于这个滑窗的维护策略，我会在后续文章中进行介绍。&lt;/p&gt;
&lt;h3 id="11"&gt;1.1 金字塔参考点初构建&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;DSO&lt;/code&gt;使用了最新关键帧作为跟踪过程中的参考关键帧。为了提高跟踪鲁棒性，滑动窗口中的所有激活地图点都会投影到最新关键帧的第0层上。正如下面这个图所描述的那样，来初始化跟踪参考关键帧上第0层的像素点逆深度。&lt;/p&gt;
&lt;p&gt;在投影过程中，&lt;code&gt;DSO&lt;/code&gt;系统使用了四舍五入的方式来计算投影点的像素位置，因此存在多个点投影到同一个像素位置的情况，这里使用&lt;a href="https://sunshanlu.github.io/dso_ssl/Initialization-of-DSO#33"&gt;DSO中的初始化的3.3节&lt;/a&gt;讲到的高斯归一化积解决同像素位置不同逆深度的问题。&lt;/p&gt;
&lt;div align="center"&gt;
&lt;img src="https://sunshanlu.github.io/sunshanlu/images/point-build.png" width="70%"/&gt;
&lt;/div&gt;
&lt;p&gt;根据跟踪参考帧第0层上的像素逆深度点，向上层进行投影得到参考帧第1层的像素逆深度点，以此类推最终得到初步的跟踪参考点逆深度，这里同样会存在多对一的情况，依然使用高斯归一化积解决。&lt;/p&gt;
&lt;h3 id="12"&gt;1.2 金字塔参考点膨胀&lt;/h3&gt;
&lt;p&gt;除了使用上述投影方式，&lt;code&gt;DSO&lt;/code&gt;的源码中使用了膨胀手段来进一步增加参考点的数目，正如下图所示，紫色的位置代表某个金字塔层级上没有逆深度点的像素位置，而青色位置代表膨胀遍历位置，即遍历青色位置试图寻找一个有逆深度的像素点，然后使用取均值的方式再紫色位置膨胀出逆深度点出来。下图是&lt;code&gt;DSO&lt;/code&gt;中采用的两种膨胀遍历方式，左侧是金字塔0层和1层的膨胀方式，右侧是其他层的膨胀方式。&lt;/p&gt;
&lt;div align="center"&gt;
&lt;img src="https://sunshanlu.github.io/sunshanlu/images/point-swell.png" width="70%"/&gt;
&lt;/div&gt;
&lt;div class="admonition note"&gt;
&lt;p class="admonition-title"&gt;膨胀点原因猜测&lt;/p&gt;
&lt;p&gt;这里膨胀点的方式和我在&lt;a href="https://sunshanlu.github.io/dso_ssl/Optimization-Model-DSO"&gt;DSO中的优化模型&lt;/a&gt;文章中提到的&lt;code&gt;pattern&lt;/code&gt;类似，&lt;code&gt;pattern&lt;/code&gt;的作用是使用周围像素共享中心点的逆深度，以此来增强灰度不变形假设的可行性。个人猜测&lt;code&gt;DSO&lt;/code&gt;在这里使用膨胀的原因和&lt;code&gt;pattern&lt;/code&gt;类似，尝试以这种方式提高假设的可靠性。&lt;/p&gt;
&lt;/div&gt;
&lt;h2 id="2"&gt;2. 实施跟踪过程（构建模型并优化）&lt;/h2&gt;
&lt;p&gt;有了参考帧和待跟踪的参考点后，就可以实施前端跟踪过程了。除了使用图像金字塔从粗到精的跟踪手段外，&lt;code&gt;DSO&lt;/code&gt;还拓展了不同的运动假设来构建优化初值假设库，试图找到一个最优初值，从而加速优化过程，提高达到最优值的可能性。&lt;/p&gt;
&lt;h3 id="21"&gt;2.1 构建优化初值假设库&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;DSO&lt;/code&gt;考虑了&lt;strong&gt;恒速运动&lt;/strong&gt;、&lt;strong&gt;倍数运动&lt;/strong&gt;、&lt;strong&gt;半速运动&lt;/strong&gt;和&lt;strong&gt;相对参考帧静止&lt;/strong&gt;的模型假设，除此之外&lt;code&gt;DSO&lt;/code&gt;还做了&lt;strong&gt;小旋转运行假设&lt;/strong&gt;，所有的运动假设模型我都在下面的图片里面表示了出来，共有&lt;span class="math"&gt;\((3 * 3 * 3 - 1) * 3 + 4\)&lt;/span&gt;种假设结果。其中待优化帧的光度仿射参数&lt;span class="math"&gt;\(a_j\)&lt;/span&gt;和&lt;span class="math"&gt;\(b_j\)&lt;/span&gt;直接使用的上一帧光度仿射参数&lt;span class="math"&gt;\(a_{last}\)&lt;/span&gt;和&lt;span class="math"&gt;\(b_{last}\)&lt;/span&gt;作为初值，这是因为自然条件下，相邻帧之间的光度仿射参数变化非常小。&lt;/p&gt;
&lt;div align="center"&gt;
&lt;img src="https://sunshanlu.github.io/sunshanlu/images/pose-assume.png" width="95%"/&gt;
&lt;/div&gt;
&lt;h3 id="22"&gt;2.2 基于某种假设的跟踪尝试&lt;/h3&gt;
&lt;h4 id="221"&gt;2.2.1 跟踪模型&lt;/h4&gt;
&lt;p&gt;某种位姿假设初值&lt;span class="math"&gt;\(T_{cr}\)&lt;/span&gt;确定后，&lt;code&gt;DSO&lt;/code&gt;前端跟踪部分会使用下面的公式构造优化目标函数。&lt;/p&gt;
&lt;div class="math"&gt;$$
E_{l}=\sum_{p_i \in ref}{||r_k||_{\gamma}}
\\
r_k=I_j[p_j]-\frac{t_j e^{a_i}}{t_i e^{a_j}}I_i[p_i]-(b_j - \frac{t_j e^{a_i}}{t_i e^{a_j}} b_i)
$$&lt;/div&gt;
&lt;p&gt;其中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;span class="math"&gt;\(p_i\)&lt;/span&gt;为跟踪参考关键帧&lt;code&gt;ref frame&lt;/code&gt;&lt;span class="math"&gt;\(l\)&lt;/span&gt;层上，带有逆深度的参考点。&lt;/li&gt;
&lt;li&gt;&lt;span class="math"&gt;\(p_j\)&lt;/span&gt;为使用当前优化参数投影到当前帧&lt;span class="math"&gt;\(l\)&lt;/span&gt;层上的点。&lt;/li&gt;
&lt;li&gt;&lt;span class="math"&gt;\(E_l\)&lt;/span&gt;为&lt;span class="math"&gt;\(l\)&lt;/span&gt;层金字塔上的能量值，同样也是优化目标函数。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;与&lt;a href="https://sunshanlu.github.io/dso_ssl/Optimization-Model-DSO"&gt;DSO中的优化模型&lt;/a&gt;文章中的模型对比发现，跟踪模型并没有使用&lt;code&gt;pattern&lt;/code&gt;，这部分也印证了我在1.2小节中的猜测。&lt;/p&gt;
&lt;h4 id="222"&gt;2.2.2 雅可比矩阵构建&lt;/h4&gt;
&lt;p&gt;虽然模型没有使用&lt;code&gt;pattern&lt;/code&gt;，但是残差部分是一致的，因此雅可比矩阵的构建也完全一致。在前端位姿估计部分，不涉及地图点逆深度的优化问题，因此待优化量主要有当前帧相对于跟踪参考帧的位姿&lt;span class="math"&gt;\(T_{cr}\)&lt;/span&gt;以及当前帧的光度仿射参数&lt;span class="math"&gt;\(a_j\)&lt;/span&gt;和&lt;span class="math"&gt;\(b_j\)&lt;/span&gt;，对于&lt;span class="math"&gt;\(a_j\)&lt;/span&gt;和&lt;span class="math"&gt;\(b_j\)&lt;/span&gt;来讲，它们的求导是平凡的，而相对位姿&lt;span class="math"&gt;\(T_{cr}\)&lt;/span&gt;的求导，我在文章&lt;a href="https://sunshanlu.github.io/dso_ssl/Optimization-Model-DSO"&gt;DSO中的优化模型&lt;/a&gt;中做了详细描述，这里只写出相关结论。&lt;/p&gt;
&lt;div class="math"&gt;$$
\begin{align*}
    \frac{\partial{r_k}}{\partial{\xi_{ji}}}&amp;amp;=
    \frac{1}{P_Z'} 
    \begin{bmatrix} d_x&amp;amp;d_y \end{bmatrix} 
    \begin{bmatrix} f_x &amp;amp; 0 \\ 0 &amp;amp; f_y \end{bmatrix}
    \begin{bmatrix}
        1 &amp;amp; 0 &amp;amp; -\frac{P_X'}{P_Z'} \\
        0 &amp;amp; 1 &amp;amp; -\frac{P_Y'}{P_Z'} \\
    \end{bmatrix}
    \begin{bmatrix}
        d_{pi}&amp;amp;-P'^{\wedge}
    \end{bmatrix}\\ &amp;amp;=
    \begin{bmatrix} d_xf_x&amp;amp;d_yf_y\end{bmatrix}
    \begin{bmatrix}
        \frac{d_{pi}}{P_{Z}'} &amp;amp; 0 &amp;amp; -\frac{d_{pi}}{P_{Z}'}\frac{P_{X}'}{P_{Z}'} &amp;amp; -\frac{P_{X}'P_{Y}'}{P_{Z}'^2} &amp;amp; 1+\frac{P_{X}^{2}}{P_{Z}^{2}} &amp;amp; -\frac{P_{Y}'}{P_{Z}'} 
        \\
        0 &amp;amp; \frac{d_{pi}}{P_{Z}'} &amp;amp; -\frac{d_{pi}}{P_{Z}'}\frac{P_{Y}'}{P_{Z}'} &amp;amp; -1-\frac{P_{Y}^{2}}{P_{Z}^{2}} &amp;amp; \frac{P_{X}'P_{Y}'}{P_{Z}^{2}} &amp;amp; \frac{P_{X}'}{P_{Z}'}
    \end{bmatrix}\\
    \frac{\partial r_k}{\partial a_j}&amp;amp;=\frac{t_j e^{a_i}}{t_i e^{a_j}}(b_i-I_i[p_i])\\
    \frac{\partial r_k}{\partial b_j}&amp;amp;=-1
\end{align*}
$$&lt;/div&gt;
&lt;div class="admonition important"&gt;
&lt;p class="admonition-title"&gt;跟踪部分的细节性问题&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;计算残差时，采用动态阈值调节的手段，即先确定一个相对可控的残差阈值，然后根据内点比例适当地调整倍率来确保有足够的内点进行优化。当这个可控阈值调整过后，说明某层优化的残差相对较大，为了保证收敛，会对这一层进行再次优化。（调整过阈值的层会优化两次，怀疑内点比例低）&lt;/li&gt;
&lt;li&gt;当某一层优化收敛后，会判断最终的优化RMSE和所有尝试的最小RMSE * 1.5做判断，如果大于1.5倍的最小值，则认定这次尝试直接失败，退出这次尝试，以节省时间，源码中维护这一行为的变量为&lt;code&gt;achieveRes&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;如何判断某次尝试失败与否？&lt;code&gt;DSO&lt;/code&gt;从光度仿射参数的相对量和绝对量上判断是否优化成功，&lt;span class="math"&gt;\(a_j &amp;gt; 1.2\)&lt;/span&gt; 或者 &lt;span class="math"&gt;\(b_j &amp;gt; 200\)&lt;/span&gt;则失败，&lt;span class="math"&gt;\(a_{ji}&amp;gt;1.5\)&lt;/span&gt;或者&lt;span class="math"&gt;\(b_{ji}&amp;gt;200\)&lt;/span&gt;则认为失败。&lt;/li&gt;
&lt;li&gt;如何提前退出尝试？从2.1节的介绍中可以发现，一共有&lt;span class="math"&gt;\((3 * 3 * 3 - 1) * 3 + 4 = 82\)&lt;/span&gt;次尝试，&lt;code&gt;DSO&lt;/code&gt;为了避免多次尝试导致的时间浪费问题，会使用上一帧优化能量值 * 1.5 作为退出尝试的条件，当然这样就可能导致动态阈值一直增大的情况。&lt;code&gt;DSO&lt;/code&gt;在&lt;strong&gt;关键帧判断&lt;/strong&gt;中对这个问题做了约束，保证了动态阈值增长的上限（&lt;strong&gt;见本章第3节&lt;/strong&gt;）。&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;h2 id="3"&gt;3. 关键帧判断（跟踪后处理）&lt;/h2&gt;
&lt;p&gt;在关键帧的判断中，除了通常考虑的&lt;strong&gt;时间间隔&lt;/strong&gt;和&lt;strong&gt;空间间隔&lt;/strong&gt;外，&lt;code&gt;DSO&lt;/code&gt;还考虑了&lt;strong&gt;光度变化&lt;/strong&gt;和用来提前退出尝试的跟踪&lt;strong&gt;能量阈值&lt;/strong&gt;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;DSO&lt;/code&gt;中考虑的时间间隔相对普通，会人为限定一个创建关键帧的最大时间间隔，在&lt;code&gt;DSO&lt;/code&gt;的源码中默认是&lt;strong&gt;不开启&lt;/strong&gt;这个配置的。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DSO&lt;/code&gt;考虑的空间间隔相对新颖，不是所谓的真实距离，而是考虑了某些位姿间隔条件下的&lt;strong&gt;平均光流&lt;/strong&gt;大小（金字塔第0层）。我们知道两张图像的平均光流大小可以近似的代表两张图像之间的视差，光流越大，视差越大，两张图片的重叠部分越少，反之亦然。&lt;code&gt;DSO&lt;/code&gt;使用&lt;span class="math"&gt;\(T_{cr}\)&lt;/span&gt;中的&lt;span class="math"&gt;\(t_{cr}\)&lt;/span&gt;、&lt;span class="math"&gt;\(-t_{cr}\)&lt;/span&gt;、&lt;span class="math"&gt;\(T_{cr}\)&lt;/span&gt;和&lt;span class="math"&gt;\([R_{cr},t_{cr}]\)&lt;/span&gt;四种位姿导致的平均光流，配合自定义权重判断是否需要创建关键帧。使用四种位姿的平均光流而不是仅仅&lt;span class="math"&gt;\(T_{cr}\)&lt;/span&gt;造成的平均光流，我&lt;strong&gt;猜测&lt;/strong&gt;可能是因为使用四种位姿可以在一定程度上模拟之后一段时间内的运动状态，从而做到提前判断。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DSO&lt;/code&gt;使用了基于光度仿射参数的直接法，因此当相对仿射参数&lt;span class="math"&gt;\(a_{ji}\)&lt;/span&gt;变化较大时，代表着当前帧和参考关键帧之间的&lt;strong&gt;光照条件&lt;/strong&gt;发生了相对较大的变化，因此需要及时创建关键帧防止跟踪丢失情况的发生。&lt;/li&gt;
&lt;li&gt;在第2小节的末尾，做了一些跟踪细节方面的讨论，提到了提前退出尝试的动态阈值可能会&lt;strong&gt;持续增大&lt;/strong&gt;，从而导致阈值限定意义失效，&lt;code&gt;DSO&lt;/code&gt;为了防止这种情况的发生，会提前记录基于当前参考关键帧第一次跟踪成功的能量值，如果当前帧跟踪阈值超过了这个值的2倍，则认定需要创建关键帧了，这么做相当于对动态阈值的上限做了规定。&lt;/li&gt;
&lt;/ul&gt;
&lt;script type="text/javascript"&gt;if (!document.getElementById('mathjaxscript_pelican_#%@#$@#')) {
    var align = "center",
        indent = "0em",
        linebreak = "false";

    if (false) {
        align = (screen.width &lt; 768) ? "left" : align;
        indent = (screen.width &lt; 768) ? "0em" : indent;
        linebreak = (screen.width &lt; 768) ? 'true' : linebreak;
    }

    var mathjaxscript = document.createElement('script');
    mathjaxscript.id = 'mathjaxscript_pelican_#%@#$@#';
    mathjaxscript.type = 'text/javascript';
    mathjaxscript.src = 'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.3/latest.js?config=TeX-AMS-MML_HTMLorMML';

    var configscript = document.createElement('script');
    configscript.type = 'text/x-mathjax-config';
    configscript[(window.opera ? "innerHTML" : "text")] =
        "MathJax.Hub.Config({" +
        "    config: ['MMLorHTML.js']," +
        "    TeX: { extensions: ['AMSmath.js','AMSsymbols.js','noErrors.js','noUndefined.js'], equationNumbers: { autoNumber: 'none' } }," +
        "    jax: ['input/TeX','input/MathML','output/HTML-CSS']," +
        "    extensions: ['tex2jax.js','mml2jax.js','MathMenu.js','MathZoom.js']," +
        "    displayAlign: '"+ align +"'," +
        "    displayIndent: '"+ indent +"'," +
        "    showMathMenu: true," +
        "    messageStyle: 'normal'," +
        "    tex2jax: { " +
        "        inlineMath: [ ['\\\\(','\\\\)'] ], " +
        "        displayMath: [ ['$$','$$'] ]," +
        "        processEscapes: true," +
        "        preview: 'TeX'," +
        "    }, " +
        "    'HTML-CSS': { " +
        "        availableFonts: ['STIX', 'TeX']," +
        "        preferredFont: 'STIX'," +
        "        styles: { '.MathJax_Display, .MathJax .mo, .MathJax .mi, .MathJax .mn': {color: '#145402 ! important'} }," +
        "        linebreaks: { automatic: "+ linebreak +", width: '90% container' }," +
        "    }, " +
        "}); " +
        "if ('default' !== 'default') {" +
            "MathJax.Hub.Register.StartupHook('HTML-CSS Jax Ready',function () {" +
                "var VARIANT = MathJax.OutputJax['HTML-CSS'].FONTDATA.VARIANT;" +
                "VARIANT['normal'].fonts.unshift('MathJax_default');" +
                "VARIANT['bold'].fonts.unshift('MathJax_default-bold');" +
                "VARIANT['italic'].fonts.unshift('MathJax_default-italic');" +
                "VARIANT['-tex-mathit'].fonts.unshift('MathJax_default-italic');" +
            "});" +
            "MathJax.Hub.Register.StartupHook('SVG Jax Ready',function () {" +
                "var VARIANT = MathJax.OutputJax.SVG.FONTDATA.VARIANT;" +
                "VARIANT['normal'].fonts.unshift('MathJax_default');" +
                "VARIANT['bold'].fonts.unshift('MathJax_default-bold');" +
                "VARIANT['italic'].fonts.unshift('MathJax_default-italic');" +
                "VARIANT['-tex-mathit'].fonts.unshift('MathJax_default-italic');" +
            "});" +
        "}";

    (document.body || document.getElementsByTagName('head')[0]).appendChild(configscript);
    (document.body || document.getElementsByTagName('head')[0]).appendChild(mathjaxscript);
}
&lt;/script&gt;</content><category term="DSO"></category><category term="vSLAM"></category><category term="DSO"></category><category term="Robot"></category></entry><entry><title>DSO中的初始化</title><link href="https://sunshanlu.github.io/sunshanlu/Initialization-of-DSO" rel="alternate"></link><published>2025-01-16T00:00:00+08:00</published><updated>2025-01-16T00:00:00+08:00</updated><author><name>孙善路-github</name></author><id>tag:sunshanlu.github.io,2025-01-16:/sunshanlu/Initialization-of-DSO</id><summary type="html">
&lt;p&gt;在这篇文章中，我会对&lt;code&gt;DSO&lt;/code&gt;初始化的过程和初始化中的一些&lt;strong&gt;要点&lt;/strong&gt;进行讲解说明。作为一个单目视觉&lt;code&gt;VO&lt;/code&gt;系统，&lt;code&gt;DSO&lt;/code&gt;也采用了多帧 …&lt;/p&gt;</summary><content type="html">
&lt;p&gt;在这篇文章中，我会对&lt;code&gt;DSO&lt;/code&gt;初始化的过程和初始化中的一些&lt;strong&gt;要点&lt;/strong&gt;进行讲解说明。作为一个单目视觉&lt;code&gt;VO&lt;/code&gt;系统，&lt;code&gt;DSO&lt;/code&gt;也采用了多帧初始化的策略，即首先在初始化参考帧上进行提点操作，然后根据不同的帧来优化初始化参考帧上点的逆深度估计，初始化的优化模型是在&lt;a href="https://sunshanlu.github.io/dso_ssl/Optimization-Model-DSO"&gt;DSO中的优化模型&lt;/a&gt;文章中提到的优化模型基础上，考虑了&lt;strong&gt;逆深度的连续性&lt;/strong&gt;和&lt;strong&gt;平移距离&lt;/strong&gt;构建的，并且使用了&lt;code&gt;Schur&lt;/code&gt;&lt;strong&gt;消元&lt;/strong&gt;来加速初始化中的优化过程。&lt;/p&gt;
&lt;p&gt;在&lt;code&gt;DSO&lt;/code&gt;的初始化过程中还使用了图像金字塔（从粗到精的优化）来提高鲁棒性，并构建点的同层相邻点和上层父点之间的联系，实现点的逆深度传递。除此之外，&lt;code&gt;DSO&lt;/code&gt;在初始化过程中还对相对光度仿射参数进行了处理，下面是我对这些要点的详细说明：&lt;/p&gt;
&lt;h2 id="1"&gt;1. 初始化中对仿射参数的处理&lt;/h2&gt;
&lt;p&gt;我在&lt;a href="https://sunshanlu.github.io/dso_ssl/Optimization-Model-DSO"&gt;DSO中的优化模型&lt;/a&gt;文章中提到了&lt;code&gt;DSO&lt;/code&gt;的优化残差，可以使用下面的公式进行描述。&lt;/p&gt;
&lt;div class="math"&gt;$$
r_k = (I_j(p') - b_j) - \frac{t_je^{a_j}}{t_ie^{a_i}}(I_i(p) - b_i)
$$&lt;/div&gt;
&lt;p&gt;可以看到，公式中涉及到了参考帧&lt;span class="math"&gt;\(i\)&lt;/span&gt;待求解帧&lt;span class="math"&gt;\(j\)&lt;/span&gt;的绝对光度仿射参数，为了简化这部分对求导的影响，&lt;code&gt;DSO&lt;/code&gt;构造了相对仿射参数来代替绝对仿射参数，即令&lt;span class="math"&gt;\(e^{aji}=\frac{t_je^{a_j}}{t_ie^{a_i}}\)&lt;/span&gt;，&lt;span class="math"&gt;\(b_{ji}=b_j - a_{ji}b_i\)&lt;/span&gt;得公式(2)：&lt;/p&gt;
&lt;div class="math"&gt;$$
r_k = I_j(p')-e^{a_{ji}}I_i(p)-b_{ji}
$$&lt;/div&gt;
&lt;div class="admonition note"&gt;
&lt;p class="admonition-title"&gt;为什么不使用&lt;span class="math"&gt;\(a_{ji}\)&lt;/span&gt;而是使用&lt;span class="math"&gt;\(e^{a_{ji}}\)&lt;/span&gt;？&lt;/p&gt;
&lt;p&gt;首先，如果使用&lt;span class="math"&gt;\(a_{ji}\)&lt;/span&gt;来代替&lt;span class="math"&gt;\(\frac{t_je^{a_j}}{t_ie^{a_i}}\)&lt;/span&gt;话，就会要求&lt;span class="math"&gt;\(a_{ji}&amp;gt;0\)&lt;/span&gt;的，因此模型会从原来的无约束优化问题变成了部分变量的有约束优化问题，会增加整个优化过程中的难度。因此为了避免这个问题，&lt;code&gt;DSO&lt;/code&gt;中使用了&lt;span class="math"&gt;\(e^{a_{ji}}\)&lt;/span&gt;来代替&lt;span class="math"&gt;\(\frac{t_je^{a_j}}{t_ie^{a_i}}\)&lt;/span&gt;而不是&lt;span class="math"&gt;\(a_{ji}\)&lt;/span&gt;。&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;为了解决这个优化问题，除了&lt;a href="https://sunshanlu.github.io/dso_ssl/Optimization-Model-DSO"&gt;DSO中的优化模型&lt;/a&gt;文章中求解的一些雅可比矩阵外，还需要求解残差对相对光度仿射参数的雅可比矩阵，从构建的残差公式中不难得到：&lt;/p&gt;
&lt;div class="math"&gt;$$
\frac{\partial r_k}{\partial a_{ji}} = e^{a_{ji}}I_i(p)\\
\frac{\partial r_k}{\partial b_{ji}} = -1
$$&lt;/div&gt;
&lt;h2 id="2"&gt;2. 初始化优化模型中的惩罚项&lt;/h2&gt;
&lt;p&gt;接下来，我们需要讨论的是，为什么&lt;code&gt;DSO&lt;/code&gt;在初始化过程中需要一个相对大并且准确估计的相对平移距离&lt;span class="math"&gt;\(t_{ji}\)&lt;/span&gt;。现在看下图的一个位姿估计过程，对比大平移距离&lt;span class="math"&gt;\(t_{ji}\)&lt;/span&gt;和小平移距离&lt;span class="math"&gt;\(t_{ji}\)&lt;/span&gt;的两张优化结果。可以发现，如果投影到&lt;span class="math"&gt;\(j\)&lt;/span&gt;帧上的像素点存在1个像素的误差，大平移距离对应的点&lt;span class="math"&gt;\(p_i\)&lt;/span&gt;的深度变化可以由图像左侧的绿色大括号表示，而小平移距离对应的点&lt;span class="math"&gt;\(p_i\)&lt;/span&gt;的深度变化由右侧的绿色大括号表示。可以看出，小平移距离估计的点&lt;span class="math"&gt;\(p_i\)&lt;/span&gt;的深度受投影像素误差影响比较大。因此&lt;code&gt;DSO&lt;/code&gt;才会针对平移距离构建不同的优化目标函数，试图找到一个准确且足够大的&lt;span class="math"&gt;\(t_{ji}\)&lt;/span&gt;，来保证初始化参考帧上的点&lt;span class="math"&gt;\(p_i\)&lt;/span&gt;逆深度的稳定性和准确性。&lt;/p&gt;
&lt;div align="center"&gt;
&lt;img src="https://sunshanlu.github.io/sunshanlu/images/triangulation.png" width="70%"/&gt;
&lt;/div&gt;
&lt;h3 id="21"&gt;2.1 平移距离不足时的惩罚项&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;DSO&lt;/code&gt;在初始化过程中还考虑了平移距离和逆深度连续性的影响，&lt;code&gt;DSO&lt;/code&gt;认为在初始化过程中， 输入的普通帧&lt;span class="math"&gt;\(j\)&lt;/span&gt;和初始化参考帧&lt;span class="math"&gt;\(i\)&lt;/span&gt;的平移距离应该达到某个阈值才认为&lt;span class="math"&gt;\(j\)&lt;/span&gt;帧对参考帧上点逆深度起作用，为了确保在优化过程中得到的相对平移距离足够可靠，&lt;code&gt;DSO&lt;/code&gt;在未达到足够的平移距离之前，会对平移距离添加惩罚项，除此之外还会对点的逆深度做一个尺度约束，因此在平移距离不够的条件下，&lt;code&gt;DSO&lt;/code&gt;构造的优化目标函数如下：&lt;/p&gt;
&lt;div class="math"&gt;$$
E_f=\sum_{p_i\in R_f}{\sum_{p\in \mathcal{N}_i(p_i)}{||I_j(p')-e^a_{ji}I_{R_f}(p)-b_{ji}||_{\gamma}}}+E_l\\
E_l=\frac{\alpha_W}{2} \sum_{p_i\in R_f}{((d_{pi}-1)^2+||t_{ji}||^2_2)}
$$&lt;/div&gt;
&lt;p&gt;其中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;span class="math"&gt;\(E_f\)&lt;/span&gt;部分为平移量不足条件下的优化目标函数；&lt;/li&gt;
&lt;li&gt;&lt;span class="math"&gt;\(E_l\)&lt;/span&gt;部分包含了逆深度尺度惩罚项&lt;span class="math"&gt;\((d_{pi} - 1)^2\)&lt;/span&gt;和相对平距离的惩罚项&lt;span class="math"&gt;\(||t_{ji}||^2_2\)&lt;/span&gt;；&lt;/li&gt;
&lt;li&gt;&lt;span class="math"&gt;\(\alpha_W\)&lt;/span&gt;代表调节正则化项部分的超参数，从而在一定程度上改变优化的侧重方向；&lt;/li&gt;
&lt;li&gt;&lt;span class="math"&gt;\(R_f\)&lt;/span&gt;代表是初始化参考帧&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;针对&lt;span class="math"&gt;\(E_l\)&lt;/span&gt;的惩罚项部分，&lt;span class="math"&gt;\(E_l^{p_j}\)&lt;/span&gt;的整个组成部分相对简单，可以直接求解惩罚项的雅可比矩阵&lt;span class="math"&gt;\(J_l\)&lt;/span&gt;和海塞矩阵&lt;span class="math"&gt;\(H_l\)&lt;/span&gt;，而不必使用高斯牛顿法利用残差项的雅可比变换得到：&lt;/p&gt;
&lt;div class="math"&gt;$$
\frac{\partial E_l^{p_j}}{\partial x} = J_{p_i}=\begin{bmatrix}
    t_{ji}^T &amp;amp; 0_{1 \times 3} &amp;amp; 0 &amp;amp; 0 &amp;amp; d_{pi}-1
\end{bmatrix}
\\
\frac{\partial^2 E_l^{p_j}}{\partial x^2} = H_{pi}=
\begin{bmatrix}
    \begin{bmatrix}
        I_{3\times 3} &amp;amp; \mathbf{0_{3\times3}}\\
        \mathbf{0_{3\times3}} &amp;amp; \mathbf{0_{3\times3}}
    \end{bmatrix} &amp;amp; \mathbf{0_{6 \times 1}} &amp;amp; \mathbf{0_{6 \times 1}} &amp;amp; \mathbf{0_{6 \times 1}}\\
    \mathbf{0_{1 \times 6}} &amp;amp; 0 &amp;amp; 0 &amp;amp; 0 \\
    \mathbf{0_{1 \times 6}} &amp;amp; 0 &amp;amp; 0 &amp;amp; 0 \\
    \mathbf{0_{1 \times 6}} &amp;amp; 0 &amp;amp; 0 &amp;amp; 1 \\
\end{bmatrix} 
$$&lt;/div&gt;
&lt;div class="admonition important"&gt;
&lt;p class="admonition-title"&gt;&lt;span class="math"&gt;\(E_l^{p_j}\)&lt;/span&gt;和&lt;span class="math"&gt;\(E_l\)&lt;/span&gt;的区别&lt;/p&gt;
&lt;p&gt;值得注意的是，&lt;span class="math"&gt;\(E_l^{p_j}\)&lt;/span&gt;的能量值，代表是初始化参考关键帧&lt;span class="math"&gt;\(R_f\)&lt;/span&gt;上的点&lt;span class="math"&gt;\(p_i\)&lt;/span&gt;向&lt;span class="math"&gt;\(p_j\)&lt;/span&gt;部分投影这一约束贡献的系统能量值，或者说是贡献的优化目标函数值。惩罚项的海塞矩阵&lt;span class="math"&gt;\(H_l\)&lt;/span&gt;可以由所有的&lt;span class="math"&gt;\(H_l^{p_j}\)&lt;/span&gt;组合而成，即相同的约束部分可以加和，而不用的约束部分需要拓展组成一个大矩阵&lt;span class="math"&gt;\(H_l\)&lt;/span&gt;，当然，雅可比矩阵也是如此。&lt;/p&gt;
&lt;/div&gt;
&lt;h3 id="22"&gt;2.2 平移距离足够时的惩罚项&lt;/h3&gt;
&lt;p&gt;当平移距离足够时，&lt;code&gt;DSO&lt;/code&gt;认为初始化参考帧&lt;span class="math"&gt;\(i\)&lt;/span&gt;中的像素点&lt;span class="math"&gt;\(p_i\)&lt;/span&gt;的逆深度会有一个相对稳定、准确且尺度一致（因为使用了&lt;span class="math"&gt;\((d_{pi}-1)^2\)&lt;/span&gt;作为尺度惩罚项）的值，这时，&lt;code&gt;DSO&lt;/code&gt;会去掉对平移距离的惩罚项，同时对逆深度进行期望约束，这里的期望值&lt;span class="math"&gt;\(iR\)&lt;/span&gt;考虑了当前点&lt;span class="math"&gt;\(p_i\)&lt;/span&gt;和周围点的逆深度关系，即逆深度的平滑性，得到的优化目标函数如下：&lt;/p&gt;
&lt;div class="math"&gt;$$
E_f=\sum_{p_i\in R_f}{\sum_{p\in \mathcal{N}_i(p_i)}{||I_j(p')-e^a_{ji}I_{R_f}(p)-b_{ji}||_{\gamma}}}+E_l\\
E_l=\frac{\alpha}{2} \sum_{p_i\in R_f}{(d_{pi}-iR_{i})^2}
$$&lt;/div&gt;
&lt;p&gt;其中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;span class="math"&gt;\(E_f\)&lt;/span&gt;部分为平移量足够条件下的优化目标函数；&lt;/li&gt;
&lt;li&gt;&lt;span class="math"&gt;\(E_l\)&lt;/span&gt;部分包含了逆深度期望惩罚项&lt;span class="math"&gt;\((d_{pi} - iR_i)^2\)&lt;/span&gt;，充分考虑了周围点逆深度的平滑性；&lt;/li&gt;
&lt;li&gt;&lt;span class="math"&gt;\(\alpha\)&lt;/span&gt;代表调节正则化项部分的超参数，从而在一定程度上改变优化的侧重方向；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;针对&lt;span class="math"&gt;\(E_l\)&lt;/span&gt;的惩罚项部分，求得其雅可比矩阵和海塞矩阵如下：&lt;/p&gt;
&lt;div class="math"&gt;$$
\frac{\partial{E_l^{p_j}}}{\partial{x}}=J_l^{p_j}=
\begin{bmatrix}
    \mathbf{0_{1\times6}} &amp;amp; 0 &amp;amp; 0 &amp;amp; d_{pi}-iR
\end{bmatrix}
\\
\frac{\partial^2{E_l^{p_j}}}{\partial{x^2}} = H_l^{p_j} = 
\begin{bmatrix}
    \mathbf{0_{6\times6}} &amp;amp; \mathbf{0_{6 \times 1}} &amp;amp; \mathbf{0_{6 \times 1}} &amp;amp; \mathbf{0_{6 \times 1}}\\
    \mathbf{0_{1\times6}} &amp;amp; 0 &amp;amp; 0 &amp;amp; 0 \\
    \mathbf{0_{1\times6}} &amp;amp; 0 &amp;amp; 0 &amp;amp; 0 \\
    \mathbf{0_{1\times6}} &amp;amp; 0 &amp;amp; 0 &amp;amp; 1 \\
\end{bmatrix}
$$&lt;/div&gt;
&lt;h2 id="3"&gt;3. 基于金字塔的逆深度传递&lt;/h2&gt;
&lt;p&gt;为了能够方便的表述逆深度传递的重要性，在这部分我会先在3.1小节中梳理一下&lt;code&gt;DSO&lt;/code&gt;&lt;strong&gt;初始化流程&lt;/strong&gt;，然后在3.2小节中重点讲解基于金字塔和相邻点的&lt;strong&gt;逆深度传递过程&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id="31-dso"&gt;3.1 DSO初始化过程&lt;/h3&gt;
&lt;p&gt;下图中，使用虚线框标注出来的部分是&lt;code&gt;DSO&lt;/code&gt;初始化器的主要工作流程，值得注意的是，在&lt;a href="https://sunshanlu.github.io/dso_ssl/Point-select-in-DSO"&gt;初始化点选之后&lt;/a&gt;，参考帧&lt;span class="math"&gt;\(R_f\)&lt;/span&gt;上会得到点&lt;span class="math"&gt;\(p_i\in R_f\)&lt;/span&gt;，这些点&lt;span class="math"&gt;\(p_i\)&lt;/span&gt;会分布在不同的金字塔层级上，为了方便后续这些点的逆深度传递，&lt;code&gt;DSO&lt;/code&gt;构造了这些点的&lt;strong&gt;同层相邻点&lt;/strong&gt;和&lt;strong&gt;上层父点&lt;/strong&gt;之间的关系。&lt;/p&gt;
&lt;div align="center"&gt;
&lt;img alt="init-process" src="https://sunshanlu.github.io/sunshanlu/images/init-process.png" width="80%"/&gt;
&lt;/div&gt;
&lt;h3 id="32"&gt;3.2 逆深度传递&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;DSO&lt;/code&gt;的初始化器构造了同层相邻点和上层父点之间的关系，这样会比较方便的执行下面的三种操作：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;从上层到下层优化过程中，上层父点会向下层子点提供&lt;strong&gt;逆深度初值&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;当优化完成后，下层子点会将充分优化的逆深度初值向上层父点传递，以&lt;strong&gt;修正父点逆深度值&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;由于&lt;code&gt;DSO&lt;/code&gt;初始化器在优化目标函数构建中，考虑了相邻点逆深度连续性，这部分连续性的考虑是由相邻点关系实现的。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在每一步优化结束后，&lt;code&gt;DSO&lt;/code&gt;初始化器会更新逆深度值，同样会更新逆深度期望值，期望值的更新考虑了相邻点之间的连续性关系，下面的公式描述了逆深度期望的更新策略：&lt;/p&gt;
&lt;div class="math"&gt;$$
iR_i=(1-\beta)\times{d_{pi}}_{new}+\beta\times{iR_{mid}^i}\\
iR_{mid}^i={Median}(neighbors(p_i))
$$&lt;/div&gt;
&lt;p&gt;其中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;span class="math"&gt;\(iR_i\)&lt;/span&gt;为点&lt;span class="math"&gt;\(p_i\)&lt;/span&gt;逆深度的更新期望；&lt;/li&gt;
&lt;li&gt;&lt;span class="math"&gt;\({d_{pi}}_{new}\)&lt;/span&gt;为点&lt;span class="math"&gt;\(p_i\)&lt;/span&gt;逆深度的优化更新值；&lt;/li&gt;
&lt;li&gt;&lt;span class="math"&gt;\(\beta\)&lt;/span&gt;为相邻点逆深度连续性置信度，在源码为&lt;code&gt;0.8&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;&lt;span class="math"&gt;\(iR_{mid}^i\)&lt;/span&gt;为点&lt;span class="math"&gt;\(p_i\)&lt;/span&gt;邻居的逆深度期望中值；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;DSO&lt;/code&gt;初始化器使用同层金字塔&lt;code&gt;K&lt;/code&gt;近邻的方式构建同层点和邻居点之间的关系，使用向上层金字塔投影寻找最近邻的方式构建与上层父点之间的关系。&lt;/p&gt;
&lt;h3 id="33"&gt;3.3 高斯归一化积&lt;/h3&gt;
&lt;p&gt;考虑父点向子点传递逆深度过程：除了第一次优化过程外，子点都会有一个&lt;strong&gt;相对有效&lt;/strong&gt;的逆深度优化值，这时父点还会向子点提供一个逆深度。&lt;code&gt;DSO&lt;/code&gt;初始化器使用了高斯归一化来耦合这两个逆深度值，&lt;strong&gt;高斯归一化积&lt;/strong&gt;的数学描述如下：&lt;/p&gt;
&lt;div class="math"&gt;$$
\mu_{c} H_{c}=\sum_{i=0}^N \mu_iH_i\\
H_c=\sum_{i=0}^N H_i
$$&lt;/div&gt;
&lt;p&gt;父点向子点传递时，子点不仅仅是逆深度值&lt;span class="math"&gt;\(d_{pi}\)&lt;/span&gt;发生了改变，子点的逆深度期望&lt;span class="math"&gt;\(iR\)&lt;/span&gt;同样发生了改变，这个传递过程可以由下面的流程图进行描述：&lt;/p&gt;
&lt;div align="center"&gt;
&lt;img alt="up-down" src="https://sunshanlu.github.io/sunshanlu/images/up-down.png" width="100%"/&gt;
&lt;/div&gt;
&lt;p&gt;3.2中的公式同样描述了，流程图中的&lt;strong&gt;考虑相邻点&lt;span class="math"&gt;\(iR\)&lt;/span&gt;的连续性&lt;/strong&gt;操作。通过这样的操作，可以人为的构造出一个考虑了连续性影响的逆深度期望值出来，从而能够完成后续初始化优化目标函数（会涉及点&lt;span class="math"&gt;\(p_i\)&lt;/span&gt;的逆深度期望&lt;span class="math"&gt;\(iR_i\)&lt;/span&gt;）的构建。&lt;/p&gt;
&lt;p&gt;考虑子点向父点的逆深度传递过程：在3.2小节的描述中，父子点之间的关系构造是通过投影和最近邻实现，因此可能会出现一个父点对应多个子点的情况，这时又会出现多个逆深度值同时出现的问题，&lt;code&gt;DSO&lt;/code&gt;仍然是通过高斯归一化积来解决这个问题，下面的流程图描述了具体的传递过程：&lt;/p&gt;
&lt;div align="center"&gt;
&lt;img alt="down-up" src="https://sunshanlu.github.io/sunshanlu/images/down-up.png" width="100%"/&gt;
&lt;/div&gt;
&lt;h2 id="4-schur"&gt;4. 初始化器的Schur优化加速&lt;/h2&gt;
&lt;p&gt;这里我打算以&lt;span class="math"&gt;\(t_{ji}\)&lt;/span&gt;不足条件下的优化加速为例，初始化中&lt;span class="math"&gt;\(t_{ji}\)&lt;/span&gt;不足时的优化目标函数如下：&lt;/p&gt;
&lt;div class="math"&gt;$$
E_f=\sum_{p_i\in R_f}{\sum_{p\in \mathcal{N}_i(p_i)}{||I_j(p')-e^a_{ji}I_{R_f}(p)-b_{ji}||_{\gamma}}}+E_l\\
E_l=\alpha \sum_{p_i\in R_f}{(d_{pi}-iR_{i})^2}
$$&lt;/div&gt;
&lt;p&gt;首先，考虑非惩罚部分&lt;span class="math"&gt;\(E_{nl}=E_f-E_l\)&lt;/span&gt;，这部分能量的残差为&lt;span class="math"&gt;\(r_k^{nl}=I_j(p')-e^a_{ji}I_{R_f}(p)-b_{ji}\)&lt;/span&gt;，那么根据高斯牛顿法，在&lt;strong&gt;不考虑&lt;code&gt;Huber&lt;/code&gt;核函数&lt;/strong&gt;的情况下，可以构建优化方程：&lt;/p&gt;
&lt;div class="math"&gt;$$ 
\begin{align*}
    H_{nl}^{p_j} &amp;amp; =\sum_{p \in \mathcal{N_i(p_i)}}{(\frac{\partial{r_k^{nl}}}{\partial{x}})^T(\frac{\partial{r_k^{nl}}}{\partial{x}})} \\ &amp;amp; =
    \begin{bmatrix}
        \sum_{p \in \mathcal{N_i(p_i)}}{(\frac{\partial{r_k^{nl}}}{\partial{\delta\xi_{ji}}})^T(\frac{\partial{r_k^{nl}}}{\partial{\delta\xi_{ji}}})} &amp;amp;
        \sum_{p \in \mathcal{N_i(p_i)}}{(\frac{\partial{r_k^{nl}}}{\partial{\delta\xi_{ji}}})^T (\frac{\partial{r_k^{nl}}}{\partial{a_{ji}}})} &amp;amp;
        \sum_{p \in \mathcal{N_i(p_i)}}{(\frac{\partial{r_k^{nl}}}{\partial{\delta\xi_{ji}}})^T (\frac{\partial{r_k^{nl}}}{\partial{b_{ji}}})} &amp;amp;
        \sum_{p \in \mathcal{N_i(p_i)}}{(\frac{\partial{r_k^{nl}}}{\partial{\delta\xi_{ji}}})^T (\frac{\partial{r_k^{nl}}}{\partial{d_{pi}}})} \\
        \sum_{p \in \mathcal{N_i(p_i)}}{(\frac{\partial{r_k^{nl}}}{\partial{a_{ji}}})^T(\frac{\partial{r_k^{nl}}}{\partial{\delta\xi_{ji}}})} &amp;amp;
        \sum_{p \in \mathcal{N_i(p_i)}}{(\frac{\partial{r_k^{nl}}}{\partial{a_{ji}}})^T\frac{\partial{r_k^{nl}}}{\partial{a_{ji}}}} &amp;amp;
        \sum_{p \in \mathcal{N_i(p_i)}}{(\frac{\partial{r_k^{nl}}}{\partial{a_{ji}}})^T(\frac{\partial{r_k^{nl}}}{\partial{b_{ji}}})} &amp;amp;
        \sum_{p \in \mathcal{N_i(p_i)}}{(\frac{\partial{r_k^{nl}}}{\partial{a_{ji}}})^T(\frac{\partial{r_k^{nl}}}{\partial{d_{pi}}})} \\
        \sum_{p \in \mathcal{N_i(p_i)}}{(\frac{\partial{r_k^{nl}}}{\partial{b_{ji}}})^T(\frac{\partial{r_k^{nl}}}{\partial{\delta\xi_{ji}}})}&amp;amp;
        \sum_{p \in \mathcal{N_i(p_i)}}{(\frac{\partial{r_k^{nl}}}{\partial{b_{ji}}})^T(\frac{\partial{r_k^{nl}}}{\partial{a_{ji}}})}&amp;amp;
        \sum_{p \in \mathcal{N_i(p_i)}}{(\frac{\partial{r_k^{nl}}}{\partial{b_{ji}}})^T(\frac{\partial{r_k^{nl}}}{\partial{b_{ji}}})}&amp;amp;
        \sum_{p \in \mathcal{N_i(p_i)}}{(\frac{\partial{r_k^{nl}}}{\partial{b_{ji}}})^T(\frac{\partial{r_k^{nl}}}{\partial{d_{pi}}})} \\
        \sum_{p \in \mathcal{N_i(p_i)}}{(\frac{\partial{r_k^{nl}}}{\partial{d_{pi}}})^T(\frac{\partial{r_k^{nl}}}{\partial{\delta\xi_{ji}}})} &amp;amp;
        \sum_{p \in \mathcal{N_i(p_i)}}{(\frac{\partial{r_k^{nl}}}{\partial{d_{pi}}})^T(\frac{\partial{r_k^{nl}}}{\partial{a_{ji}}})} &amp;amp;
        \sum_{p \in \mathcal{N_i(p_i)}}{(\frac{\partial{r_k^{nl}}}{\partial{d_{pi}}})^T(\frac{\partial{r_k^{nl}}}{\partial{b_{ji}}})} &amp;amp;
        \sum_{p \in \mathcal{N_i(p_i)}}{(\frac{\partial{r_k^{nl}}}{\partial{d_{pi}}})^T(\frac{\partial{r_k^{nl}}}{\partial{d_{pi}}})}
    \end{bmatrix}
\end{align*}
$$&lt;/div&gt;
&lt;div class="math"&gt;$$
b_{nl}^{p_j}=-\sum_{p \in \mathcal{N_i(p_i)}}{(\frac{\partial{r_k^{nl}}}{\partial{x}})^T \times r_k^{nl}}\quad=
-\begin{bmatrix}
    \sum_{p \in \mathcal{N_i(p_i)}}{(\frac{\partial{r_k^{nl}}}{\partial{\delta\xi_{ji}}})^T \times r_k^{nl}} \\
    \sum_{p \in \mathcal{N_i(p_i)}}{(\frac{\partial{r_k^{nl}}}{\partial{a_{ji}}})^T \times r_k^{nl}} \\
    \sum_{p \in \mathcal{N_i(p_i)}}{(\frac{\partial{r_k^{nl}}}{\partial{b_{ji}}})^T \times r_k^{nl}} \\
    \sum_{p \in \mathcal{N_i(p_i)}}{(\frac{\partial{r_k^{nl}}}{\partial{d_{pi}}})^T \times r_k^{nl}}
\end{bmatrix}
$$&lt;/div&gt;
&lt;div class="math"&gt;$$
H_{nl}=
\begin{bmatrix}
    \sum_{p_i \in R_f}{U_{p_j}} &amp;amp; W^{p_0} &amp;amp; W_{p_1} &amp;amp; ... &amp;amp; W_{p_m} \\
    W_{p_0} &amp;amp; V_{p_0} \\
    W_{p_1} &amp;amp; &amp;amp; V_{p_1}\\
    ... &amp;amp; &amp;amp; &amp;amp; ... \\ 
    W_{p_m} &amp;amp; &amp;amp; &amp;amp; &amp;amp; V_{p_m}\\
\end{bmatrix}
\quad
b_{nl}=
-\begin{bmatrix}
    \sum_{p_i \in R_f}{b_{U}} \\
    b_{V_{p_0}} \\ 
    b_{V_{p_1}} \\ 
    ... \\
    b_{V_{p_m}} \\ 
\end{bmatrix}
$$&lt;/div&gt;
&lt;p&gt;其中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;span class="math"&gt;\(\frac{\partial{r_k^{nl}}}{\partial{\delta\xi_{ji}}}\)&lt;/span&gt;、&lt;span class="math"&gt;\(\frac{\partial{r_k^{nl}}}{\partial{d_{pi}}}\)&lt;/span&gt;的计算公式在&lt;a href="https://sunshanlu.github.io/dso_ssl/Optimization-Model-DSO"&gt;DSO中的优化模型&lt;/a&gt;文章中推导过；&lt;/li&gt;
&lt;li&gt;&lt;span class="math"&gt;\(\frac{\partial{r_k^{nl}}}{\partial{b_{ji}}}\)&lt;/span&gt;、&lt;span class="math"&gt;\(\frac{\partial{r_k^{nl}}}{\partial{d_{pi}}}\)&lt;/span&gt;的计算公式在本篇文章的第一小节中推导过；&lt;/li&gt;
&lt;li&gt;&lt;span class="math"&gt;\(H_{nl}^{pj}\)&lt;/span&gt;和&lt;span class="math"&gt;\(b_{nl}^{p_j}\)&lt;/span&gt;，为某个点&lt;span class="math"&gt;\(p_i\)&lt;/span&gt;投影到&lt;span class="math"&gt;\(p_j\)&lt;/span&gt;这部分的优化约束；&lt;/li&gt;
&lt;li&gt;&lt;span class="math"&gt;\(H_{nl}\)&lt;/span&gt;和&lt;span class="math"&gt;\(b_{nl}\)&lt;/span&gt;，为不含惩罚项部分的能量函数总约束，我使用了分块矩阵进行表示，这么做比较方便，矩阵分块结构图如下：&lt;/li&gt;
&lt;/ul&gt;
&lt;div align="center"&gt;
&lt;img alt="mat-block" src="https://sunshanlu.github.io/sunshanlu/images/mat-block.png" width="100%"/&gt;
&lt;/div&gt;
&lt;p&gt;对惩罚项部分，根据文章的第2小节推导的公式，考虑超参数&lt;span class="math"&gt;\(\alpha_W\)&lt;/span&gt;，并使用上图中的分块方式，可以得到惩罚项部分的优化约束矩阵：&lt;/p&gt;
&lt;div class="math"&gt;$$
H_l= \alpha_W
\begin{bmatrix}
    \sum_{p_i \in R_f}{\begin{bmatrix}
        I_{3\times 3} &amp;amp; \mathbf{0_{3\times3}} &amp;amp; \mathbf{0_{3 \times 1}} &amp;amp; \mathbf{0_{3 \times 1}}\\ 
        \mathbf{0_{3\times3}} &amp;amp; \mathbf{0_{3\times3}} &amp;amp; \mathbf{0_{3 \times 1}} &amp;amp; \mathbf{0_{3 \times 1}}\\ 
        \mathbf{0_{1\times3}} &amp;amp; \mathbf{0_{1\times3}} &amp;amp; 0 &amp;amp; 0\\ 
        \mathbf{0_{1\times3}} &amp;amp; \mathbf{0_{1\times3}} &amp;amp; 0 &amp;amp; 0\\ 
    \end{bmatrix}} &amp;amp; \mathbf{0_{8\times1}} &amp;amp; \mathbf{0_{8\times1}} &amp;amp; ... &amp;amp; \mathbf{0_{8\times1}}\\
    \mathbf{0_{1\times8}} &amp;amp; 1\\
    \mathbf{0_{1\times8}} &amp;amp; &amp;amp; 1\\
    ... &amp;amp; &amp;amp; &amp;amp;...\\
    \mathbf{0_{1\times8}} &amp;amp; &amp;amp; &amp;amp; &amp;amp; 1\\
\end{bmatrix} 
\\
b_l = -\alpha_W 
\begin{bmatrix}
    \begin{bmatrix}t_{ji}\\ \mathbf{0_{3\times1}} \\0\\0\end{bmatrix}\\d_{p0}-1\\d_{p1}-1\\...\\d_{pm}-1
\end{bmatrix}
$$&lt;/div&gt;
&lt;p&gt;因此整个系统的优化约束为：&lt;/p&gt;
&lt;div class="math"&gt;$$
H=H_{nl}+H_{l}=
\begin{bmatrix}
    \sum_{p_i \in R_f}{U_{p_j}'} &amp;amp; W^{p_0} &amp;amp; W_{p_1} &amp;amp; ... &amp;amp; W_{p_m} \\
    W_{p_0} &amp;amp; V_{p_0}+\alpha_W \\
    W_{p_1} &amp;amp; &amp;amp; V_{p_1}+\alpha_W \\
    ... &amp;amp; &amp;amp; &amp;amp; ... \\ 
    W_{p_m} &amp;amp; &amp;amp; &amp;amp; &amp;amp; V_{p_m}+\alpha_W \\
\end{bmatrix}
\quad
b=b_{nl}+b_l=
-\begin{bmatrix}
    \sum_{p_i \in R_f}{b_{U}'} \\
    b_{V_{p_0}} + \alpha_W(d_{p_0} - 1) \\ 
    b_{V_{p_1}} + \alpha_W(d_{p_1} - 1) \\ 
    ... \\
    b_{V_{p_m}} + \alpha_W(d_{p_m} - 1) \\ 
\end{bmatrix}
\\
U_{p_j}'=U_{p_j} + \alpha_W
\begin{bmatrix}
    I_{3\times 3} &amp;amp; \mathbf{0_{3\times3}} &amp;amp; \mathbf{0_{3 \times 1}} &amp;amp; \mathbf{0_{3 \times 1}}\\ 
    \mathbf{0_{3\times3}} &amp;amp; \mathbf{0_{3\times3}} &amp;amp; \mathbf{0_{3 \times 1}} &amp;amp; \mathbf{0_{3 \times 1}}\\ 
    \mathbf{0_{1\times3}} &amp;amp; \mathbf{0_{1\times3}} &amp;amp; 0 &amp;amp; 0\\ 
    \mathbf{0_{1\times3}} &amp;amp; \mathbf{0_{1\times3}} &amp;amp; 0 &amp;amp; 0\\ 
\end{bmatrix}
\quad
b_{U}'=b_{U} + \alpha_W \begin{bmatrix}t_{ji}\\ \mathbf{0_{3\times1}} \\0\\0\end{bmatrix}
$$&lt;/div&gt;
&lt;p&gt;最后，根据SLAM十四讲&lt;code&gt;p249&lt;/code&gt;页描述的&lt;code&gt;Schur&lt;/code&gt;分解公式，先将涉及逆深度部分分解掉，求解相对位姿和相对仿射参数的增量，然后将相对位姿增量和相对仿射参数增量再次带入涉及逆深度方程的部分，求解得到逆深度增量即可。&lt;/p&gt;
&lt;div class="admonition important"&gt;
&lt;p class="admonition-title"&gt;大矩阵&lt;code&gt;Schur&lt;/code&gt;和小矩阵&lt;code&gt;Schur&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;在上面的推导中，初始化器总优化约束的&lt;span class="math"&gt;\(H\)&lt;/span&gt;矩阵和&lt;span class="math"&gt;\(b\)&lt;/span&gt;矩阵由所有投影关系&lt;span class="math"&gt;\(p_j\)&lt;/span&gt;-&lt;span class="math"&gt;\(p_i\)&lt;/span&gt;的优化约束&lt;span class="math"&gt;\(H_{p_j}\)&lt;/span&gt;和&lt;span class="math"&gt;\(b_{p_j}\)&lt;/span&gt;得到。同样不难证明，大矩阵&lt;span class="math"&gt;\(H\)&lt;/span&gt;和&lt;span class="math"&gt;\(b\)&lt;/span&gt;进行&lt;code&gt;Schur&lt;/code&gt;分解和所有小矩阵&lt;span class="math"&gt;\(H_{p_j}\)&lt;/span&gt;和&lt;span class="math"&gt;\(b_{p_j}\)&lt;/span&gt;分别&lt;code&gt;Schur&lt;/code&gt;分解然后组合拼接（这里直接累加即可，因为分解后的矩阵的约束变量是相同的）的效果是一样的。&lt;code&gt;DSO&lt;/code&gt;的源码里面也利用了这个结论，从而避免了大稀疏矩阵构建导致的高空间复杂度问题。&lt;/p&gt;
&lt;/div&gt;
&lt;script type="text/javascript"&gt;if (!document.getElementById('mathjaxscript_pelican_#%@#$@#')) {
    var align = "center",
        indent = "0em",
        linebreak = "false";

    if (false) {
        align = (screen.width &lt; 768) ? "left" : align;
        indent = (screen.width &lt; 768) ? "0em" : indent;
        linebreak = (screen.width &lt; 768) ? 'true' : linebreak;
    }

    var mathjaxscript = document.createElement('script');
    mathjaxscript.id = 'mathjaxscript_pelican_#%@#$@#';
    mathjaxscript.type = 'text/javascript';
    mathjaxscript.src = 'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.3/latest.js?config=TeX-AMS-MML_HTMLorMML';

    var configscript = document.createElement('script');
    configscript.type = 'text/x-mathjax-config';
    configscript[(window.opera ? "innerHTML" : "text")] =
        "MathJax.Hub.Config({" +
        "    config: ['MMLorHTML.js']," +
        "    TeX: { extensions: ['AMSmath.js','AMSsymbols.js','noErrors.js','noUndefined.js'], equationNumbers: { autoNumber: 'none' } }," +
        "    jax: ['input/TeX','input/MathML','output/HTML-CSS']," +
        "    extensions: ['tex2jax.js','mml2jax.js','MathMenu.js','MathZoom.js']," +
        "    displayAlign: '"+ align +"'," +
        "    displayIndent: '"+ indent +"'," +
        "    showMathMenu: true," +
        "    messageStyle: 'normal'," +
        "    tex2jax: { " +
        "        inlineMath: [ ['\\\\(','\\\\)'] ], " +
        "        displayMath: [ ['$$','$$'] ]," +
        "        processEscapes: true," +
        "        preview: 'TeX'," +
        "    }, " +
        "    'HTML-CSS': { " +
        "        availableFonts: ['STIX', 'TeX']," +
        "        preferredFont: 'STIX'," +
        "        styles: { '.MathJax_Display, .MathJax .mo, .MathJax .mi, .MathJax .mn': {color: '#145402 ! important'} }," +
        "        linebreaks: { automatic: "+ linebreak +", width: '90% container' }," +
        "    }, " +
        "}); " +
        "if ('default' !== 'default') {" +
            "MathJax.Hub.Register.StartupHook('HTML-CSS Jax Ready',function () {" +
                "var VARIANT = MathJax.OutputJax['HTML-CSS'].FONTDATA.VARIANT;" +
                "VARIANT['normal'].fonts.unshift('MathJax_default');" +
                "VARIANT['bold'].fonts.unshift('MathJax_default-bold');" +
                "VARIANT['italic'].fonts.unshift('MathJax_default-italic');" +
                "VARIANT['-tex-mathit'].fonts.unshift('MathJax_default-italic');" +
            "});" +
            "MathJax.Hub.Register.StartupHook('SVG Jax Ready',function () {" +
                "var VARIANT = MathJax.OutputJax.SVG.FONTDATA.VARIANT;" +
                "VARIANT['normal'].fonts.unshift('MathJax_default');" +
                "VARIANT['bold'].fonts.unshift('MathJax_default-bold');" +
                "VARIANT['italic'].fonts.unshift('MathJax_default-italic');" +
                "VARIANT['-tex-mathit'].fonts.unshift('MathJax_default-italic');" +
            "});" +
        "}";

    (document.body || document.getElementsByTagName('head')[0]).appendChild(configscript);
    (document.body || document.getElementsByTagName('head')[0]).appendChild(mathjaxscript);
}
&lt;/script&gt;</content><category term="DSO"></category><category term="vSLAM"></category><category term="DSO"></category><category term="Robot"></category></entry><entry><title>DSO中的优化模型</title><link href="https://sunshanlu.github.io/sunshanlu/Optimization-Model-DSO" rel="alternate"></link><published>2025-01-15T00:00:00+08:00</published><updated>2025-01-15T00:00:00+08:00</updated><author><name>孙善路-github</name></author><id>tag:sunshanlu.github.io,2025-01-15:/sunshanlu/Optimization-Model-DSO</id><summary type="html">
&lt;p&gt;在之前的&lt;a href="https://sunshanlu.github.io/dso_ssl/De-distortion-in-DSO"&gt;DSO中的去畸变操作&lt;/a&gt;文章中，讲到&lt;code&gt;DSO&lt;/code&gt;考虑了相机的成像过程对图像像素的影响：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在有光度参数的条件下，&lt;code&gt;DSO&lt;/code&gt;使用&lt;span class="math"&gt;\(G^{-1}(I)\)&lt;/span&gt;非线性响应 …&lt;/li&gt;&lt;/ul&gt;&lt;script type='text/javascript'&gt;if (!document.getElementById('mathjaxscript_pelican_#%@#$@#')) {
    var align = "center",
        indent = "0em",
        linebreak = "false";

    if (false) {
        align = (screen.width &lt; 768) ? "left" : align;
        indent = (screen.width &lt; 768) ? "0em" : indent;
        linebreak = (screen.width &lt; 768) ? 'true' : linebreak;
    }

    var mathjaxscript = document.createElement('script');
    mathjaxscript.id = 'mathjaxscript_pelican_#%@#$@#';
    mathjaxscript.type = 'text/javascript';
    mathjaxscript.src = 'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.3/latest.js?config=TeX-AMS-MML_HTMLorMML';

    var configscript = document.createElement('script');
    configscript.type = 'text/x-mathjax-config';
    configscript[(window.opera ? "innerHTML" : "text")] =
        "MathJax.Hub.Config({" +
        "    config: ['MMLorHTML.js']," +
        "    TeX: { extensions: ['AMSmath.js','AMSsymbols.js','noErrors.js','noUndefined.js'], equationNumbers: { autoNumber: 'none' } }," +
        "    jax: ['input/TeX','input/MathML','output/HTML-CSS']," +
        "    extensions: ['tex2jax.js','mml2jax.js','MathMenu.js','MathZoom.js']," +
        "    displayAlign: '"+ align +"'," +
        "    displayIndent: '"+ indent +"'," +
        "    showMathMenu: true," +
        "    messageStyle: 'normal'," +
        "    tex2jax: { " +
        "        inlineMath: [ ['\\\\(','\\\\)'] ], " +
        "        displayMath: [ ['$$','$$'] ]," +
        "        processEscapes: true," +
        "        preview: 'TeX'," +
        "    }, " +
        "    'HTML-CSS': { " +
        "        availableFonts: ['STIX', 'TeX']," +
        "        preferredFont: 'STIX'," +
        "        styles: { '.MathJax_Display, .MathJax .mo, .MathJax .mi, .MathJax .mn': {color: '#145402 ! important'} }," +
        "        linebreaks: { automatic: "+ linebreak +", width: '90% container' }," +
        "    }, " +
        "}); " +
        "if ('default' !== 'default') {" +
            "MathJax.Hub.Register.StartupHook('HTML-CSS Jax Ready',function () {" +
                "var VARIANT = MathJax.OutputJax['HTML-CSS'].FONTDATA.VARIANT;" +
                "VARIANT['normal'].fonts.unshift('MathJax_default');" +
                "VARIANT['bold'].fonts.unshift('MathJax_default-bold');" +
                "VARIANT['italic'].fonts.unshift('MathJax_default-italic');" +
                "VARIANT['-tex-mathit'].fonts.unshift('MathJax_default-italic');" +
            "});" +
            "MathJax.Hub.Register.StartupHook('SVG Jax Ready',function () {" +
                "var VARIANT = MathJax.OutputJax.SVG.FONTDATA.VARIANT;" +
                "VARIANT['normal'].fonts.unshift('MathJax_default');" +
                "VARIANT['bold'].fonts.unshift('MathJax_default-bold');" +
                "VARIANT['italic'].fonts.unshift('MathJax_default-italic');" +
                "VARIANT['-tex-mathit'].fonts.unshift('MathJax_default-italic');" +
            "});" +
        "}";

    (document.body || document.getElementsByTagName('head')[0]).appendChild(configscript);
    (document.body || document.getElementsByTagName('head')[0]).appendChild(mathjaxscript);
}
&lt;/script&gt;</summary><content type="html">
&lt;p&gt;在之前的&lt;a href="https://sunshanlu.github.io/dso_ssl/De-distortion-in-DSO"&gt;DSO中的去畸变操作&lt;/a&gt;文章中，讲到&lt;code&gt;DSO&lt;/code&gt;考虑了相机的成像过程对图像像素的影响：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在有光度参数的条件下，&lt;code&gt;DSO&lt;/code&gt;使用&lt;span class="math"&gt;\(G^{-1}(I)\)&lt;/span&gt;非线性响应函数的逆过程和渐晕函数&lt;span class="math"&gt;\(V(x)\)&lt;/span&gt;进行图像的光度去畸变操作，可以得到由能量单位组成的去光度畸变的图像。&lt;/li&gt;
&lt;li&gt;在没有光度参数的条件下，&lt;code&gt;DSO&lt;/code&gt;使用仿射参数&lt;code&gt;a&lt;/code&gt;和&lt;code&gt;b&lt;/code&gt;来模拟光度参数的去畸变过程。&lt;/li&gt;
&lt;li&gt;在&lt;a href="https://sunshanlu.github.io/dso_ssl/De-distortion-in-DSO"&gt;DSO中的去畸变操作&lt;/a&gt;文章中，也进行了讨论，即去畸变得到的像素能量并不能保证同一点的一致性，因为还没有考虑曝光时间的影响。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="1"&gt;1. 构建优化模型&lt;/h2&gt;
&lt;p&gt;SLAM十四讲中提到，直接法会直接使用像素的灰度值，构建优化模型的残差，其公式表示为&lt;span class="math"&gt;\(r_k=I_i(p_i)-I_j(p_j)\)&lt;/span&gt;。由于&lt;code&gt;DSO&lt;/code&gt;引入了仿射参数去光度畸变的操作，同时考虑了曝光时间对其一致性的影响，构建的残差应该为：&lt;/p&gt;
&lt;div class="math"&gt;$$
r_k = \frac{1}{t_i}(e^{a_i} I_i(p_i) - b_i) - \frac{1}{t_j}(e^{a_j} I_i(p_i) - b_j)
$$&lt;/div&gt;
&lt;p&gt;通过一些移项和变换的操作，并&lt;strong&gt;考虑像素块&lt;code&gt;pattern&lt;/code&gt;&lt;/strong&gt;的影响，就可以得到&lt;code&gt;DSO&lt;/code&gt;论文里面给出的残差模型了：
&lt;/p&gt;
&lt;div class="math"&gt;$$
E_{p_j} = \sum_{p \in \mathcal{N_{p_i}}}{\left | \left | (I_j(p') - b_j) - \frac{t_je^{a_j}}{t_ie^{a_i}}(I_i(p) - b_i) \right |  \right | }_\gamma
\\
P_{norm} = \pi^{-1}(p)
\\
P' = R_{ji}  P_{norm} + t_{ji} d_{pi}
\\
P_{norm}' = \frac{P'}{P'_Z}
\\
p' = \pi(P'_{norm})
$$&lt;/div&gt;
&lt;p&gt;其中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;span class="math"&gt;\(t_i\)&lt;/span&gt;和&lt;span class="math"&gt;\(t_j\)&lt;/span&gt;分别为&lt;span class="math"&gt;\(i\)&lt;/span&gt;帧和&lt;span class="math"&gt;\(j\)&lt;/span&gt;帧的曝光时间，如果不存在曝光时间，则设定&lt;span class="math"&gt;\(t_i=t_j=1\)&lt;/span&gt;；&lt;/li&gt;
&lt;li&gt;&lt;span class="math"&gt;\(a_i\)&lt;/span&gt;和&lt;span class="math"&gt;\(b_i\)&lt;/span&gt;为&lt;span class="math"&gt;\(i\)&lt;/span&gt;帧的仿射参数，&lt;span class="math"&gt;\(a_j\)&lt;/span&gt;和&lt;span class="math"&gt;\(b_j\)&lt;/span&gt;为&lt;span class="math"&gt;\(j\)&lt;/span&gt;帧的仿射参数，如果存在光度参数和曝光时间，则设定&lt;span class="math"&gt;\(a_j = a_i = b_j = b_i = 0\)&lt;/span&gt;；&lt;/li&gt;
&lt;li&gt;&lt;span class="math"&gt;\(I_i\)&lt;/span&gt;和&lt;span class="math"&gt;\(I_j\)&lt;/span&gt;分别为&lt;span class="math"&gt;\(i\)&lt;/span&gt;帧和&lt;span class="math"&gt;\(j\)&lt;/span&gt;帧的去畸变图像，设定&lt;span class="math"&gt;\(i\)&lt;/span&gt;帧为参考帧，而&lt;span class="math"&gt;\(j\)&lt;/span&gt;帧为待估计帧；&lt;/li&gt;
&lt;li&gt;&lt;span class="math"&gt;\(p\)&lt;/span&gt;和&lt;span class="math"&gt;\(p'\)&lt;/span&gt;分别为&lt;span class="math"&gt;\(i\)&lt;/span&gt;帧上的像素点和&lt;span class="math"&gt;\(j\)&lt;/span&gt;帧上的像素点，其中&lt;span class="math"&gt;\(p'\)&lt;/span&gt;是&lt;span class="math"&gt;\(p\)&lt;/span&gt;经过反向投影，位姿变换和正向投影得到的像素点；&lt;/li&gt;
&lt;li&gt;&lt;span class="math"&gt;\(\pi\)&lt;/span&gt;函数指的是从归一化坐标系到像素坐标系的投影，&lt;span class="math"&gt;\(\pi^{-1}\)&lt;/span&gt;是&lt;span class="math"&gt;\(\pi\)&lt;/span&gt;的反函数；&lt;/li&gt;
&lt;li&gt;&lt;span class="math"&gt;\(\mathcal{N_{p_i}}\)&lt;/span&gt;为以&lt;span class="math"&gt;\(i\)&lt;/span&gt;帧上的像素点&lt;span class="math"&gt;\(p_i\)&lt;/span&gt;为中心的像素块，在&lt;code&gt;DSO&lt;/code&gt;的论文中，把他叫做一个&lt;code&gt;pattern&lt;/code&gt;，&lt;code&gt;DSO&lt;/code&gt;引入&lt;code&gt;pattern&lt;/code&gt;的概念，认为在一个&lt;code&gt;pattern&lt;/code&gt;上，所有像素的&lt;strong&gt;逆深度&lt;span class="math"&gt;\(d_{pi}\)&lt;/span&gt;&lt;/strong&gt;值保持&lt;strong&gt;一致&lt;/strong&gt;，下图中是在&lt;code&gt;DSO&lt;/code&gt;论文中讨论的一些&lt;code&gt;pattern&lt;/code&gt;；&lt;/li&gt;
&lt;/ul&gt;
&lt;div align="center"&gt;
&lt;img alt="DSO 中的 pattern 类型" src="https://sunshanlu.github.io/sunshanlu/images/pattern.png" width="80%"/&gt;
&lt;/div&gt;
&lt;div class="admonition important"&gt;
&lt;p class="admonition-title"&gt;&lt;code&gt;DSO&lt;/code&gt;的投影过程与普通&lt;code&gt;BA&lt;/code&gt;之间存在区别？&lt;/p&gt;
&lt;p&gt;在&lt;code&gt;DSO&lt;/code&gt;构建的模型中，有一个与普通&lt;code&gt;BA&lt;/code&gt;过程存在明显不同的地方，即在构建完成反向投影后，并不会使用&lt;span class="math"&gt;\(R_{ji}\frac{P_{norm}}{d_{pi}}+t_{ji}\)&lt;/span&gt;来求解真实的&lt;code&gt;3d&lt;/code&gt;点，而是使用&lt;span class="math"&gt;\(R_{ji}P_{norm}+t_{ji}d_{pi}\)&lt;/span&gt;的方式，乘在了右边，构建了一个虚拟的&lt;code&gt;3d&lt;/code&gt;点，这个虚拟&lt;code&gt;3d&lt;/code&gt;点在坐标系原点真实&lt;code&gt;3d&lt;/code&gt;的直线上，因此真实点和虚拟点之间对应着一个相同的归一化坐标系下的点。这么构建有一个比较明显的优势，即针对&lt;span class="math"&gt;\(d_{pi}\)&lt;/span&gt;求导时，会变的比较简单。&lt;/p&gt;
&lt;/div&gt;
&lt;h2 id="2"&gt;2. 求解模型雅可比&lt;/h2&gt;
&lt;p&gt;现在，令&lt;span class="math"&gt;\(r_k=I_j(p')-\frac{t_je^{a_j}}{t_ie^{a_i}}I_i(p)+\frac{t_je^{a_j}}{t_ie^{a_i}}b_i-b_j\)&lt;/span&gt;，考虑使用&lt;code&gt;GN&lt;/code&gt;法或者&lt;code&gt;LM&lt;/code&gt;法求解这个优化问题，因此需要求解残差&lt;span class="math"&gt;\(r_k\)&lt;/span&gt;对待优化量的雅可比矩阵，后续无论是需要为优化模型添加核函数，或者是求解优化模型的海塞矩阵&lt;code&gt;H&lt;/code&gt;，都可以通过残差对待优化量的雅可比矩阵进行变换得到。&lt;/p&gt;
&lt;h3 id="21"&gt;2.1 残差对位姿的雅可比矩阵&lt;/h3&gt;
&lt;p&gt;根据链式求导法则，残差对位姿&lt;span class="math"&gt;\(T_{ji}\)&lt;/span&gt;的左侧扰动&lt;span class="math"&gt;\(\xi\)&lt;/span&gt;的雅可比矩阵&lt;span class="math"&gt;\(\frac{\partial{r_k}}{\partial{{\xi_{ji}}}}\)&lt;/span&gt;：&lt;/p&gt;
&lt;div class="math"&gt;$$
\frac{\partial{r_k}}{\partial{{\xi_{ji}}}} = \frac{\partial{r_k}}{\partial I_j} * \frac{\partial{I_j}}{\partial{p'}} * \frac{\partial{p'}}{\partial{P_{norm}'}} * \frac{\partial{P_{norm}'}}{\partial{P'}} * \frac{\partial{P'}}{\partial{\xi_{ji}}} 
$$&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;span class="math"&gt;\(\frac{\partial{r_k}}{\partial I_j}\)&lt;/span&gt;，根据残差公式，可以看出来&lt;span class="math"&gt;\(\frac{\partial{r_k}}{\partial I_j}=1\)&lt;/span&gt;；&lt;/li&gt;
&lt;li&gt;&lt;span class="math"&gt;\(\frac{\partial{I_j}}{\partial{p'}}\)&lt;/span&gt;，可以定义为&lt;span class="math"&gt;\(p'\)&lt;/span&gt;在图像&lt;span class="math"&gt;\(I_j\)&lt;/span&gt;上的像素梯度，以&lt;span class="math"&gt;\([d_x,d_y]\)&lt;/span&gt;进行表示；&lt;/li&gt;
&lt;li&gt;&lt;span class="math"&gt;\(\frac{\partial{p'}}{\partial{P_{norm}'}}\)&lt;/span&gt;，这部分表示的是归一化坐标系到像素坐标系的投影过程，其雅可比矩阵可以使用如下公式进行表示；&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="math"&gt;$$
\frac{\partial{p'}}{\partial{P_{norm}'}} = \begin{bmatrix}f_x&amp;amp;0\\0&amp;amp;f_y\end{bmatrix}
$$&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;span class="math"&gt;\(\frac{\partial{P_{norm}'}}{\partial{P'}}\)&lt;/span&gt;，这部分表示的是虚拟点&lt;span class="math"&gt;\(P'\)&lt;/span&gt;到归一化坐标系的投影过程，根据公式不难推导出,其雅可比矩阵可以由下面的公式进行表示：
&lt;div class="math"&gt;$$
\frac{\partial{P_{norm}'}}{\partial{P'}} = 
\begin{bmatrix}\frac{1}{P_Z'}&amp;amp;0&amp;amp;-\frac{P_X'}{P_Z'^2}\\0&amp;amp;\frac{1}{P_Z'}&amp;amp;-\frac{P_Y'}{P_Z'^2}\end{bmatrix}
$$&lt;/div&gt;
&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;span class="math"&gt;\(\frac{\partial{P'}}{\partial{\xi_{ji}}}\)&lt;/span&gt;，这部分表示的是虚拟&lt;code&gt;3d&lt;/code&gt;点位姿&lt;span class="math"&gt;\(T_{ji}\)&lt;/span&gt;左乘扰动&lt;span class="math"&gt;\(\xi_{ji}\)&lt;/span&gt;的雅可比矩阵，在视觉SLAM十四讲的中86页中推导过，这里只不过是针对虚拟点做了一些变换，可以得到&lt;span class="math"&gt;\(\frac{\partial{P'}}{\partial{\xi_{ji}}}=[d_{pi}I,-P'^{\wedge}]\)&lt;/span&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;综上，通过链式法则将5部分的雅可比矩阵相乘可得残差对&lt;span class="math"&gt;\(T_{ji}\)&lt;/span&gt;的左乘雅可比矩阵为:&lt;/p&gt;
&lt;div class="math"&gt;$$
\begin{align*}
    \frac{\partial{r_k}}{\partial{\xi_{ji}}}&amp;amp;=
    \frac{1}{P_Z'} 
    \begin{bmatrix} d_x&amp;amp;d_y \end{bmatrix} 
    \begin{bmatrix} f_x &amp;amp; 0 \\ 0 &amp;amp; f_y \end{bmatrix}
    \begin{bmatrix}
        1 &amp;amp; 0 &amp;amp; -\frac{P_X'}{P_Z'} \\
        0 &amp;amp; 1 &amp;amp; -\frac{P_Y'}{P_Z'} \\
    \end{bmatrix}
    \begin{bmatrix}
        d_{pi}&amp;amp;-P'^{\wedge}
    \end{bmatrix}\\ &amp;amp;=
    \begin{bmatrix} d_xf_x&amp;amp;d_yf_y\end{bmatrix}
    \begin{bmatrix}
        \frac{d_{pi}}{P_{Z}'} &amp;amp; 0 &amp;amp; -\frac{d_{pi}}{P_{Z}'}\frac{P_{X}'}{P_{Z}'} &amp;amp; -\frac{P_{X}'P_{Y}'}{P_{Z}'^2} &amp;amp; 1+\frac{P_{X}^{2}}{P_{Z}^{2}} &amp;amp; -\frac{P_{Y}'}{P_{Z}'} 
        \\
        0 &amp;amp; \frac{d_{pi}}{P_{Z}'} &amp;amp; -\frac{d_{pi}}{P_{Z}'}\frac{P_{Y}'}{P_{Z}'} &amp;amp; -1-\frac{P_{Y}^{2}}{P_{Z}^{2}} &amp;amp; \frac{P_{X}'P_{Y}'}{P_{Z}^{2}} &amp;amp; \frac{P_{X}'}{P_{Z}'}
    \end{bmatrix}
\end{align*}
$$&lt;/div&gt;
&lt;h3 id="22-p_i"&gt;2.2 残差对&lt;span class="math"&gt;\(p_i\)&lt;/span&gt;点逆深度的雅可比矩阵&lt;/h3&gt;
&lt;p&gt;根据链式求导法则，残差对点的逆深度&lt;span class="math"&gt;\(d_{pi}\)&lt;/span&gt;的雅可比矩阵&lt;span class="math"&gt;\(\frac{\partial{r_k}}{\partial{{d_{pi}}}}\)&lt;/span&gt;：&lt;/p&gt;
&lt;div class="math"&gt;$$
\frac{\partial{r_k}}{\partial{{d_{pi}}}} = \frac{\partial{r_k}}{\partial I_j} * \frac{\partial{I_j}}{\partial{p'}} * \frac{\partial{p'}}{\partial{P_{norm}'}} * \frac{\partial{P_{norm}'}}{\partial{P'}} * \frac{\partial{P'}}{\partial{d_{pi}}} 
$$&lt;/div&gt;
&lt;p&gt;可以发现，残差对&lt;span class="math"&gt;\(p_i\)&lt;/span&gt;点逆深度的链式求导的雅可比矩阵的前4部分都是相同的，因此只需要考虑&lt;span class="math"&gt;\(\frac{\partial{P'}}{\partial{d_{pi}}}\)&lt;/span&gt;这部分的雅可比即可，从公式中不难推导出：
&lt;/p&gt;
&lt;div class="math"&gt;$$
\frac{\partial{P'}}{\partial{d_{pi}}}=t_{ji}
$$&lt;/div&gt;
&lt;p&gt;综上，通过链式法则将5部分的雅可比矩阵相乘可得残差对&lt;span class="math"&gt;\(d_{pi}\)&lt;/span&gt;的雅可比矩阵为:&lt;/p&gt;
&lt;div class="math"&gt;$$
\begin{align*}
    \frac{\partial{r_k}}{\partial{d_{pi}}}&amp;amp;=
    \frac{1}{P_Z'} 
    \begin{bmatrix} d_x&amp;amp;d_y \end{bmatrix} 
    \begin{bmatrix} f_x &amp;amp; 0 \\ 0 &amp;amp; f_y \end{bmatrix}
    \begin{bmatrix}
        1 &amp;amp; 0 &amp;amp; -\frac{P_X'}{P_Z'} \\
        0 &amp;amp; 1 &amp;amp; -\frac{P_Y'}{P_Z'} \\
    \end{bmatrix}
    \begin{bmatrix}
        t_{ji}^X\\t_{ji}^Y\\t_{ji}^Z
    \end{bmatrix}\\ &amp;amp;=
    \frac{1}{P_Z'}[d_xf_x(t_X^{ji}-\frac{P_X'}{P_Z'}t^Z_{ji})+d_yf_y(t_Y^{ji}-\frac{P_Y'}{P_Z'}t^Z_{ji})]
\end{align*}
$$&lt;/div&gt;
&lt;h3 id="23"&gt;2.3 残差对光度仿射参数的雅可比矩阵&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;DSO&lt;/code&gt;在不同的阶段对光度仿射参数做了不同程度的处理，其主要表现在初始化阶段、前端跟踪阶段和后端滑窗阶段。其中，初始化阶段和前端跟踪阶段主要注重相对仿射参数&lt;span class="math"&gt;\(a_{ji}\)&lt;/span&gt;和&lt;span class="math"&gt;\(b_{ji}\)&lt;/span&gt;，而在后端滑窗优化阶段中则更加注重全局的仿射参数&lt;span class="math"&gt;\(a_j\)&lt;/span&gt;、&lt;span class="math"&gt;\(b_j\)&lt;/span&gt;、&lt;span class="math"&gt;\(a_i\)&lt;/span&gt;和&lt;span class="math"&gt;\(b_i\)&lt;/span&gt;。&lt;/p&gt;
&lt;p&gt;我打算分别在初始化、前端跟踪和后端滑窗优化三篇文章中单独对”&lt;code&gt;DSO&lt;/code&gt;对仿射参数处理”进行解析说明，在这篇文章中就不过多赘述了。&lt;/p&gt;
&lt;h2 id="3"&gt;3. 模型在后端优化中的问题&lt;/h2&gt;
&lt;p&gt;正如我在2.3小节中所描述的，&lt;code&gt;DSO&lt;/code&gt;的模型在后端优化中会产生一些问题，原因在于后端优化的参数量为全局量，而不是相对量。即是&lt;code&gt;global&lt;/code&gt;而非&lt;code&gt;local&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;其中在后端中需要特殊处理的参数主要有相对位姿&lt;span class="math"&gt;\(T_{ji}\)&lt;/span&gt;转变为绝对位姿&lt;span class="math"&gt;\(T_{jw}\)&lt;/span&gt;和&lt;span class="math"&gt;\(T_{iw}\)&lt;/span&gt;、相对光度仿射参数&lt;span class="math"&gt;\(a_{ji}\)&lt;/span&gt;和&lt;span class="math"&gt;\(b_{ji}\)&lt;/span&gt;转变为全局光度仿射参数&lt;span class="math"&gt;\(a_j\)&lt;/span&gt;，&lt;span class="math"&gt;\(b_j\)&lt;/span&gt;，&lt;span class="math"&gt;\(a_i\)&lt;/span&gt;和&lt;span class="math"&gt;\(b_i\)&lt;/span&gt;。&lt;/p&gt;
&lt;p&gt;模型如何在后端优化中解决这个问题的说明，我会在&lt;code&gt;DSO&lt;/code&gt;滑窗优化后端中单独进行说明。&lt;/p&gt;
&lt;div class="admonition note"&gt;
&lt;p class="admonition-title"&gt;提前透漏&lt;/p&gt;
&lt;p&gt;可以提前说明的是，在相对位姿转绝对位姿部分，&lt;code&gt;DSO&lt;/code&gt;使用的是位姿矩阵伴随的性质，而光度仿射参数的转换则采用的是雅可比矩阵中转变换的方法。&lt;/p&gt;
&lt;/div&gt;
&lt;script type="text/javascript"&gt;if (!document.getElementById('mathjaxscript_pelican_#%@#$@#')) {
    var align = "center",
        indent = "0em",
        linebreak = "false";

    if (false) {
        align = (screen.width &lt; 768) ? "left" : align;
        indent = (screen.width &lt; 768) ? "0em" : indent;
        linebreak = (screen.width &lt; 768) ? 'true' : linebreak;
    }

    var mathjaxscript = document.createElement('script');
    mathjaxscript.id = 'mathjaxscript_pelican_#%@#$@#';
    mathjaxscript.type = 'text/javascript';
    mathjaxscript.src = 'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.3/latest.js?config=TeX-AMS-MML_HTMLorMML';

    var configscript = document.createElement('script');
    configscript.type = 'text/x-mathjax-config';
    configscript[(window.opera ? "innerHTML" : "text")] =
        "MathJax.Hub.Config({" +
        "    config: ['MMLorHTML.js']," +
        "    TeX: { extensions: ['AMSmath.js','AMSsymbols.js','noErrors.js','noUndefined.js'], equationNumbers: { autoNumber: 'none' } }," +
        "    jax: ['input/TeX','input/MathML','output/HTML-CSS']," +
        "    extensions: ['tex2jax.js','mml2jax.js','MathMenu.js','MathZoom.js']," +
        "    displayAlign: '"+ align +"'," +
        "    displayIndent: '"+ indent +"'," +
        "    showMathMenu: true," +
        "    messageStyle: 'normal'," +
        "    tex2jax: { " +
        "        inlineMath: [ ['\\\\(','\\\\)'] ], " +
        "        displayMath: [ ['$$','$$'] ]," +
        "        processEscapes: true," +
        "        preview: 'TeX'," +
        "    }, " +
        "    'HTML-CSS': { " +
        "        availableFonts: ['STIX', 'TeX']," +
        "        preferredFont: 'STIX'," +
        "        styles: { '.MathJax_Display, .MathJax .mo, .MathJax .mi, .MathJax .mn': {color: '#145402 ! important'} }," +
        "        linebreaks: { automatic: "+ linebreak +", width: '90% container' }," +
        "    }, " +
        "}); " +
        "if ('default' !== 'default') {" +
            "MathJax.Hub.Register.StartupHook('HTML-CSS Jax Ready',function () {" +
                "var VARIANT = MathJax.OutputJax['HTML-CSS'].FONTDATA.VARIANT;" +
                "VARIANT['normal'].fonts.unshift('MathJax_default');" +
                "VARIANT['bold'].fonts.unshift('MathJax_default-bold');" +
                "VARIANT['italic'].fonts.unshift('MathJax_default-italic');" +
                "VARIANT['-tex-mathit'].fonts.unshift('MathJax_default-italic');" +
            "});" +
            "MathJax.Hub.Register.StartupHook('SVG Jax Ready',function () {" +
                "var VARIANT = MathJax.OutputJax.SVG.FONTDATA.VARIANT;" +
                "VARIANT['normal'].fonts.unshift('MathJax_default');" +
                "VARIANT['bold'].fonts.unshift('MathJax_default-bold');" +
                "VARIANT['italic'].fonts.unshift('MathJax_default-italic');" +
                "VARIANT['-tex-mathit'].fonts.unshift('MathJax_default-italic');" +
            "});" +
        "}";

    (document.body || document.getElementsByTagName('head')[0]).appendChild(configscript);
    (document.body || document.getElementsByTagName('head')[0]).appendChild(mathjaxscript);
}
&lt;/script&gt;</content><category term="DSO"></category><category term="vSLAM"></category><category term="DSO"></category><category term="Robot"></category></entry><entry><title>DSO中的点选策略</title><link href="https://sunshanlu.github.io/sunshanlu/Point-select-in-DSO" rel="alternate"></link><published>2025-01-14T00:00:00+08:00</published><updated>2025-01-14T00:00:00+08:00</updated><author><name>孙善路-github</name></author><id>tag:sunshanlu.github.io,2025-01-14:/sunshanlu/Point-select-in-DSO</id><summary type="html">
&lt;p&gt;&lt;code&gt;DSO&lt;/code&gt;作为一个稀疏直接法，其中一个核心内容就是对图像上的部分点进行跟踪和&lt;strong&gt;逆深度&lt;/strong&gt;估计，既然是对图像上的某些点进 …&lt;/p&gt;</summary><content type="html">
&lt;p&gt;&lt;code&gt;DSO&lt;/code&gt;作为一个稀疏直接法，其中一个核心内容就是对图像上的部分点进行跟踪和&lt;strong&gt;逆深度&lt;/strong&gt;估计，既然是对图像上的某些点进行跟踪估计操作，那么肯定会涉及到如何在图像上选择点的操作，在整个&lt;code&gt;DSO&lt;/code&gt;的算法逻辑中，点选策略主要使用在两个阶段，一个是&lt;strong&gt;算法初始化阶段&lt;/strong&gt;，还有一个是&lt;strong&gt;创建关键帧阶段&lt;/strong&gt;。&lt;/p&gt;
&lt;h2 id="1"&gt;1. 初始化阶段的点选策略&lt;/h2&gt;
&lt;p&gt;在&lt;code&gt;DSO&lt;/code&gt;的初始化过程中，需要确定一个初始化参考帧，当然为了初始化的稳定和准确性，需要构建图像金字塔来完成初始化参考帧的构建。当然初始化部分我会单独出一个文章来进行讲解，这里主要是对初始化过程中的点选策略进行分析，大家只需要记住在初始化过程中需要对初始化关键帧进行&lt;strong&gt;金字塔多层级&lt;/strong&gt;的点选操作，以供后续的初始化操作使用即可，至于为什么使用金字塔？或者为什么使用这样的点选策略，我会在后续的&lt;code&gt;DSO&lt;/code&gt;初始化文章中进行阐述。&lt;/p&gt;
&lt;h3 id="11"&gt;1.1 金字塔第零层点选&lt;/h3&gt;
&lt;p&gt;第0层的金字塔点的选择的要求主要有以下三点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;数量要求&lt;/strong&gt;：第0层上选择的点的数量要满足要求，这因该是最基本的选点要求；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;特殊性要求&lt;/strong&gt;：第0层上选择的点要具有一定的特殊性，能够对模型提供比较大信息，在&lt;a href="https://sunshanlu.github.io/dso_ssl/Advantages-of-DSO"&gt;Advantages-of-&lt;span class="caps"&gt;DSO&lt;/span&gt;&lt;/a&gt;文章里面提到，直接法往往和像素梯度直接相关，因此大梯度点，往往能对模型优化提供一个相对多的信息；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;均匀性要求&lt;/strong&gt;：第0层上选择的点要求具有一定的均匀性，保证位姿估计的稳定性，因为大梯度点，往往在图像的极个别区域内集中分布，选择均匀分布的大梯度点，往往能规避集中分布的像素点导致位姿估计的不稳定性问题；&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;code&gt;DSO&lt;/code&gt;首先使用&lt;strong&gt;块阈值计算策略&lt;/strong&gt;，将输入图像（经过光度畸变矫正和像素畸变矫正）划分为&lt;code&gt;32px * 32px&lt;/code&gt;的&lt;code&gt;n&lt;/code&gt;多个小像素块，对每个块内独立的统计像素梯度的均方根，找到&lt;code&gt;0.5&lt;/code&gt;分位数对应的均方根梯度值，然后加上一个超参数&lt;code&gt;bias&lt;/code&gt;的方式，作为大梯度阈值，除此之外，为了保证整张图像的块梯度平滑性，使用一个&lt;code&gt;3 * 3&lt;/code&gt;的均值滤波来对块梯度阈值进行操作。使用上述的块阈值计算策略，可以为后续提供不同区域下的梯度阈值，满足了点选要求的&lt;strong&gt;特殊性&lt;/strong&gt;和&lt;strong&gt;均匀性&lt;/strong&gt;，整个块阈值计算策略如下图所示。&lt;/p&gt;
&lt;div align="center"&gt;
&lt;img alt="块阈值计算策略" src="https://sunshanlu.github.io/sunshanlu/images/grad-th.png" width="100%"/&gt;
&lt;/div&gt;
&lt;p&gt;&lt;code&gt;DSO&lt;/code&gt;使用&lt;strong&gt;定&lt;code&gt;pot&lt;/code&gt;选点策略&lt;/strong&gt;，首先使用给定的&lt;code&gt;pot&lt;/code&gt;，依次从&lt;code&gt;4pot * 4pot&lt;/code&gt;到&lt;code&gt;2pot * 2pot&lt;/code&gt;再到&lt;code&gt;pot * pot&lt;/code&gt;的区域内，进行遍历，试图找到满足阈值条件的像素点。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;首先，针对最内层的&lt;code&gt;pot * pot&lt;/code&gt;区域内，进行逐像素的遍历，找到&lt;code&gt;pot * pot&lt;/code&gt;区域内的最大梯度像素点，并且要满足最大像素梯度值要大于&lt;strong&gt;块阈值计算策略&lt;/strong&gt;中计算的平滑阈值。&lt;/li&gt;
&lt;li&gt;当完成了&lt;code&gt;2pot * 2pot&lt;/code&gt;内的&lt;code&gt;pot * pot&lt;/code&gt;的遍历后，如果区域内的4个&lt;code&gt;pot * pot&lt;/code&gt;区域没有找到一个满足阈值条件的像素点（在&lt;code&gt;DSO&lt;/code&gt;中，会将这部分像素点从0层投影到1层，使用1层的梯度进行阈值条件判断），那么这里将阈值要求降低&lt;code&gt;0.75&lt;/code&gt;倍，然后重新遍历&lt;code&gt;2pot * 2pot&lt;/code&gt;区域内的所有像素试图找到一个满足阈值条件的像素点（这里的阈值条件已经降低了0.75倍数）。&lt;/li&gt;
&lt;li&gt;当完成了&lt;code&gt;4pot * 4pot&lt;/code&gt;内的&lt;code&gt;2pot * 2pot&lt;/code&gt;的遍历后，如果区域内的4个&lt;code&gt;2pot * 2pot&lt;/code&gt;区域内没有找到一个满足阈值条件的像素点，那么这里将阈值要求再次降低&lt;code&gt;0.75&lt;/code&gt;倍，然后重新遍历&lt;code&gt;4pot * 4pot&lt;/code&gt;区域内的所有像素点（在&lt;code&gt;DSO&lt;/code&gt;中，会将这部分像素点从0层投影到2层，使用2层的梯度进行阈值条件判断），试图找到一个满足阈值条件的像素点（这里的阈值条件已经降低了&lt;code&gt;0.75 * 0.75&lt;/code&gt;）倍。&lt;/li&gt;
&lt;li&gt;使用这种&lt;strong&gt;定&lt;code&gt;pot&lt;/code&gt;选点策略&lt;/strong&gt;，可以保证选点要求的&lt;strong&gt;均匀性&lt;/strong&gt;和&lt;strong&gt;特殊性&lt;/strong&gt;，除了可以保证&lt;strong&gt;均匀性&lt;/strong&gt;外，该策略还可以在图像梯度值比较低的条件下完成特征的提取，因为这里涉及到梯度阈值的降低，该策略的示意图如下所示。&lt;/li&gt;
&lt;/ul&gt;
&lt;div align="center"&gt;
&lt;img alt="定pot选点策略" src="https://sunshanlu.github.io/sunshanlu/images/pot-select.png" width="80%"/&gt;
&lt;/div&gt;
&lt;div class="admonition note"&gt;
&lt;p class="admonition-title"&gt;定&lt;code&gt;pot&lt;/code&gt;选点策略的加速&lt;/p&gt;
&lt;p&gt;&lt;code&gt;DSO&lt;/code&gt;中的定&lt;code&gt;pot&lt;/code&gt;选点策略的思路就是我在上面描述的那样，但是反映在代码逻辑上，存在着一些可以加速的策略，比如说在&lt;code&gt;DSO&lt;/code&gt;的源码部分，当小区域&lt;code&gt;pot * pot&lt;/code&gt;对应的像素点任何点达到梯度阈值要求的时候，会同步判断&lt;code&gt;2pot * 2pot&lt;/code&gt;和&lt;code&gt;4pot * 4pot&lt;/code&gt;大区域是否满足阈值条件的要求，只有当小区域出现满足阈值条件要求的像素点时，才放弃对大区域内像素点的梯度判断，从而避免了小区域内没选到点而再次遍历的尴尬逻辑。 当然，除此之外，还可以使用多线程加速的策略，即每个区域的所有像素点对应一个线程，三个线程同时进行遍历和判断，然后对最后得到的结果进行统计，取小区域中满足阈值要求的点。当&lt;code&gt;cpu&lt;/code&gt;的性能足够时，这也不失为一种加速策略。&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;&lt;code&gt;DSO&lt;/code&gt;使用&lt;strong&gt;递归&lt;code&gt;pot&lt;/code&gt;随动策略&lt;/strong&gt;，来保证选点要求的&lt;strong&gt;数量要求&lt;/strong&gt;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;通过分析，不难发现，调整&lt;code&gt;pot&lt;/code&gt;的大小可以间接的提点数量进行控制。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DSO&lt;/code&gt;中，使用&lt;code&gt;pot=3&lt;/code&gt;作为初始的&lt;code&gt;pot&lt;/code&gt;大小，通过&lt;strong&gt;定&lt;code&gt;pot&lt;/code&gt;选点策略&lt;/strong&gt;来选择均匀且带有特殊性的像素点。&lt;/li&gt;
&lt;li&gt;当选择到的点比期望的点多时，说明当前的&lt;code&gt;pot&lt;/code&gt;比较小，得到的&lt;code&gt;pot * pot&lt;/code&gt;的区域比较多，因此需要递增&lt;code&gt;pot&lt;/code&gt;的大小。&lt;/li&gt;
&lt;li&gt;当选择到的点比期望的点少时，说明当前的&lt;code&gt;pot&lt;/code&gt;比较大，得到的&lt;code&gt;pot * pot&lt;/code&gt;的区域比较少，因此需要递减&lt;code&gt;pot&lt;/code&gt;的大小。&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="admonition note"&gt;
&lt;p class="admonition-title"&gt;与期望数目之间差多少才算多？&lt;/p&gt;
&lt;p&gt;&lt;code&gt;DSO&lt;/code&gt;在源码中，规定了一个尺度范围&lt;code&gt;ratio&lt;/code&gt;为&lt;span class="math"&gt;\([0.25,1.25]\)&lt;/span&gt;，注意这个尺度范围&lt;code&gt;ratio&lt;/code&gt;指的是 &lt;strong&gt;期望值 / 提取值&lt;/strong&gt;，当&lt;code&gt;ratio &amp;lt; 0.25&lt;/code&gt;时，认定此时提取的点比较多，需要递增&lt;code&gt;pot&lt;/code&gt;的大小，当&lt;code&gt;ratio &amp;gt; 1.25&lt;/code&gt;时，认定此时提取的点比较少，需要递减&lt;code&gt;pot&lt;/code&gt;的大小。&lt;/p&gt;
&lt;/div&gt;
&lt;h3 id="12"&gt;1.2 金字塔其他层点选&lt;/h3&gt;
&lt;p&gt;在初始化过程中，金字塔的其他层选择相对简单，也存在一些&lt;strong&gt;定&lt;code&gt;pot&lt;/code&gt;选点策略&lt;/strong&gt;和&lt;strong&gt;递归&lt;code&gt;pot&lt;/code&gt;随动策略&lt;/strong&gt;来保证特征点的&lt;strong&gt;均匀性&lt;/strong&gt;要求和&lt;strong&gt;数量一致性&lt;/strong&gt;要求。但是这两种策略与第0层选点使用的&lt;strong&gt;定&lt;code&gt;pot&lt;/code&gt;选点策略&lt;/strong&gt;和&lt;strong&gt;递归&lt;code&gt;pot&lt;/code&gt;随动策略&lt;/strong&gt;不太一样，这里仅单独的进行一些说明。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;首先，在阈值方面，金字塔其它层的提取使用定阈值&lt;code&gt;10&lt;/code&gt;和阈值缩放因子&lt;code&gt;scaleTH&lt;/code&gt;一起决定，即阈值的大小与缩放因子相关。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;定&lt;code&gt;pot&lt;/code&gt;选点策略&lt;/strong&gt;，不像第0层那样，由&lt;code&gt;4pot * 4pot&lt;/code&gt; 到 &lt;code&gt;2pot * 2pot&lt;/code&gt; 再到 &lt;code&gt;pot * pot&lt;/code&gt;那样以此降低阈值来提点了，而是仅&lt;code&gt;pot * pot&lt;/code&gt;区域的遍历，试图找到&lt;strong&gt;最多4个&lt;/strong&gt;像素点，该像素点要求阈值大于给定的&lt;code&gt;10 * scaleTH&lt;/code&gt;，并且其x轴梯度最大，或y轴梯度最大，或x轴 - y轴梯度最大，或x轴 + y轴梯度最大。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;递归&lt;code&gt;pot&lt;/code&gt;随动策略&lt;/strong&gt;，同样不像第0层那样，它考虑了一个&lt;strong&gt;模型&lt;/strong&gt;，认定可以提取的点与&lt;code&gt;pot&lt;/code&gt;的大小成反比，即&lt;span class="math"&gt;\(pot^2*num=K\)&lt;/span&gt;，也就是说，首先使用一个默认&lt;code&gt;pot&lt;/code&gt;进行点选，然后根据点选的数量和默认&lt;code&gt;pot&lt;/code&gt;的大小，确定&lt;span class="math"&gt;\(K\)&lt;/span&gt;值，然后使用&lt;span class="math"&gt;\(K\)&lt;/span&gt;值计算期望的&lt;code&gt;pot&lt;/code&gt;大小，然后递归的调用即可，其原理可以由下面的公式解释。&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="math"&gt;$$
\frac{pot_d^2}{pot^2}=\frac{n_{have}}{n_{desir}}
$$&lt;/div&gt;
&lt;p&gt;其中，&lt;span class="math"&gt;\(pot_d\)&lt;/span&gt;为期望的&lt;code&gt;pot&lt;/code&gt;大小，&lt;span class="math"&gt;\(pot\)&lt;/span&gt;为当前提取的&lt;code&gt;pot&lt;/code&gt;大小，&lt;span class="math"&gt;\(n_{have}\)&lt;/span&gt;为使用&lt;span class="math"&gt;\(pot\)&lt;/span&gt;提取到的点的数量，&lt;span class="math"&gt;\(n_{desir}\)&lt;/span&gt;为期望提取的点的数量。&lt;/p&gt;
&lt;div class="admonition important"&gt;
&lt;p class="admonition-title"&gt;递归停止条件&lt;/p&gt;
&lt;p&gt;由于&lt;code&gt;pot&lt;/code&gt;总是一个整数，因此使用整型的&lt;code&gt;pot&lt;/code&gt;可能永远得到不期望的提取数量，因此这里需要设置一个递归的停止条件。 接下来，需要确定一个比例范围&lt;code&gt;ratio&lt;/code&gt;，来定义比期望值多或者比期望值少，&lt;code&gt;DSO&lt;/code&gt;的源码中定义这个比例范围为&lt;span class="math"&gt;\([0.8,1.2]\)&lt;/span&gt;，这个比例是&lt;strong&gt;提取值 / 期望值&lt;/strong&gt;（注意与第0层的比例意义不同）。 首先考虑，&lt;code&gt;pot=1&lt;/code&gt;时，提取到的点仍然远少于期望提取的点，那么需要考虑减小&lt;code&gt;scaleTH&lt;/code&gt;来放宽梯度阈值条件。
当使用的 (提取&lt;span class="math"&gt;\(pot-pot_{d}=0\)&lt;/span&gt;) || (&lt;span class="math"&gt;\(n_{have}/n_{desir}\in[0.8,1.2]\)&lt;/span&gt;) || (达到最大要求递归次数)，则退出递归。&lt;/p&gt;
&lt;/div&gt;
&lt;h2 id="2"&gt;2. 创建关键帧阶段的点选策略&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;DSO&lt;/code&gt;的点选策略除了用在初始化阶段外，还会用在新关键帧的创建阶段。当新关键帧被创建时，&lt;code&gt;DSO&lt;/code&gt;会使用初始化过程中，金字塔第零层点选策略来进行像素点的选取，并初始化为未成熟点，以供后续跟踪过程的逆深度优化。&lt;/p&gt;
&lt;script type="text/javascript"&gt;if (!document.getElementById('mathjaxscript_pelican_#%@#$@#')) {
    var align = "center",
        indent = "0em",
        linebreak = "false";

    if (false) {
        align = (screen.width &lt; 768) ? "left" : align;
        indent = (screen.width &lt; 768) ? "0em" : indent;
        linebreak = (screen.width &lt; 768) ? 'true' : linebreak;
    }

    var mathjaxscript = document.createElement('script');
    mathjaxscript.id = 'mathjaxscript_pelican_#%@#$@#';
    mathjaxscript.type = 'text/javascript';
    mathjaxscript.src = 'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.3/latest.js?config=TeX-AMS-MML_HTMLorMML';

    var configscript = document.createElement('script');
    configscript.type = 'text/x-mathjax-config';
    configscript[(window.opera ? "innerHTML" : "text")] =
        "MathJax.Hub.Config({" +
        "    config: ['MMLorHTML.js']," +
        "    TeX: { extensions: ['AMSmath.js','AMSsymbols.js','noErrors.js','noUndefined.js'], equationNumbers: { autoNumber: 'none' } }," +
        "    jax: ['input/TeX','input/MathML','output/HTML-CSS']," +
        "    extensions: ['tex2jax.js','mml2jax.js','MathMenu.js','MathZoom.js']," +
        "    displayAlign: '"+ align +"'," +
        "    displayIndent: '"+ indent +"'," +
        "    showMathMenu: true," +
        "    messageStyle: 'normal'," +
        "    tex2jax: { " +
        "        inlineMath: [ ['\\\\(','\\\\)'] ], " +
        "        displayMath: [ ['$$','$$'] ]," +
        "        processEscapes: true," +
        "        preview: 'TeX'," +
        "    }, " +
        "    'HTML-CSS': { " +
        "        availableFonts: ['STIX', 'TeX']," +
        "        preferredFont: 'STIX'," +
        "        styles: { '.MathJax_Display, .MathJax .mo, .MathJax .mi, .MathJax .mn': {color: '#145402 ! important'} }," +
        "        linebreaks: { automatic: "+ linebreak +", width: '90% container' }," +
        "    }, " +
        "}); " +
        "if ('default' !== 'default') {" +
            "MathJax.Hub.Register.StartupHook('HTML-CSS Jax Ready',function () {" +
                "var VARIANT = MathJax.OutputJax['HTML-CSS'].FONTDATA.VARIANT;" +
                "VARIANT['normal'].fonts.unshift('MathJax_default');" +
                "VARIANT['bold'].fonts.unshift('MathJax_default-bold');" +
                "VARIANT['italic'].fonts.unshift('MathJax_default-italic');" +
                "VARIANT['-tex-mathit'].fonts.unshift('MathJax_default-italic');" +
            "});" +
            "MathJax.Hub.Register.StartupHook('SVG Jax Ready',function () {" +
                "var VARIANT = MathJax.OutputJax.SVG.FONTDATA.VARIANT;" +
                "VARIANT['normal'].fonts.unshift('MathJax_default');" +
                "VARIANT['bold'].fonts.unshift('MathJax_default-bold');" +
                "VARIANT['italic'].fonts.unshift('MathJax_default-italic');" +
                "VARIANT['-tex-mathit'].fonts.unshift('MathJax_default-italic');" +
            "});" +
        "}";

    (document.body || document.getElementsByTagName('head')[0]).appendChild(configscript);
    (document.body || document.getElementsByTagName('head')[0]).appendChild(mathjaxscript);
}
&lt;/script&gt;</content><category term="DSO"></category><category term="vSLAM"></category><category term="DSO"></category><category term="Robot"></category></entry><entry><title>DSO中的去畸变操作</title><link href="https://sunshanlu.github.io/sunshanlu/De-distortion-in-DSO" rel="alternate"></link><published>2025-01-09T00:00:00+08:00</published><updated>2025-01-09T00:00:00+08:00</updated><author><name>孙善路-github</name></author><id>tag:sunshanlu.github.io,2025-01-09:/sunshanlu/De-distortion-in-DSO</id><summary type="html">
&lt;h2 id="1"&gt;1. 像素去畸变&lt;/h2&gt;
&lt;h3 id="11"&gt;1.1 畸变模型&lt;/h3&gt;
&lt;p&gt;DSO中引入了不同的畸变模型，来解决不同相机的像素畸变问题，除了我们在SLAM十四讲里面熟知的&lt;code&gt;RadTan&lt;/code&gt;畸变模型外，还有&lt;code&gt;FOV&lt;/code&gt;和&lt;code&gt;KannalaBrandt&lt;/code&gt;畸变模型。不同 …&lt;/p&gt;</summary><content type="html">
&lt;h2 id="1"&gt;1. 像素去畸变&lt;/h2&gt;
&lt;h3 id="11"&gt;1.1 畸变模型&lt;/h3&gt;
&lt;p&gt;DSO中引入了不同的畸变模型，来解决不同相机的像素畸变问题，除了我们在SLAM十四讲里面熟知的&lt;code&gt;RadTan&lt;/code&gt;畸变模型外，还有&lt;code&gt;FOV&lt;/code&gt;和&lt;code&gt;KannalaBrandt&lt;/code&gt;畸变模型。不同的像素畸变模型的公式由下面的公式给出：&lt;/p&gt;
&lt;p&gt;首先是我们熟知的&lt;code&gt;RadTan&lt;/code&gt;径向切向畸变模型，可以根据畸变程度选择不同参数数量进行配置计算，常用的有三参数法（&lt;span class="math"&gt;\(k_1\)&lt;/span&gt;,&lt;span class="math"&gt;\(p_1\)&lt;/span&gt;,&lt;span class="math"&gt;\(p_2\)&lt;/span&gt;）和五参数法（&lt;span class="math"&gt;\(k_1\)&lt;/span&gt;,&lt;span class="math"&gt;\(k_2\)&lt;/span&gt;,&lt;span class="math"&gt;\(k_3\)&lt;/span&gt;,&lt;span class="math"&gt;\(p_1\)&lt;/span&gt;,&lt;span class="math"&gt;\(p_2\)&lt;/span&gt;）：&lt;/p&gt;
&lt;div class="math"&gt;$$
\left\{\begin{matrix}
 x_{distorted} = x(1+k_1r^2+k_2r^4+k_3r^6) + 2p_1xy+p_2(r^2+2x^2)\\
 y_{distorted} = y(1+k_1r^2+k_2r^4+k_3r^6) + 2p_2xy+p_1(r^2+2y^2)\\
 r = \sqrt{x^2+y^2}
\end{matrix}\right.
$$&lt;/div&gt;
&lt;p&gt;然后是&lt;code&gt;FOV&lt;/code&gt;视野畸变模型，仅有一个参数&lt;span class="math"&gt;\(\omega\)&lt;/span&gt;：&lt;/p&gt;
&lt;div class="math"&gt;$$
\left\{\begin{matrix} 
 x_{distorted} =\frac{r_{d}}{r}\cdot x\\ 
 y_{distorted} =\frac{r_{d}}{r}\cdot y\\
 r_{d} =\frac{1}{\omega }arctan(2\cdot r\cdot tan(\frac{\omega }{2}))\\
 r = \sqrt{x^2 + y^2}\\
\end{matrix}\right.
$$&lt;/div&gt;
&lt;p&gt;最后是&lt;code&gt;KannalaBrandt&lt;/code&gt;光度畸变模型，有四个参数&lt;span class="math"&gt;\(k_1\)&lt;/span&gt;,&lt;span class="math"&gt;\(k_2\)&lt;/span&gt;,&lt;span class="math"&gt;\(k_3\)&lt;/span&gt;,&lt;span class="math"&gt;\(k_4\)&lt;/span&gt;：&lt;/p&gt;
&lt;div class="math"&gt;$$
\left\{\begin{matrix} 
 x_{distorted} = \frac{\theta _{d}}{r}\cdot x_{c}\\ 
 y_{distorted} = \frac{\theta _{d}}{r}\cdot y_{c}\\
 \theta = atan(r)\\
 \theta_{d}=\theta(1+k_1\theta^2+k_2\theta^4+k_3\theta^6+k_4\theta^8)
\end{matrix}\right.
$$&lt;/div&gt;
&lt;h3 id="12-dsopinhole"&gt;1.2 DSO中畸变模型转Pinhole逻辑&lt;/h3&gt;
&lt;p&gt;当然，这里的畸变模型并不是我想向大家进行解析的重点，&lt;code&gt;DSO&lt;/code&gt;将这种像素畸变模型向小孔成像模型&lt;code&gt;Pinhole&lt;/code&gt;进行了转换，而这个转换的逻辑，是我想给大家分享的重点！这里三种不同的畸变模型，我以&lt;span class="math"&gt;\(d(p,K)\)&lt;/span&gt;来进行表示，这里的&lt;span class="math"&gt;\(p\)&lt;/span&gt;表示归一化坐标系下的某个实际点，而&lt;span class="math"&gt;\(K\)&lt;/span&gt;表示畸变模型的参数。&lt;/p&gt;
&lt;div align="center"&gt;
&lt;img alt="distorted image" src="https://sunshanlu.github.io/sunshanlu/images/distorted.png" width="40%"/&gt;
&lt;img alt="undistorted image" src="https://sunshanlu.github.io/sunshanlu/images/undistorted.png" width="40%"/&gt;
&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;在归一化坐标系上，划定一块足够大的区域&lt;span class="math"&gt;\(S\)&lt;/span&gt;，在&lt;code&gt;DSO&lt;/code&gt;的源码里面是&lt;span class="math"&gt;\(x\in[-5,+5],y\in[-5,+5]\)&lt;/span&gt;，试图通过在&lt;span class="math"&gt;\(x=0\)&lt;/span&gt;和&lt;span class="math"&gt;\(y=0\)&lt;/span&gt;的区域线段上，找到一组极限位置&lt;span class="math"&gt;\(X_{min}\)&lt;/span&gt;、&lt;span class="math"&gt;\(X_{max}\)&lt;/span&gt;、&lt;span class="math"&gt;\(Y_{min}\)&lt;/span&gt;和&lt;span class="math"&gt;\(Y_{max}\)&lt;/span&gt;，作为初始的粗略归一化区域（这个区域包含足够多的像素信息）。&lt;/li&gt;
&lt;li&gt;在&lt;code&gt;X=0&lt;/code&gt;轴方向上，以y划分为10万个刻度点，使用这些点向畸变坐标系进行投影，试图找到一个&lt;span class="math"&gt;\(Y_{min}\)&lt;/span&gt;和&lt;span class="math"&gt;\(Y_{max}\)&lt;/span&gt;，对应的这两个点的像素投影点都应在畸变像素坐标系内。&lt;/li&gt;
&lt;li&gt;在&lt;code&gt;Y=0&lt;/code&gt;轴方向上，使用上面一样的策略，寻找&lt;span class="math"&gt;\(X_{min}\)&lt;/span&gt;和&lt;span class="math"&gt;\(X_{max}\)&lt;/span&gt;。&lt;/li&gt;
&lt;li&gt;在获得的极限位置轴上，取点（点的个数可以是畸变图像的宽度或者高度，以保证足够的分辨率），进行畸变和投影获得畸变像素坐标，判断像素坐标是否在图像范围内。&lt;/li&gt;
&lt;li&gt;如果所有点都在像素图像范围内，则认为这个这个轴的位置是合理的。&lt;/li&gt;
&lt;li&gt;如果存在点不在像素图像范围内，则需要将这个轴的位置进行向内移动，源码中是&lt;code&gt;*0.995&lt;/code&gt;，以此来达到向内移动的效果。&lt;/li&gt;
&lt;li&gt;注意，当X轴和Y轴部分都需要向内移动时，源码中为了防止额外的像素损失，仅尺寸较大的那一轴进行移动。&lt;/li&gt;
&lt;li&gt;总的来说，&lt;code&gt;DSO&lt;/code&gt;在实现畸变模型到纯小孔成像模型之间转换的时候，使用了两步法实现，首先，通过&lt;code&gt;X=0&lt;/code&gt;和&lt;code&gt;Y=0&lt;/code&gt;两个轴上的点确定一个较粗粒度的归一化区域，因为使用的是归一化中心点，因此这个区域肯定可以覆盖绝大部分的可视像素信息。然后再不断的投影四个极限轴上的点来实现轴的缩放，因此来获取一个精确的归一化区域。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;使用上述说明的方式，可以获得一个归一化区域，这个区域内的所有点向畸变像素坐标系投影的结果都会在畸变图像范围内。在给定目标图像的宽度&lt;code&gt;w&lt;/code&gt;和高度&lt;code&gt;h&lt;/code&gt;后，可以计算获得&lt;code&gt;pinhole&lt;/code&gt;条件下的内参矩阵&lt;code&gt;K&lt;/code&gt;:&lt;/p&gt;
&lt;div class="math"&gt;$$
\left\{\begin{matrix}
    f_xX_{min} + c_x = 0\\
    f_yY_{min} + c_y = 0\\
    f_xX_{max} + c_x = w\\
    f_yY_{max} + c_y = h 
\end{matrix}
\right.
$$&lt;/div&gt;
&lt;p&gt;最终可以得到：&lt;/p&gt;
&lt;div class="math"&gt;$$
K = \begin{bmatrix}
    \frac{w}{X_{max}-X_{min}} &amp;amp; 0 &amp;amp; -\frac{wX_{min}}{X_{max}-X_{min}}\\
    0 &amp;amp; \frac{h}{Y_{max}-Y_{min}} &amp;amp; -\frac{hY_{min}}{Y_{max}-Y_{min}}\\
    0 &amp;amp; 0 &amp;amp; 1
\end{bmatrix}
$$&lt;/div&gt;
&lt;p&gt;在得到纯小孔成像模型的相机内参矩阵&lt;code&gt;K&lt;/code&gt;后，为了更加方便的实现畸变图像到非畸变图像的转换，&lt;code&gt;DSO&lt;/code&gt;维护了一组映射表&lt;code&gt;remapX&lt;/code&gt;和&lt;code&gt;remapY&lt;/code&gt;。这组映射表的构建逻辑也比较简单，可以通过下面的这张图进行表示：&lt;/p&gt;
&lt;div align="center"&gt;
&lt;img alt="distorted image" src="https://sunshanlu.github.io/sunshanlu/images/remap.png" width="100%"/&gt;
&lt;/div&gt;
&lt;p&gt;针对无畸变坐标系下的某个像素点&lt;span class="math"&gt;\(p_i\)&lt;/span&gt;，通过反向投影到归一化坐标系下得到&lt;span class="math"&gt;\(p_{norm}\)&lt;/span&gt;，然后使用畸变参数模型在归一化坐标系下对点&lt;span class="math"&gt;\(p_{norm}\)&lt;/span&gt;进行畸变处理得到点&lt;span class="math"&gt;\(p_{norm}^{dis}\)&lt;/span&gt;，然后使用原来的相机内参&lt;span class="math"&gt;\(K_{org}\)&lt;/span&gt;进行投影，得到无畸变像素坐标系点&lt;span class="math"&gt;\(p_i\)&lt;/span&gt;和有畸变像素坐标系&lt;span class="math"&gt;\(p_i^{dis}\)&lt;/span&gt;之间的映射关系。&lt;code&gt;DSO&lt;/code&gt;将这部分对应起来的映射关系存储到&lt;code&gt;remapX&lt;/code&gt;和&lt;code&gt;remapY&lt;/code&gt;中，以便快速的进行无畸变图像的构建。这里还需要注意的是，使用映射表得到畸变像素坐标后，需要使用双线性插值实现像素值或者说是像素能量值（光度去畸变后）的精确获取。&lt;/p&gt;
&lt;h2 id="2"&gt;2. 光度去畸变&lt;/h2&gt;
&lt;h3 id="21"&gt;2.1 光度畸变模型&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;DSO&lt;/code&gt;的作者考虑了相机的成像过程，针对形成漫反射的某个点&lt;code&gt;p&lt;/code&gt;来讲,它会产生辐射&lt;span class="math"&gt;\(B_i(x)\)&lt;/span&gt;，相机的感光器件会接收到这个这个点的辐射&lt;span class="math"&gt;\(B_i(x)\)&lt;/span&gt;。但是由于辐射在相机的透镜中会发生一些扭曲，造成相机成像的渐晕现象（中间部分接收的辐射较高，而周围的较低），这种渐晕会被建模称&lt;span class="math"&gt;\(V(x)\)&lt;/span&gt;，一般会用一张和成像尺寸相同的灰度图来表示，下面是&lt;code&gt;TUM/sequence_29&lt;/code&gt;数据集中的渐晕图表示。&lt;/p&gt;
&lt;div align="center"&gt;
&lt;img alt="渐晕表示" src="https://sunshanlu.github.io/sunshanlu/images/vignette.png" width="60%"/&gt;
&lt;/div&gt;
&lt;p&gt;相机的感光器件在一定的曝光时间&lt;span class="math"&gt;\(t_i\)&lt;/span&gt;内（很短的一段时间内），对辐射进行积分，假设在曝光时间内的辐射恒定那么感光器件获取到的能量值可以通过&lt;span class="math"&gt;\(IR_{acc}(x)=t_iV(x)B_i(x)\)&lt;/span&gt;进行表示。&lt;/p&gt;
&lt;p&gt;除此之外，相机并不会直接将能量作为像素值的输出，而是会经过相机的非线性响应函数&lt;span class="math"&gt;\(G\)&lt;/span&gt;进行转换，&lt;span class="math"&gt;\(I_i(x)=G(t_iV(x)B_i(x))\)&lt;/span&gt;，而&lt;span class="math"&gt;\(G^{-1}\)&lt;/span&gt;一般通过&lt;strong&gt;单调递增&lt;/strong&gt;的&lt;a href="https://sunshanlu.github.io/sunshanlu/files/pcalib.txt"&gt;256个数值&lt;/a&gt;进行表示，即索引部分为像素值，而值部分对应的是某个适应的能量值。&lt;/p&gt;
&lt;p&gt;因为&lt;code&gt;DSO&lt;/code&gt;这种直接法会直接跟像素值打交道，为了避免渐晕&lt;span class="math"&gt;\(V(x)\)&lt;/span&gt;和曝光时间&lt;span class="math"&gt;\(G\)&lt;/span&gt;响应函数的干扰，&lt;code&gt;DSO&lt;/code&gt;在这里进行了光度矫正处理，矫正处理的结果为：&lt;/p&gt;
&lt;div class="math"&gt;$$
I'_i(x)=t_iB_i(x)=\frac{G^{-1}(I_i(x))}{V(x)}
$$&lt;/div&gt;
&lt;p&gt;通过深度思考发现，如果光照恒定的条件下，不同点产生的辐射值&lt;span class="math"&gt;\(B_i(x)\)&lt;/span&gt;是相同的，但是这里的光度矫正为什么还会存在曝光时间&lt;span class="math"&gt;\(t_i\)&lt;/span&gt;的影响呢？&lt;/p&gt;
&lt;p&gt;我认为，这种疑问考虑是完全正确的，这里的光度矫正确实没有去掉曝光时间&lt;span class="math"&gt;\(t_i\)&lt;/span&gt;的影响，但是曝光时间这部分的影响是在优化的能量函数中进行考虑的。能量函数的建模部分我会单独进行讲解，这里只需要记住&lt;code&gt;DSO&lt;/code&gt;会在能量函数建模中考虑曝光时间的影响即可。&lt;/p&gt;
&lt;h3 id="22"&gt;2.2 无光度参数的解决方案&lt;/h3&gt;
&lt;p&gt;在2.1部分，光度畸变模型中，要求需要对相机的光度参数部分进行标定，这会涉及到渐晕&lt;span class="math"&gt;\(V(x)\)&lt;/span&gt;的标定、非线性响应函数&lt;span class="math"&gt;\(G(I)\)&lt;/span&gt;的标定，以及曝光时间的获取。&lt;/p&gt;
&lt;p&gt;然而在实际的数据集中，这些参数绝大部分的数据集是没有的，因此&lt;code&gt;DSO&lt;/code&gt;提供了一种无光度参数的解决方案，即使用仿射函数的方式实现模拟光度校正，并且&lt;strong&gt;认定曝光时间&lt;span class="math"&gt;\(t_i\)&lt;/span&gt;为1&lt;/strong&gt;，即：&lt;/p&gt;
&lt;div class="math"&gt;$$
I'_i(x) = e^{a_i}I_i(x) - b_i
$$&lt;/div&gt;
&lt;p&gt;&lt;code&gt;DSO&lt;/code&gt;的论文里面还这样描述：这里乘法因子使用&lt;span class="math"&gt;\(e^{a_i}\)&lt;/span&gt;而不是直接使用&lt;span class="math"&gt;\(a_i\)&lt;/span&gt;的原因是，使用指数函数，既可以防止其乘法项变为负数，又可以避免因乘法漂移而产生的数值问题。&lt;/p&gt;
&lt;div class="admonition important"&gt;
&lt;p class="admonition-title"&gt;注意去畸变的先后顺序&lt;/p&gt;
&lt;p&gt;&lt;code&gt;DSO&lt;/code&gt;的畸变处理顺序是先进行光度去畸变，在进行像素去畸变，这么做的原因在于，针对渐晕&lt;span class="math"&gt;\(V(x)\)&lt;/span&gt;的表达形式是与原图大小相同的灰度图，因此如果先进行像素去畸变，然后再进行光度去畸变的话，在消除渐晕部分可能会需要&lt;code&gt;remapX&lt;/code&gt;和&lt;code&gt;remapY&lt;/code&gt;的映射表来找到一个真实的渐晕位置，这样还会涉及到双线性插值，会导致计算量上的增加。&lt;/p&gt;
&lt;/div&gt;
&lt;script type="text/javascript"&gt;if (!document.getElementById('mathjaxscript_pelican_#%@#$@#')) {
    var align = "center",
        indent = "0em",
        linebreak = "false";

    if (false) {
        align = (screen.width &lt; 768) ? "left" : align;
        indent = (screen.width &lt; 768) ? "0em" : indent;
        linebreak = (screen.width &lt; 768) ? 'true' : linebreak;
    }

    var mathjaxscript = document.createElement('script');
    mathjaxscript.id = 'mathjaxscript_pelican_#%@#$@#';
    mathjaxscript.type = 'text/javascript';
    mathjaxscript.src = 'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.3/latest.js?config=TeX-AMS-MML_HTMLorMML';

    var configscript = document.createElement('script');
    configscript.type = 'text/x-mathjax-config';
    configscript[(window.opera ? "innerHTML" : "text")] =
        "MathJax.Hub.Config({" +
        "    config: ['MMLorHTML.js']," +
        "    TeX: { extensions: ['AMSmath.js','AMSsymbols.js','noErrors.js','noUndefined.js'], equationNumbers: { autoNumber: 'none' } }," +
        "    jax: ['input/TeX','input/MathML','output/HTML-CSS']," +
        "    extensions: ['tex2jax.js','mml2jax.js','MathMenu.js','MathZoom.js']," +
        "    displayAlign: '"+ align +"'," +
        "    displayIndent: '"+ indent +"'," +
        "    showMathMenu: true," +
        "    messageStyle: 'normal'," +
        "    tex2jax: { " +
        "        inlineMath: [ ['\\\\(','\\\\)'] ], " +
        "        displayMath: [ ['$$','$$'] ]," +
        "        processEscapes: true," +
        "        preview: 'TeX'," +
        "    }, " +
        "    'HTML-CSS': { " +
        "        availableFonts: ['STIX', 'TeX']," +
        "        preferredFont: 'STIX'," +
        "        styles: { '.MathJax_Display, .MathJax .mo, .MathJax .mi, .MathJax .mn': {color: '#145402 ! important'} }," +
        "        linebreaks: { automatic: "+ linebreak +", width: '90% container' }," +
        "    }, " +
        "}); " +
        "if ('default' !== 'default') {" +
            "MathJax.Hub.Register.StartupHook('HTML-CSS Jax Ready',function () {" +
                "var VARIANT = MathJax.OutputJax['HTML-CSS'].FONTDATA.VARIANT;" +
                "VARIANT['normal'].fonts.unshift('MathJax_default');" +
                "VARIANT['bold'].fonts.unshift('MathJax_default-bold');" +
                "VARIANT['italic'].fonts.unshift('MathJax_default-italic');" +
                "VARIANT['-tex-mathit'].fonts.unshift('MathJax_default-italic');" +
            "});" +
            "MathJax.Hub.Register.StartupHook('SVG Jax Ready',function () {" +
                "var VARIANT = MathJax.OutputJax.SVG.FONTDATA.VARIANT;" +
                "VARIANT['normal'].fonts.unshift('MathJax_default');" +
                "VARIANT['bold'].fonts.unshift('MathJax_default-bold');" +
                "VARIANT['italic'].fonts.unshift('MathJax_default-italic');" +
                "VARIANT['-tex-mathit'].fonts.unshift('MathJax_default-italic');" +
            "});" +
        "}";

    (document.body || document.getElementsByTagName('head')[0]).appendChild(configscript);
    (document.body || document.getElementsByTagName('head')[0]).appendChild(mathjaxscript);
}
&lt;/script&gt;</content><category term="DSO"></category><category term="vSLAM"></category><category term="DSO"></category><category term="Robot"></category></entry><entry><title>DSO的算法优势</title><link href="https://sunshanlu.github.io/sunshanlu/Advantages-of-DSO" rel="alternate"></link><published>2025-01-02T00:00:00+08:00</published><updated>2025-01-02T00:00:00+08:00</updated><author><name>孙善路-github</name></author><id>tag:sunshanlu.github.io,2025-01-02:/sunshanlu/Advantages-of-DSO</id><summary type="html">
&lt;h2 id="_1"&gt;介绍&lt;/h2&gt;
&lt;p&gt;&lt;span class="caps"&gt;DSO&lt;/span&gt; 即 Direct Sparse Odometry，直接稀疏里程计。作为vSLAM直接法中开山鼻祖，在vSLAM方法中具有比较高的地位，并且其算法原理考虑内容比较多，代码的实现上更是接近底层（甚至底层到 …&lt;/p&gt;</summary><content type="html">
&lt;h2 id="_1"&gt;介绍&lt;/h2&gt;
&lt;p&gt;&lt;span class="caps"&gt;DSO&lt;/span&gt; 即 Direct Sparse Odometry，直接稀疏里程计。作为vSLAM直接法中开山鼻祖，在vSLAM方法中具有比较高的地位，并且其算法原理考虑内容比较多，代码的实现上更是接近底层（甚至底层到优化过程、字节对齐都是自己实现）。因此我认为DSO是一个非常值得学习和借鉴的vSLAM直接法。&lt;/p&gt;
&lt;p&gt;在这本篇中，我打算着重解释什么是Direct，什么是Indirect；什么是Sparse，什么是Dense。它们之间到底有什么区别，具体的优劣势又在哪？&lt;/p&gt;
&lt;h2 id="1"&gt;1. 直接法 和 非直接法&lt;/h2&gt;
&lt;p&gt;在SLAM十四讲中的第8章（视觉里程计2）中，提到了直接法的概念和基本原理，重点在于像素级别的灰度不变性，通过灰度不变假设构建优化模型。这里与非直接法，通过构建重投影误差有本质区别。&lt;/p&gt;
&lt;h3 id="11"&gt;1.1 直接法&lt;/h3&gt;
&lt;div class="math"&gt;$$
r_k = I_j[p_j] - I_i[p_i] 
$$&lt;/div&gt;
&lt;div class="math"&gt;$$
p_j = \pi(T_{ji} * \frac{\pi^{-1}(p_i)}{d_{pi}})
$$&lt;/div&gt;
&lt;p&gt;正如上面的公式描述的一样，残差应该是ij两帧之间的像素差值。根据灰度不变假设，当&lt;span class="math"&gt;\(p_i\)&lt;/span&gt;和&lt;span class="math"&gt;\(p_j\)&lt;/span&gt;之间描述的是同一个3d空间中的点时，&lt;span class="math"&gt;\(r_k\)&lt;/span&gt;最小，应该为0。为了重点突出灰度不变的假设，并没有考虑仅用一个点投影的非鲁棒性（实际情况下，需要考虑一个patch——像素块的灰度不变假设，这样更鲁邦一些）。&lt;/p&gt;
&lt;div class="math"&gt;$$
\frac{\partial r_k}{\partial \delta \xi_{ji}} = \frac{\partial r_k}{\partial I_j} * \frac{\partial I_j}{\partial p_j} * \frac{\partial p_j}{\partial \delta \xi_{ji}}
$$&lt;/div&gt;
&lt;div class="math"&gt;$$
\frac{\partial r_k}{\partial d_{pi}} = \frac{\partial r_k}{\partial I_j} * \frac{\partial I_j}{\partial p_j} * \frac{\partial p_j}{\partial d_{pi}}
$$&lt;/div&gt;
&lt;p&gt;根据链式法则，可以看到残差对位姿的雅可比矩阵与&lt;span class="math"&gt;\(I_j\)&lt;/span&gt;帧的像素梯度有关系。然而由于图像的非凸性比较明显，在经验上，图像的梯度仅能表示2个像素范围内的灰度变化。因此当使用直接法对图像进行跟踪时，需要较好的初始值，并且当跟踪丢失后，就很难进行重定位了。&lt;/p&gt;
&lt;p&gt;但是由于直接法构建的是灰度之间的差异，而不是特征点的计算。因此直接法在算法运行速度上，要比非直接法快得多。&lt;/p&gt;
&lt;h3 id="12"&gt;1.2 非直接法&lt;/h3&gt;
&lt;p&gt;我最先能想到的非直法就是 &lt;span class="caps"&gt;ORB&lt;/span&gt;-SLAM系列。经典的特征点匹配，通过BA构建重投影误差。针对非直接法来讲，像素的灰度变化并不会导致非直接法的强烈波动。原因在于，非直接法的特征点是由关键点和描述子两部分组成，具有相当强的稳定性。&lt;/p&gt;
&lt;p&gt;非直接法之所以被称为非直接，是因为非直接法需要将得到的数据进行初步的加工处理，而不是直接使用传感器得到的结果。针对视觉SLAM来讲，传感器得到的数据就是像素点的灰度，而非直接法需要根据灰度和像素位置两方面的信息进行某种方式的计算，从而提取出特征点。而这部分计算任务往往是非直接法中最耗时的部分。&lt;/p&gt;
&lt;p&gt;由于非直接法的特征点计算总是和像素梯度强相关，当图像中存在弱梯度时，特征点的提取可能失败，并且图像中的梯度越弱，提取出的特征点的鲁棒性和稳定性相对较差。因此特征点法对图像的梯度较敏感。&lt;/p&gt;
&lt;h3 id="13"&gt;1.3 优缺点汇总&lt;/h3&gt;
&lt;p&gt;根据上面的分析，直接法的优劣势如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;直接法的速度更快，因为不需要计算特征点。&lt;/li&gt;
&lt;li&gt;直接法对图像的梯度不是那么敏感。&lt;/li&gt;
&lt;li&gt;直接法在优化过程中会涉及到像素梯度，因此跟踪失败后，重定位相对困难。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;除此之外，要是以回环闭合的角度去讨论的话，非直接法可能更具优势，因为目前视觉SLAM中比较常用的回环检测方法都是基于词袋的。而词袋方法的直接依据往往都是特征点的描述子。&lt;/p&gt;
&lt;h2 id="2"&gt;2. 稀疏 和 稠密&lt;/h2&gt;
&lt;p&gt;在视觉SLAM中，稀疏和稠密主要是用来描述建图过程中是否会考虑点与点之间的关系。在视觉SLAM中，稀疏的方法往往占据主流，因为以优化的角度去讨论，稀疏方法往往不考虑点与点之间的关系，反映在优化的H矩阵中，表现为稀疏的矩阵，或者说引用视觉SLAM十四讲的内容，应该为一个箭头矩阵。只有这种稀疏的箭头矩阵，才能使用Schur消元的方式去加速事实求解。&lt;/p&gt;
&lt;p&gt;当然，目前也有半稠密的视觉SLAM方法，比如说LSD-&lt;span class="caps"&gt;SLAM&lt;/span&gt;，LSD的方法考虑了点与点之间的连通性关系，但是LSD并没有使用所有的像素点来构建地图，因此在学术上称这种方法为半稠密。LSD使用了GPU，来实时的解一个稠密的H矩阵。&lt;/p&gt;
&lt;p&gt;考虑像素中的所有点，并且考虑点与点之间的关系问题，在SFM问题中比较常见，即3D重建任务（从移动中恢复结构）。这种问题往往都是离线，使用多GPU的方式求解，重点在于恢复3D空间结构，而不是实时的给出相机位姿。&lt;/p&gt;
&lt;script type="text/javascript"&gt;if (!document.getElementById('mathjaxscript_pelican_#%@#$@#')) {
    var align = "center",
        indent = "0em",
        linebreak = "false";

    if (false) {
        align = (screen.width &lt; 768) ? "left" : align;
        indent = (screen.width &lt; 768) ? "0em" : indent;
        linebreak = (screen.width &lt; 768) ? 'true' : linebreak;
    }

    var mathjaxscript = document.createElement('script');
    mathjaxscript.id = 'mathjaxscript_pelican_#%@#$@#';
    mathjaxscript.type = 'text/javascript';
    mathjaxscript.src = 'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.3/latest.js?config=TeX-AMS-MML_HTMLorMML';

    var configscript = document.createElement('script');
    configscript.type = 'text/x-mathjax-config';
    configscript[(window.opera ? "innerHTML" : "text")] =
        "MathJax.Hub.Config({" +
        "    config: ['MMLorHTML.js']," +
        "    TeX: { extensions: ['AMSmath.js','AMSsymbols.js','noErrors.js','noUndefined.js'], equationNumbers: { autoNumber: 'none' } }," +
        "    jax: ['input/TeX','input/MathML','output/HTML-CSS']," +
        "    extensions: ['tex2jax.js','mml2jax.js','MathMenu.js','MathZoom.js']," +
        "    displayAlign: '"+ align +"'," +
        "    displayIndent: '"+ indent +"'," +
        "    showMathMenu: true," +
        "    messageStyle: 'normal'," +
        "    tex2jax: { " +
        "        inlineMath: [ ['\\\\(','\\\\)'] ], " +
        "        displayMath: [ ['$$','$$'] ]," +
        "        processEscapes: true," +
        "        preview: 'TeX'," +
        "    }, " +
        "    'HTML-CSS': { " +
        "        availableFonts: ['STIX', 'TeX']," +
        "        preferredFont: 'STIX'," +
        "        styles: { '.MathJax_Display, .MathJax .mo, .MathJax .mi, .MathJax .mn': {color: '#145402 ! important'} }," +
        "        linebreaks: { automatic: "+ linebreak +", width: '90% container' }," +
        "    }, " +
        "}); " +
        "if ('default' !== 'default') {" +
            "MathJax.Hub.Register.StartupHook('HTML-CSS Jax Ready',function () {" +
                "var VARIANT = MathJax.OutputJax['HTML-CSS'].FONTDATA.VARIANT;" +
                "VARIANT['normal'].fonts.unshift('MathJax_default');" +
                "VARIANT['bold'].fonts.unshift('MathJax_default-bold');" +
                "VARIANT['italic'].fonts.unshift('MathJax_default-italic');" +
                "VARIANT['-tex-mathit'].fonts.unshift('MathJax_default-italic');" +
            "});" +
            "MathJax.Hub.Register.StartupHook('SVG Jax Ready',function () {" +
                "var VARIANT = MathJax.OutputJax.SVG.FONTDATA.VARIANT;" +
                "VARIANT['normal'].fonts.unshift('MathJax_default');" +
                "VARIANT['bold'].fonts.unshift('MathJax_default-bold');" +
                "VARIANT['italic'].fonts.unshift('MathJax_default-italic');" +
                "VARIANT['-tex-mathit'].fonts.unshift('MathJax_default-italic');" +
            "});" +
        "}";

    (document.body || document.getElementsByTagName('head')[0]).appendChild(configscript);
    (document.body || document.getElementsByTagName('head')[0]).appendChild(mathjaxscript);
}
&lt;/script&gt;</content><category term="DSO"></category><category term="vSLAM"></category><category term="DSO"></category><category term="Robot"></category></entry></feed>