SVO代码笔记

主要是frame_handler_mono.cpp

关于ros部分不了解.

做的笔记主要来方便保存,不定正确....

主要目的还是想代码 论文结合的整明白.

initialize:

  1.快速的特征检测:返回相机分辨率的宽度,高度,格子数,金字塔层数
  2.边的检测:相同
  3.这是一个绑定函数.(对boost不了解)

  4.深度滤波线程


addImage:
  1.添加一张图像.
  2.判断启动程序是否启动.
  3.清理一些离的更近的关键帧.
  4.清理所有具有重叠视野的关键帧.
  5.创建新的帧
  5.判断帧的类型并根据类型进行相应的操作.
  6.如果当前状态判断为第二帧
    6.1返回第一二关键帧由单映性估计出的Pose的参考帧keypoints坐标集
    6.2返回第一二关键帧由单映性估计出的Pose的当前帧被追踪的keypoints坐标集
    6.3返回第一二关键帧由单映性估计出的Pose的特征点类型
    6.4迭代器的遍历  遍历当前帧和参考帧的特征点
    6.5如果type为1(代表什么?) 画红线 否则画绿线
  7.如果当前状态不是第二帧
    7.1 判断是否为关键帧 如果是,变为蓝色 如果不是 为绿色
    7.2 迭代器的遍历 遍历此帧
    7.3 判断此帧特征类型为边还是点
  8.显示 end debug
  9.将新来的帧赋予last_frame_
  10.判断新来帧为关键帧并且将已将此帧赋予last_frame_  则将新来帧重置.
  11.调用finishFrameProcessingCommon成员函数
     11.1


processFirstFrame()

   1.新建一个新来帧的变换矩阵T_f_w_表示从世界坐标到相机?(Transform (f)rame from (w)orld)
   2.对new_frame_新来帧调用初始化类中的addFirstFrame成员函数判断是否为initialization::FAILURE,如果是则结束,并返回RESULT_NO_KEYFRAME.
     2.1addFirstFrame成员函数 先进行reset()成员函数,清理参考帧keypoints点集,重置参考帧.
     2.2 调用detectFeatures成员函数 对输入的帧 也就是new_frame_帧进行Fast特征检测.输出为对应特征点位置和单位方向向量
         2.2.1 调用feature_detection::FastDetector detector, FastDetector又包含了抽象检测AbstractDetector主要是将图像分为格子,将特征分散
         2.2.2detector.detect 对每层金字塔都进行fast特征检测选择一些得分高的角点放入new_features.
         2.2.3 与2.2.1类似 变成对特征类型为边而已
         2.2.4 与2.2.2类似
         2.2.5 对new_features遍历  返回特征位置和对应的特征单位方向向量
      
    3.经过第二步,如果继续,就可以判断此帧为关键帧,设为关键帧.
       3.1 点开setKeyframe,里面是setKeyPoints (这些点用于快速检测是否两个帧有重叠的视野,选取5个特征,一个在图像中点另外4个靠近图像的4个边角,并且这5个特征都要有对应的3D点)
       3.2 再点开setKeyPoints,如果特征指向的3D点为空,则设置该特征为NULL.再通过调用checkKeyPoints函数,对当前图像中每个特征点进行遍历比较,最终选出最具有代表性的5个作为关键点。实质上是1个靠近图像中心的点和4个靠近图像四个角的点.
    4.不知道为啥我的addKeyframe点不进去...
    5.stage_设为second_frame
    6.将信息"Init: Selected first frame."记录在日志

    7.返回 result_is_keyframe.

processSecondFrame
   1.调用initialization.cpp中addSecondFrame函数返回一个初始化结果.
      1.1调用tracklt,第一帧确定好了之后,然后通过金字塔Lucas-Kanade光流方法计算前期特征的光流(稀疏光流),具体使用OpenCV的方法calcOpticalFlowPyrLK
         1.1.1 创建cv::TermCriteria类型变量termcrit(用于设置迭代算法终止条件)最大迭代次数和所期望的精度,两个加起来一个满足即可.
         1.1.2 Opencv中LK法,具体构造函数(
                        img_prev 帧与帧之间的传递?
                        frame_cur->img_pyr_[0] 当前帧图像的金字塔
                        px_prev, //被跟踪特征点(上帧)坐标集合
                        px_cur, //当前帧特征点坐标集合
                        status,//输出状态向量status,用于表示每个特征是否被找到,找到就置1
                        error,//输出错误向量error,用于表示每个特征的错误(距离误差)。
                        cv::Size2i(klt_win_size, klt_win_size),//设置搜索窗口的大小为klt_win_size*klt_win_size。
                        3,//金字塔总层数为3.
                        termcrit,//终止条件,也就是上一步的那个终止条件
                         cv::OPTFLOW_USE_INITIAL_FLOW//利用px_cur中存储的坐标信息进行初步估计  )
        1.1.3 清理f_cur(当前帧Keypoint方向向量集)和disparities(两帧之间的差距),然后将容器预留跟踪到的当前的keypoints数空间.
        1.1.4 利用for循环剔除没有追踪到的特征点
        1.1.5  将剩余特征点的像素坐标转换为世界坐标 通过frame类的函数c2f(调用cam2world函数)实现。然后再存入f_cur中。
        1.1.6 将特征点移动的像素距离存入disparities中。  norm()范数
        1.1.7 统计特征点特征类型 参考帧和当前帧keypoint坐标集数量  方向向量集  (这里仅仅是统计数量就ok吗??)
        1.1.8 帧的金字塔复制给img_prev
        1.1.9 把 当前帧的keypoints集合给px_prev
        trackKlt函数完事
   1.2 记录日志 "Init: KLT tracked "<< disparities_.size() <<" features"   根据1.1.3 和1.1.6 可以看出,disparities存储着特征点移动的距离 数量为检测到的特征数量
   1.3 如果跟踪到的point数小于设定的最小值 失败 返回FAILURE 结束
   1.4 调用getMedian函数得到disparities_中的中位数作为平均距离,幅值给变量disparity。
   1.5 记录日志"Init: KLT "<<disparity<<"px average disparity."
   1.6 如果中值小于给定配置参数,则表明这一帧不是关键帧,也就是刚开始的时候两帧不能太近
   1.7 计算所有元素之和  得到距离的平均值 内积  (内积/数量-平均值的平方)的开平方 ??
   1.8 计算单映性矩阵 输出 存储内点下标(inliers_)  存储3D点按估计的位姿投影得到的像素坐标(xyz_in_cur)  存储从和上帧到当前帧的位姿变化矩阵
   1.9 记录日志 "Init: Homography RANSAC "<<inliers_.size()<<" inliers." 共找到多少内点
   1.10 若内点数量小于设定阈值,则结束addSecondFrame函数,记录日志并返回FAILURE。
   1.11 调节地图大小以使地图平均深度等于指定比例
       把投影得到的像素坐标的z放在depth_vec容器 然后一个均值 取倒数得到scale
       获得当前帧世界坐标系的位姿 然后对位移添加尺度(pos=-R-1次方乘t)
   1.12 对每个内点 创建3Dpoint 和设置特征添加到这两帧中.
        1.12.1 二维向量px_cur和px_ref用于存储内点特征点的像素坐标
        1.12.2进行判断,若超出可视范围或者深度为负,则跳过,进行下一轮。
              内点特征点坐标(相机坐标系)乘以scale后,得到其世界坐标系坐标,存入指针变量new_point
              调用Frame类成员函数addFeature,将ftr_cur添加进frame_cur的fts(特征点列表)将同一个点对应的特征保存起来,这样点删除了,对应的特征都可以删除             
             调用Point类成员函数addFrameRef将ftr_cur添加进new_point的obs_(可以观测到此特征点的帧的指针的列表)
       1.13 返回成功  到这里整个初始位置的确定就结束了,主要思想就是通过光流跟踪获得对应特征点对,通过对应特征点对估计单应矩阵,将单应矩阵进行分解或者相机外参数据,这个里面主要要注意的是检测到特征的点数,跟踪的特征点数,计算单应矩阵特征的内点数进行阈值限定,以及scale的估计.     
2. 一个判断,若初始化失败或不是关键帧都会结束.
   3.是否使用两帧之间的BA
   4.将此帧设为关键帧.与processFirstFrame()中步骤3相同.
   5.获取平均深度和最小深度.
   6.将关键帧push_back到keyframes_中(List of keyframes in the map).
   7.stage设置为STAGE_DEFAULT_FRAME.
   8.初始化px_cur_和frame_ref_.
   9.记录日志"Init: Selected second frame, triangulated initial map."
   10.返回RESULT_IS_KEYFRAME.

processFrame()
  1.设置初始位姿 将上一帧(last_frame_)的变换矩阵(T_f_w_)赋给当前帧的变换矩阵(T_f_w_). (  理下思路.这里正是贺一家大佬SVO论文分析里面 sparse model-based image alignment的step1.准备工作.)
  2.记录:"sparse_img_align" 图像的稀疏对齐.
  3.SparseImgAlign类型变量img_align,对构造函数进行初始化,图像金字塔最大层和最小层、迭代次数、采用高斯牛顿法、display_和verbose

扫描二维码关注公众号,回复: 2654285 查看本文章

  4.调用 run函数进行对齐.  (通过这个run函数 得到了优化后的粗略位姿(这里的位姿是Frame-to-Frame对齐得到的),也得到n_meas_/patch_area 也就是每个面片检测到的特征数.赋值给img_align_tracked) 

(对于金字塔的处理这一步估计是从金字塔的顶层开始,把上一层的结果作为下一层估计的初始值,最后迭代到底层的。顶层的分辨率最小,所以这是一个由粗到精的过程(Coarse-to-Fine),使得在运动较大时也能有较好的结果。这一步没有找到在哪)

(完全可以使用优化库来解决问题)

     4.1 reset()初始化.

     4.2判断参考帧特征数是否为0 若是记录并结束.
     4.3赋值
     4.4 创建cv::Mat对象(行数为ref_frame_->fts_.size(),,列数为patch_area_(16),数值类型为CV_32F)并赋给ref_patch_cache_.(create  n x 16 matrix).
     4.5//调用resize函数对jacobian_cache_矩阵大小初始化,行不变,列设为ref_patch_cache_.rows*16.   jacobian_cache_矩阵ColMajor按列存储 行为6 列动态
     4.6 初始化向量visible_fts_(这个是什么向量),使其长度为ref_patch_cache_.rows,值均为false。
     4.7创建SE3型变量T_cur_from_ref(用于存储从上帧到当前帧的变换矩阵),初始化值为当前帧变换矩阵 乘以 上帧变换矩阵逆矩阵. Tji=Tjw*Tiw(-1)
     4.8 for循环实现对变换矩阵的优化(level_在优化过程中当前帧的金字塔层数)

         SparseAlign::optimize  这个优化得到了残差和雅克比 和H   n_meas_/patch_area_ 同时优化了残差得到了粗略的位姿.....应该是作者自己写的,看高博知乎上的解答,是一种反向求导的方式.)


         4.8.1 old_model 如果更新失败,保存旧的SE3
         4.8.2 迭代估计

              4.8.2.1 计算残差 赋值给new_chi2 

                     4.8.2.1.1 cur_img赋值为 第level层金字塔图像
                     4.8.2.1.2 如果linearize_system && display_ 就esimg_ = cv::Mat(cur_img.size(), CV_32F, cv::Scalar(0))  
                     4.8.2.1.3 如果have_ref_patch_cache_ == false 就 提前计算参考帧的面片 不展开了..  以上应该是文中注释的 对当前图像进行warp处理以对应参考图像.
                     4.8.2.1.4 在第一次迭代中计算权重
                     4.8.2.1.5 计算缓存的雅克比,对应每个特征
                     4.8.2.1.6 for循环对参考帧特征的遍历
                       4.8.2.1.6.1 检测特征在图像中是否可见
                       4.8.2.1.6.2 计算在当前图像中投影的像素位置
                       4.8.2.1.6.3 这个depth为两个点世界坐标系下相差的范数??
                       4.8.2.1.6.4 得到参考帧的特征在世界坐标系下的坐标,经过位姿转换得到当前帧的,经过world2cam得到相机坐标系下坐标,乘 尺度的像素位置
                       4.8.2.1.6.5 检测投影是否在图像中
                       4.8.2.1.6.6 对当前图像进行双边插值加权
                       4.8.2.1.6.7 不明白这啥float* ref_patch_cache_ptr = reinterpret_cast<float*>(ref_patch_cache_.data) + patch_area_*feature_counter;
                       4.8.2.1.6.8  res = intensity_cur - (*ref_patch_cache_ptr);
                       4.8.2.1.6.9     遍历面片 计算残差chi2 += res*res*weight
                       4.8.2.1.6.10 然后这个n_meas_ ++
                       4.8.2.1.6.11  如果线性系统,计算雅克比,带权重的Hessian和残差图像

              4.8.2.2 判断是否solve, solve是求解过程 若求解过程停止 返回 H和J
              4.8.2.3 判断从上次优化后 误差是否增大,若增大,model=old_model
              4.8.2.4 更新model. 调用了update函数具体的为 乘exp(se3李代数)
              4.8.2.5 更新后的model即 new_model赋值给 model, 以前的model赋值给old_model(主要用来 4.8.2.3的判断)
              4.8.2.6 更新残差
              4.8.2.7 如果verbose_为真  输出 迭代的下标  残差  观测数量  norm_max(x_)  x_是se3的李代数形式 即6*1矩阵
              4.8.2.8 结束迭代估计
     4.9 使用优化后的变换矩阵T_cur_from_ref乘以上一帧的变换矩阵 Tjw=Tji*Tiw (这里优化后的位姿)
     4.10 返回n_meas_/patch_area_(值为16)给img_align_n_tracked  即frame_handler_mono中的函数
  5. 记录日志 图像对齐结束时间
  6. 记录 4.10得到的结果
  7. SVO_DEBUG_STREAM("Img Align:\t Tracked = " << img_align_n_tracked);
  //SVO_DEBUG_STREAM("hyj   Direct  Align, T_f_w:  " << new_frame_->T_f_w_);
  8. 记录重投影开始时间   而这里的重投影是帧与地图( Frame-to-Map)之间的重投影,注意与上面的帧与帧(Frame-to-Frame)之间的区别
  9.reprojectMap  具体是找到与当前帧有相关视野最靠近的N个关键帧 ,这边设置N的最大值为10,靠近的N个关键帧的个数不超过10个。遍历这N个关键帧,对每个关键帧观察到的点投影到当前帧中,记录这每个关键帧与当前帧共同的观察点的个数
     9.1 resetGrid()  grid的顺序做了一次随机排序
     9.2 得到跟当前帧有重叠视野的所有关键帧
     9.3 根据靠近程度进行排序
     9.4 对最近的N个关键帧进行迭代,找到有重叠视野的关键帧
        9.4.1 将参考帧的img()clone赋值给debug_img_
        9.4.2 将参考帧的指针 和 size_t(这表示下标?) push_back到overlap_kfs          
        9.4.3 for循环 将排好序的每个关键帧遍历每个特征点,根据特征点找到对应的map point,再投影到当前帧.
             9.4.3.1 检测这个特征是否有分配的mappoint
             9.4.3.2 确保只投影一次,不同帧上的特征会对应同一个3D点
             9.4.3.3 统计相同观察点的数目
     9.5 reproject candidates (投影那些还未插入关键帧的point_candidates_)
     9.6 feature_align (特征块匹配,类似于光流,位置求精操作)

         9.6.1 这样cur_frame的每个cell里就有了多个投影点, 每个cell只挑选1个质量最好的特征点。注意,这里作者为了速度考虑,不是对图像上的所有cell都挑选,随机挑选了maxFts这么多个cell。这就意味着并不是所有的投影地图点都会来用后面的pose,struct 优化。程序中grid_.cell_order[i]是用随机函数打乱了cell的排序.

         在feature_align中的reprojectCell点进去,有一个findMatchDirect点进去,可以找到贺一家大佬在SVO代码笔记中所说的getCloseViewObs函数.

        n_matches_ 多少个块匹配

         n_trails_在reprojectCell中体现,重投影了多少特征点

  10. 记录重投影结束时间
  11. 进行判断,如果匹配到的特征数小于阈值,则打印没有匹配到足够的特征信息,同时设置当前帧变换矩阵为上帧变换矩阵,设置tracking_quality为TRACKING_INSUFFICIENT,并返回RESULT_FAILURE。
  12. 记录pose_optimizer优化开始时间
  13. 高斯牛顿的优化.(这个不点开了)(根据投影误差丢掉一些误差大的特征点,最后留下来进行位姿优化的这些特征点被变量sfba_n_edges_final记录下来。)
  14. 判断 sfba_n_edges_final 小于20 失败
  15. 结构优化 输入 frame 每次迭代中最大point数 最大迭代次数max_iter
      15.1 对此帧的特征点迭代  如果特征点对应的point不为空 push_back到pts
      15.2 设置最大point数为 min(max_n_pts, pts.size())
      15.3 nth_element(标准模板函数 使第N大元素排在第N个位子,前面的比第N个元素小,后面的大)
      15.4 调用了一个 point.cpp的一个优化函数optimize
      15.5 统计优化次数?
  16. 将当前帧插入core_kfs_(用于存储附近关键帧)
  17. 跟踪质量设置为 sfba_n_edges_final(利用它来判断跟踪质量好不好setTrackingQuality(sfba_n_edges_final). 跟踪不好的判断依据是,用于位姿优化的点数sfba_n_edges_final小于一个阈值,或者比上一帧中用于优化的点减少了很多.)
      17.1 如果被跟踪的特征数量小于设定的值 qualityMinFts 则跟踪bad
      17.2 将feature_drop 设为 sfba_n_edges_final
      17.3 drop/sfba_n_edges_final 值与 0.6比较 若大 则匹配bad
 18. 判断tracking_quality_ ,若等于TRACKING_INSUFFICIENT,同时设置当前帧变换矩阵为上帧变换矩阵,并返回RESULT_FAILURE。
  19. 获取场景最小和平均深度
  20. 根据平均深度判断是否符合关键帧选择标准,若不合适或者tracking_quality_ 值为 TRACKING_BAD,就将当前帧添加入深度滤波器,然后返回RESULT_NO_KEYFRAME
  21.当新的帧new frame和相邻KF的平移量超过场景深度平均值的12%时(比如四轴上升),new frame就会被当做KF.
  22. 记录日志 新的关键帧被选取
  23. 遍历此帧的特征点 将map_.point_candidates_中与当前帧相关的特征点添加到当前帧。
  24. 将当前关键帧添加到深度滤波器。
  25. 如果关键帧的数量达到限制,移除最远一帧

  26. 添加当前关键帧到map_


relocalizeFrame

   1.调用map_.getClosestKeyframe函数查找最近的关键帧并赋给ref_keyframe.

   2.判断ref_keyframe值,若为nullptr,则结束并返回RESULT_FAILURE

   3.调用SparseAlign类并命名为img_align进行图像对齐

   4.调用run函数对齐,赋值n_meas_/patch_area_ (每个面片的特征数)给img_align_n_tracked

  5.如果每个面片匹配特征数大于30,就将上帧变换矩阵赋给T_f_w_last,设置last_frame为ref_keyframe,然后调用processFram()函数,返回值保存到res,然后执行下一步

   

  6. 判断res,若值不等于RESULT_FAILURE,就将stage_设置为STAGE_DEFAULT_FRAME,并打印 重定位成功 信息。否则,就将当前帧变换矩阵设置为T_f_w_last(即最近关键帧变换矩阵)。结束重定位函数,并返回res。

  7. 如果匹配特征数小于等于30,就结束重定位函数,并返回RESULT_FAILURE。



参考:   高博在知乎上对SVO的解答.

            贺一家大佬的SVO: semi-direct visual odometry 论文解析 

           及另一篇博客:SVO代码笔记.

           花心葬空魂的关于SVO的代码分析.



猜你喜欢

转载自blog.csdn.net/weixin_38358435/article/details/80785296