DSO代码笔记【待整理】

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/a356337092/article/details/83339652

经过一系列准备工作后

fullSystem->addActiveFrame(img,id);

正式跑DSO。

每个帧的数据存储于FrameHessian结构体中,FrameHessian即是一个带着状态变量与Hessian信息的帧。
Jk = [JI*Jgeo,Jphoto],分别伟图像、几何、光照雅可比

几何和光度的雅可比,相对自变量来说通常是光滑函数;而图像雅可比则依赖图像数据,不够光滑;所以,在优化过程中,几何和光度的雅可比仅在迭代开始时计算一次,此后固定不变[1]。而图像雅可比则随着迭代更新。这种做法称为First-Estimate-Jacobian(FEJ),在VIO中也会经常用到[8]。它可以减小计算量、防止优化往错误的地方走太多,也可以在边缘化过程中保证零空间的维度不会降低

FrameShell.h

类似链表?包含camToTrackingRef和FrameShell*

MatrixAccumulators.hpp

一个累积器有三层缓存如下所示。SSE的话每个缓存器的大小是4*(1+2+…+X),比如acc9就是4*45,普通的话没有4×

name usage num
SSEData 一级缓存 numIn1 1000
SSEData1 二级缓存 numIn1k
SSEData1 三级缓存 numIn1m

update函数进行更新,每个累加器重载。大致都是H= J^T * W * J

void shiftUp(bool force) 是 向上级存储器转换, 每次update后都检查一次,如果低级缓存数量过大或force=true,数值付给高层并且清空低层
finish函数就是强制shiftUp后,全部数据累加到最上层,最后取得数据也就是1m的数据。A=1m。
以上是普通的情况,如果有SSE的话就是4位一起加速,原理一样。

updateSingle函数就是普通更新,updateSSE就是加速更新。
#- [ ] updateSSENoShift存疑 就用到一次,为什么不shift。

各个累加器:
acc9在初始化和track中都有,初始化中,acc9的update输入是dp0-7和r,前8个是光度误差r对se3的六个导数和对a和b的导数, acc9SC

PixelSelector.cpp

makemaps在setFirst和makeNewTraces的时候用到。  
对于高层,选点直接是grid中梯度最大的点makePixelStatus,只在setFirst的时候用  
int PixelSelector::makeMaps(
		const FrameHessian* const fh,
		float* map_out, float density, int recursionsLeft, bool plot, float thFactor)
{
    makeHists(fh);
    Eigen::Vector3i n = this->select(fh, map_out,currentPotential, thFactor);
     n[0]+n[1]+n[2]就是选中的点数,和density决定quotia。  
     然后判断quotia,不合适的话改步长pot再递归。
}

void PixelSelector::makeHists(const FrameHessian* const fh)

对absSquaredGrad处理,先分割成32*32的块,在每块中,遍历每个点{直方图统计梯度0-50,赋值给gradHist. }, 
然后给每块生成一个动态阈值,这个阈值就是直方图中1梯度开始求和,直到=0梯度点的数量 × setting_minGradHistCut时的梯度 ,  
再+setting_minGradHistAdd, 赋值给ths

然后对32*32的块做阈值平滑处理,赋值给thsSmoothed,这就是最终的动态阈值。后续select处理的时候点>>5,正好是对32做一个取值处理。


递归函数实现点管理,即step1,过程如下:
输入参数pot决定了步长。

    Eigen::Vector3i PixelSelector::select(const FrameHessian* const fh,
		  float* map_out, int pot, float thFactor)
		  
4倍步长遍历  
{
    在4倍这个大块中2倍步长遍历  
    {
        在2倍大块中1倍步长遍历  
        {
            挑点的原则是在随机选取的方向上投影模最大,优先选光度,其次dx、dy。  
            分别增加返回的Vector3i的数量。
        }
    }
}

ImmaturePoint.cpp

class ImmaturePoint:
构造函数:hostFrame、生成点的位置、梯度和权重

traceOn():–
初始化后的第一帧的所有候选点idepth_min/max都是1;
更新host上的深度??这个函数是将关键帧上的点与非关键帧影像进行匹配,得到在非关键帧影像上的位置,知道这个信息就可以更新深度了。先进行极线搜索,搜索得到一个比较好的值(energy最小),再使用高斯牛顿优化,找个更好的匹配。最后更新一波深度–只有一个范围没有确切之,idepth_min~idepth_max,

在单目SLAM中,所有地图点在一开始被观测到时,都只有一个2D的像素坐标,其深度是未知的。这种点在DSO中称为未成熟的地图点:Immature Points。随着相机的运动,DSO会在每张图像上追踪这些未成熟的地图点,这个过程称为trace——实际上是一个沿着极线搜索的过程,十分类似于svo的depth filter。Trace的过程会确定每个Immature Point的逆深度和它的变化范围。如果Immature Point的深度(实际中为深度的倒数,即逆深度)在这个过程中收敛,那么我们就可以确定这个未成熟地图点的三维坐标,形成了一个正常的地图点。

linearizeResidual()
calcResidual()
不成熟点过程:
–初始化后,在initialforminitialer时创建,然后在makeKeyFrame中,FullSystem::traceNewCoarse()中通过traceon()设置每个未成熟点的性质,然后activatePointsMT()激活点(实际上是通过FullSystem::optimizeImmaturePoint将好的未成熟点转化为PointHessian),然后后面优化的都是成熟点了。然后FullSystem::makeNewTraces()创建新的未成熟点


HessianBlock.cpp

struct CalibHessian:相机内参

struct PointHessian:–活跃点
构造函数:由ImmaturePoint得到,并设置状态、IdepthScaled
vector<PointFrameResidual*> residuals
isOB()–is out boundary
isInlierNew()

struct FrameHessian:–一个带着状态变量与Hessian信息的帧
vector<PointHessian*> pointHessian pointHessianMarginalized pointHessianOut–包含的所有active点 边缘化点 outlier点
Vec10 state_** 状态变量 前6个是xi(t rot) 后4个是光度a b
EFFrame* efFrame
setState() 更新T

struct FrameFramePrecalc:–恢复RT并计算各种预算量
set()–根据两个FrameHessian计算host到target的点坐标变换矩阵和AffLight – [R t;0 1]=TjTi^-1


Residuals.cpp

class PointFrameResidual: --某点在两帧间的残差
构造函数:param in: PointHessian, host, target
包括 PointHessian和两个FrameHessian-host and target
RawResualJacobian* J的形式见PDF(1.71)

核心函数linearize():计算雅可比J 推导和(1.51-1.70)对应,代码实现对应1.71-1.92,需要遍历每一个 PointFrameResidual将与这个 Residual 相关的导数计算出来,再进行优化。而这些计算出来的相关导数被存储在 RawResidualJacobian 中


CoarseInitialize.cpp

setFirst()–设置第一帧–makeMaps() in PiexlSelector2.cpp–递归函数实现点管理step1,选取候选点。然后makeNN(),

makeNN(): k-最近邻建立kd-tree
每一层是一个KD树,对每层的点找到最近邻的10个点和归一化距离
除了最高层外给每层的点赋值parent,l层的点在l+1层找parent。

trackFrame():–初始化的track,propagateDown以后,**calcResAndGS()**计算残差和海塞、Jb,new frame 相对 ref frame 之间存在 8 个参数需要确定,前 6 个参数是 se(3),后 2 个参数是光度仿射变换的参数。;然后算H,高斯迭代更新姿态和Rt
点的lastHessian就是b的雅可比的最后一位

propagateDown(LVL) – 如果是坏点就用上层parent的深度,好点的话用parent和自己的加权
calcRes():

Vec3f CoarseInitializer::calcResAndGS(
		int lvl, Mat88f &H_out, Vec8f &b_out,
		Mat88f &H_out_sc, Vec8f &b_out_sc,
		const SE3 &refToNew, AffLight refToNew_aff, 
		bool plot)
{
    Accumulator11 E;  emmm这里的11代表1*1 即A是一个数
    对筛选后N个点的每一个点
    {
        算出重投影误差后,求r对[xi a b]的偏导--dp0-7. 
        acc9的H=[Jx21^T*Jx21(8*8) Jx21^T*r21(8*1) 
                r21^T*Jx21(1*8) r21^T*r21(1*1)  ]
        Tx21是N*(N+8),到了每一个点,dp0-7就是Tx21的每一行,r就是r21的每一行
        对于H的更新来说,推出:
        acc9.updateSSE(dp0-7,r);
        acc9SC.updateSingleWeighted()用的是JbBuffer_new,具体见calcResAndGS推导笔记
    }
    acc9.finish(); //可以由上面推出左上角的8*8是xi相关的H,右上角8*1是对应的b
    总之acc9用的是[ap0-7,r],acc9SC用的是[ap0-7*dd,r*dd]和weight dd
    之后就输出E,是res的总能量,代表总误差,alpha能量,和点数。
    里面的alpha好像是加上了和深度1平面的约束
    
}
包括后面calcEC里面似乎也是和深度1平面的约束。

FullSystem.cpp

makeKeyFrame(): traceNewCoarse()遍历所有active帧,所有 不成熟点分别调用traceOn(),更新不成熟点的逆深度。到这为止和NonKeyFrame一样/。调用FullSystem::flagFramesForMarginalization 标记需要被 marg 掉的帧。向FrameHessian结构中插入新的帧。遍历窗口中所有帧的pointHessians,建立它们链接自己 hostframe 与当前帧的 PointFrameResidual,加入到EnergyFunctional 中。调用 FullSystem::activatePointsMT 遍历窗口中所有帧的immaturePoints并激活合适的点。接下来就是调用 FullSystem::optimize 窗口优化。窗口优化完,就是marg掉不需要的帧和点。去外点后,setCoarseTrackingRef去建立以后要用的参考帧,并通过 makeCoarseDepthL0() 建立coarse tracking lastRef的模板。做完这些操作之后,有一个重要的操作是 FullSystem::makeNewTraces 在当前关键帧提取 immaturePoints并生成新的残差,为后面做准备。

trackNewCoarse():先假设匀速运动,fh2slast=slast2preslast ,用此得到 lastF_2_fh=fh2slast^-1*lastF2slast, 再依次按照一帧、两帧、半帧、zero motion的假设,并进行了 a TON of 旋转作为假设,一起加入到总的假设(lastF_2_fh_tries)中。每个假设再用trackNewestCoarse()与最新关键帧匹配。

traceNewCoarse(): 对所有active Frame的不成熟点traceOn()极线搜索,更新未成熟点

activatePointsMT(): 遍历frameHessians里面所有不成熟点,判断canActitive,激活那些离现有点距离最远的候选点,转换成地图点并把剩余的不成熟点删除。

FullSystem::optimize(): 遍历所有成熟点,找到非residuals的残差点,全都放进activeResiduals。然后调用 FullSystem::linearizedAll 计算所有residual 的误差,Energy 求解(1.190-1.200)。然后用for 循环优化了,在这个优化过程中,调用了若干次 FullSystem::linearizedAll()迭代

FullSystem::linearizedAll,这个函数用来计算系统能量,并且对所有的 residual 求导数。

FullSystem::activePointMT(): 用coarseDistanceMap->makeDistanceMap(0)建立距离图(同心圆,所以类似BFS),然后从所有帧的所有未成熟点中筛选出好的,组成toOptimize,并用coarseDistanceMap->addIntoDistFinal()建立最终距离图。 然后new出成熟点PointHessian,调用FullSystem::activePointMT_Reductor()即optimizeImmaturePoint(),优化未成熟点。得到成熟点。然后将这些成熟点加入FrameHessian中,,并加入energyfunction的ef->insertPoint(),ef->insertResidual()

FullSystemOptPoint.cpp

FullSystem::optimizeImmaturePoint(): 对每个未成熟点进行优化,

CoarseTracker.cpp

trackNewestCoarse(): 与最新关键帧匹配。依旧是高斯牛顿
优化,用calcRes函数进行优化。而这个优化只优化相两帧的相对状态(相对位姿 6 + 光度仿射变换 2)。当前
假设得到的结果与前面假设得到的结果比较,当前结果与前面一帧匹配的结果比较(这个跨度有点大,是上一帧),满足条件
就可以跳出了。

calcRes(lvl ……): 计算当前层各点匹配后的残差,当是第0层是要计算一堆sumSquaredShift的东西??,并累加的能量函数

FullSystemOptimize.cpp

FullSystem::optimize():
FullSystem::linearizeAll()::计算所有residual的误差和(整个系统优化需要降低的能量),这里也顺便进行了 导数准备 工作。
FullSystem::linearizeAll_Reductor():对每个PointFrameResidual调用linearize(),计算导数,返回能量stats[0]。


EnergyFuctional.cpp

calcLEnergyF():
calcMEnergyF():
sloveSyestmF():

AccumulatedTopHessianSSE.cpp

addPoint():计算Hessian矩阵 mode=0:ac,1:lineariize,2:marginalize.
AccumulatorApprox 也就是AccumulatedTopHessianSSE::acc 变量的“基
础”类型。这个类型对应着 13x13 的矩阵。
代码中通过 stitchDoubleInternal() 进行帧帧之间的所有的 Point 点对的 accH 的求和(1.121)


流程:

各种参数准备  
CoarseInitialize->setFrist()-设置第一帧
{
    通过makeMaps和makePixelStatus作点管理,根据梯度选点  
    makeNN()建立层内10个最近点和层间parent点的索引  
}
CoarseInitialize->trackFrameWithScaleEstimation()-粗初始化-第二帧开始直接初始化成功 
{
    bool ac = trackFrameMain(newFrameHessian);  //左相机
    bool CoarseInitializer::trackFrameMain(FrameHessian* newFrameHessian)
    {
        resOld = calcResAndGS();
        while(1)
        {
            之前的calc函数算出的H b Hsc bsc,做L-M迭代,算inc
            LM的u越大越接近最速算法,越小越接近GN算法。如果accept, lambda减半,否则增大4倍,u起着步长缩短的阻尼作用。
            对比resNew和resOld,如果新的误差小于旧的,accept,满足条件后跳出
            跳出条件是迭代次数太多、失败太多或inc小于阈值
        }
    }
}

初始化成功后 
{  
    initializeFromInitializer()--为第一帧生成pointHessians 是成熟点,具有逆深度信息的点  
    diliverFrame(fh,**true**)--作为关键帧添加  
}  
-初始化结束,追踪

FullSystem::trackNewCoarse()--返回的res包括ResRMSE和Flow,可以用来判断是否makeKF
{
    得到总假设lastF_2_fh_tries,每次假设用trackNewestCoarse()与最新帧匹配,高斯优化8维inc,找到合适的假设后跳出,得到lastF_2_fh和lastCoarseRMSE等,
}

needToMakeKF,用res判断
diliverFrame(fh,**needToMakeKF**)---makeKeyFrame() or makeNonKeyFrame()

-如果makeKeyFrame():
FullSystem::makeKeyFrame()
{
    FullSystem::traceNewCoarse()-这一步不管是不是KF都做
    {
        for(active Frame)
        {
            for(host->ImmaturePoint)
            {
                ImmaturePoint:traceOn()-更新不成熟点的逆深度。   
            }
        }
    }
    
    FullSystem::flagFramesForMarginalization 标记需要被 marg 掉的帧
    
    向FrameHessian结构中插入新的帧。
    
    ef->insertFrame();
    {
        setAdjointsF();
        connectivityMap;
    }
    ef->setDeltaF();
    
    遍历窗口中所有帧的 pointHessians,建立它们链接自己 hostframe 与当前帧的 PointFrameResidual,加入到 EnergyFunctional 中:
    for(fh1)
    {
        fo(ph)
        {
            PointFrameResidual* r = new PointFrameResidual(ph,fh1,fh);
            //fh--当前 fh1-其他
            ef->insertResidual(r);
        }
    }
    
    调用 FullSystem::activatePointsMT遍历窗口中所有帧的immaturePoints并激活合适的点:
    activatePointsMT()
    {
        for(ph : immaturePoints)
        {
            makeDistanceMap();
            bool canActive;
            if(dist>=)
                toOptimize.push_back(ph);
        }
        activatePointsMT_Reductor(&optimized,&toOptimize);--optimizeImmaturePoint(optimize[k])
        
        for(newpoint:optimize)
        {
            ef->insertPoint(newpoint);
            for(r:newpoint->residuals)
                ef->insertResidual(r);
        }        
    }
    
    ef->makeIDX();
>  感觉,至此为止,把所有FrameHessian(即keyframe)里面的PointHessian(即成熟点)里面的所有PointFrameResidual
加入到EnergyFunctional里面,分别对应EFFrame,EFPoint,EFResidual,接下来可以对此优化了。

    接下来就是调用 FullSystem::optimize 窗口优化。
    {
        FullSystem::optimize()
        {
            在所有帧的所有点的所有 PointFrameResidual 中找到非 linearized的 ,将他们放入变量 activeResiduals 中  
            
            linearizeAll(false)
            {
                linearizeAll_Reductor()
                {
                    对上述每个PointFrameResidual调用linearize(),分别计算两相机的ref和new的残差和雅可比,返回能量stats[0]=lastEnergyP。
                    setNewFrameEnergyTH_full()根据linearize后的残差获得新的阈值-对good点残差排序后前70%
                    
                }
            }
            
            calcLEnergy();--linearized energy, EL=Eab+Ec+Ep
            calcMEnergy();--marginalized energy, EM=(19)
                    
            applyRes_Reductor();--更新变量,efResidual->takeDataF(),计算JpJd
            X的维度是C的4+8*帧数,每帧提供8维变量(xi,a,b)
            
            for(0:mnumOptIts)   //迭代
            {
                backupState();备份一波,如果迭代结果不好了可以退回。
                
                solveSystem();--EnergyFunctional::sloveSyestmF(),迭代更新,
                最终得到新的x ef->lastX, 并通过resubstituteF() 将 x 作用到step上(14) 
                {
                    EnergyFuntional::accumulateAF_MT和EnergyFunctional::accumulateLF_MT区别是addPoint的mode,分别是active和linearize对应0和1   
                    EnergyFunctional::accumulateSCF_MT  消除Hδ中的点部分,只剩下相机位姿和相机内参。在求解 ξ; c后,利用 SC 更新单个 Point 的深度 d。  
                            
                }
                
                previousX=ef->lastX 
                doStepFromBackup(),apply setp to linearization point
                linearizeAll(false)、calcLEnergy()、calcMEnergy()计算新能量  
                判断退出循环条件
                
            }            
        
            ef->setAdjointsF() ?不懂伴随矩阵的意义:it is often necessary to transform a tangent vector from the tangent space around one element to the tangent space of another. The adjoint performs this transformation  
            从李群G到其李代数上的一个线性变换叫做这个李群的伴随表示  
            
            linearizeAll(true)--fixLinearization=true
        }
    }
    
    removeOutliers();
    flagPointsForRemoval();
    ef->dropPointsF();
    getNullspaces();
    ef->marginalizePointsF();
    
    FullSystem::makeNewTraces在当前关键帧提取immaturePoints,这样下一个关键帧处理的时候可以生成 pointHessians 加入到窗口优化过程中。
    
    marginalizeFrame();
    
}

猜你喜欢

转载自blog.csdn.net/a356337092/article/details/83339652
DSO