SVO使用稀疏直接法计算两帧之间的初始相机位姿,即使用两帧之间稀疏的4*4 patch的光度误差为损失函数,使用G-N优化算法获得两帧之间的位姿变换,由于没有特征匹配过程效率较高。相比自己实现的稀疏直接法的代码,总是难以达到理想的效率,因此分析SVO代码如下:

  首先是入口函数:

//输入的是参考帧和当前帧,即连续的两帧
size_t SparseAlign::run(FramePtr ref_frame, FramePtr cur_frame)
{
reset();
if(ref_frame->fts_.empty())
{
SVO_WARN_STREAM("SparseAlign: no features to track!");
return ;
} ref_frame_ = ref_frame;
cur_frame_ = cur_frame;  //ref_patch_cache_:参考帧patch的缓存,即每行一个特征patch的16个像素灰度值;
ref_patch_cache_ = cv::Mat(ref_frame_->fts_.size(), patch_area_, CV_32F); // create n x 16 matrix  //雅克比矩阵,每一个特征patch的每个像素对应一个6*1的雅克比;
jacobian_cache_.resize(Eigen::NoChange, ref_patch_cache_.rows*patch_area_);
visible_fts_.resize(ref_patch_cache_.rows, false); // TODO: should it be reset at each level? // n x 1  //帧间位姿初始化;
SE3 T_cur_from_ref(cur_frame_->T_f_w_ * ref_frame_->T_f_w_.inverse());  //金字塔迭代,从最高层(即分辨率最低)开始迭代,到最低层(原始图像);
for(level_=max_level_; level_>=min_level_; --level_)
{
  //每次迭代,雅克比都置0;
jacobian_cache_.setZero();
  //这个参数用于控制参考帧patch的缓存ref_patch_cache_是否被初始化;
have_ref_patch_cache_ = false;
if(verbose_)printf("\nPYRAMID LEVEL %i\n---------------\n", level_);
  //迭代优化函数;
optimize(T_cur_from_ref);
} //优化完成之后左乘获得世界坐标系原点到当前帧的变换;
cur_frame_->T_f_w_ = T_cur_from_ref * ref_frame_->T_f_w_; return n_meas_/patch_area_; //返回的是平均特征数量;
}

  迭代优化函数:

void  SparseAlign::optimize(SE3& model)
{
// Save the old model to rollback in case of unsuccessful update
SE3 old_model(model); // perform iterative estimation
for (iter_ = ; iter_<n_iter_; ++iter_)
{
rho_ = ;
//G-N优化过程中的H矩阵,和g矩阵(J*b)
H_.setZero();
Jres_.setZero(); // compute initial error
n_meas_ = ;
//根据两帧之间的初始位姿变换,计算两帧之间patch的残差并返回;
double new_chi2 = computeResiduals(model, true, false); // solve the linear system
if(!solve())
{
//求解出现问题就退出迭代,具体就是出现nan;
stop_ = true;
} // 判断cost是否增长了,正常来说光度误差是下降的,如上升则说明上一次迭代是最优的,并把上轮迭代结果输出
if((iter_ > && new_chi2 > chi2_) || stop_)
{
model = old_model; // rollback
break;
} // 如果光度误差下降了,则更新迭代结果
SE3 new_model;
update(model, new_model); //变量交换,准备下次迭代;
old_model = model;
model = new_model; chi2_ = new_chi2; //没实际意义
finishIteration(); // stop when converged, i.e. update step too small
if(norm_max(x_)<=eps_)
break;
} }

  残差计算函数:

double SparseAlign::computeResiduals(
const SE3& T_cur_from_ref,
bool linearize_system,
bool compute_weight_scale)
{
//当前迭代金字塔层的图像
const cv::Mat& cur_img = cur_frame_->img_pyr_.at(level_); //可忽略
if(linearize_system && display_)
resimg_ = cv::Mat(cur_img.size(), CV_32F, cv::Scalar()); //预计算参考帧特征patch的缓存,即将ref_patch_cache_开辟的存储空间填上相应的值,见下面的分析
//可以暂时认为ref_patch_cache_中已经有值了;
if(have_ref_patch_cache_ == false)
precomputeReferencePatches(); // compute the weights on the first iteration 可忽略
std::vector<float> errors;
if(compute_weight_scale)
errors.reserve(visible_fts_.size()); //下面这段是临时变量
const int stride = cur_img.cols; //类似与OpenCV Mat中的步;
const int border = patch_halfsize_+; //patch的边界;
const float scale = 1.0f/(<<level_); //对应金字塔层,1<<level_表示2的level_次方; const Vector3d ref_pos(ref_frame_->pos()); //参考帧在世界坐标系中的位置,即T(R|t)中的t;
float chi2 = 0.0; //光度误差cost size_t feature_counter = ; // is used to compute the index of the cached jacobian 特征索引;
std::vector<bool>::iterator visiblity_it = visible_fts_.begin();
for(auto it=ref_frame_->fts_.begin(); it!=ref_frame_->fts_.end();
++it, ++feature_counter, ++visiblity_it)
{
// check if feature is within image; visiblity_it参数在预计算过程中初始化;
if(!*visiblity_it)
continue; // compute pixel location in cur img 计算该特征通过位姿变换和相机投影过程后,在当前帧中的像素坐标;
const double depth = ((*it)->point->pos_ - ref_pos).norm(); //通过特征的世界坐标和参考帧的世界坐标计算深度值;
const Vector3d xyz_ref((*it)->f*depth); //f为特征球面归一化之后的相机坐标系下的坐标;
const Vector3d xyz_cur(T_cur_from_ref * xyz_ref); //参考帧特征通过初始化的或上次迭代估计的值计算在当前帧下的特征坐标;
const Vector2f uv_cur_pyr(cur_frame_->cam_->world2cam(xyz_cur).cast<float>() * scale); //通过相机投影变换获得图像像素坐标,注意金字塔scale;
const float u_cur = uv_cur_pyr[]; //像素坐标浮点型和整型,用于双线性插值;
const float v_cur = uv_cur_pyr[];
const int u_cur_i = floorf(u_cur);
const int v_cur_i = floorf(v_cur); // check if projection is within the image
if(u_cur_i < || v_cur_i < || u_cur_i-border < || v_cur_i-border < || u_cur_i+border >= cur_img.cols || v_cur_i+border >= cur_img.rows)
continue;
if(u_cur_i > || v_cur_i > ) continue; // fix segment fault bug // compute bilateral interpolation weights for the current image
//通过双线性插值计算像素光度值;
const float subpix_u_cur = u_cur-u_cur_i; //子像素值;
const float subpix_v_cur = v_cur-v_cur_i; //双线性插值参数,tl:topleft,tr:topright,bl:bottomleft,br:bottomright
const float w_cur_tl = (1.0-subpix_u_cur) * (1.0-subpix_v_cur);
const float w_cur_tr = subpix_u_cur * (1.0-subpix_v_cur);
const float w_cur_bl = (1.0-subpix_u_cur) * subpix_v_cur;
const float w_cur_br = subpix_u_cur * subpix_v_cur; //指向参考帧特征patch的指针:头指针+特征数*每个特征patch的像素个数;
float* ref_patch_cache_ptr = reinterpret_cast<float*>(ref_patch_cache_.data) + patch_area_*feature_counter;
size_t pixel_counter = ; // is used to compute the index of the cached jacobian
for(int y=; y<patch_size_; ++y)
{
//指向当前帧像素值的指针,4*4patch的左上角开始;
uint8_t* cur_img_ptr = (uint8_t*) cur_img.data + (v_cur_i+y-patch_halfsize_)*stride + (u_cur_i-patch_halfsize_); //注意各个指针递增;
for(int x=; x<patch_size_; ++x, ++pixel_counter, ++cur_img_ptr, ++ref_patch_cache_ptr)
{
// compute residual 根据双线性插值计算当前pixel的像素值;
const float intensity_cur = w_cur_tl*cur_img_ptr[] + w_cur_tr*cur_img_ptr[] + w_cur_bl*cur_img_ptr[stride] + w_cur_br*cur_img_ptr[stride+];
//计算残差:当前帧-参考帧
const float res = intensity_cur - (*ref_patch_cache_ptr); // used to compute scale for robust cost 可忽略;
if(compute_weight_scale)
errors.push_back(fabsf(res)); // robustification 可忽略
float weight = 1.0;
if(use_weights_) {
//weight = weight_function_->value(res/scale_);
} //差值平方累加和
chi2 += res*res*weight;
n_meas_++; //求解雅克比过程
if(linearize_system)
{
// compute Jacobian, weighted Hessian and weighted "steepest descend images" (times error)
//取出当前特征对应的雅克比矩阵,因为使用的是逆向组合算法,所以jacobian_cache_预先计算好了,存储了所有特征的雅克比
const Vector6d J(jacobian_cache_.col(feature_counter*patch_area_ + pixel_counter));
H_.noalias() += J*J.transpose()*weight;
Jres_.noalias() -= J*res*weight;
if(display_)
resimg_.at<float>((int) v_cur+y-patch_halfsize_, (int) u_cur+x-patch_halfsize_) = res/255.0;
}
}
}
} //返回的是cost的平均值;
return chi2/n_meas_;
}

  预计算参考帧的相关信息和上面的残差计算过程差不多:

void SparseAlign::precomputeReferencePatches()
{
//临时变量
const int border = patch_halfsize_+; //边界;
const cv::Mat& ref_img = ref_frame_->img_pyr_.at(level_); //参考帧图像;
const int stride = ref_img.cols; //步长;
const float scale = 1.0f/(<<level_); //金字塔层尺度; const Vector3d ref_pos = ref_frame_->pos(); //参考帧位置;
const double focal_length = ref_frame_->cam_->errorMultiplier2();//焦距 size_t feature_counter = ;
std::vector<bool>::iterator visiblity_it = visible_fts_.begin();
for(auto it=ref_frame_->fts_.begin(), ite=ref_frame_->fts_.end();
it!=ite; ++it, ++feature_counter, ++visiblity_it)
{
//在当前金字塔层下的像素坐标值;
// check if reference with patch size is within image
const float u_ref = (*it)->px[]*scale;
const float v_ref = (*it)->px[]*scale;
const int u_ref_i = floorf(u_ref);
const int v_ref_i = floorf(v_ref);
if((*it)->point == NULL || u_ref_i-border < || v_ref_i-border < || u_ref_i+border >= ref_img.cols || v_ref_i+border >= ref_img.rows)
continue; //该特征是否可视在这里改变状态,初始化时为false;
*visiblity_it = true; // cannot just take the 3d points coordinate because of the reprojection errors in the reference image!!!
//通过特征点世界坐标和参考帧位置计算深度值
const double depth(((*it)->point->pos_ - ref_pos).norm());
const Vector3d xyz_ref((*it)->f*depth); // evaluate projection jacobian
//获取2*6的投影雅克比矩阵,该雅克比没有乘以焦距,如下所示
Matrix<double,,> frame_jac;
Frame::jacobian_xyz2uv(xyz_ref, frame_jac); // inline static void jacobian_xyz2uv(
// const Eigen::Vector3d& xyz_in_f,
// Eigen::Matrix<double, 2, 6>& J)
// {
// const double x = xyz_in_f[0];
// const double y = xyz_in_f[1];
// const double z_inv = 1. / xyz_in_f[2];
// const double z_inv_2 = z_inv*z_inv; // J(0, 0) = -z_inv; // -1/z
// J(0, 1) = 0.0; // 0
// J(0, 2) = x*z_inv_2; // x/z^2
// J(0, 3) = y*J(0, 2); // x*y/z^2
// J(0, 4) = -(1.0 + x*J(0, 2)); // -(1.0 + x^2/z^2)
// J(0, 5) = y*z_inv; // y/z // J(1, 0) = 0.0; // 0
// J(1, 1) = -z_inv; // -1/z
// J(1, 2) = y*z_inv_2; // y/z^2
// J(1, 3) = 1.0 + y*J(1, 2); // 1.0 + y^2/z^2
// J(1, 4) = -J(0, 3); // -x*y/z^2
// J(1, 5) = -x*z_inv; // x/z
// } //双线性插值参数,和computeresidual函数中一样
// compute bilateral interpolation weights for reference image
const float subpix_u_ref = u_ref-u_ref_i;
const float subpix_v_ref = v_ref-v_ref_i;
const float w_ref_tl = (1.0-subpix_u_ref) * (1.0-subpix_v_ref);
const float w_ref_tr = subpix_u_ref * (1.0-subpix_v_ref);
const float w_ref_bl = (1.0-subpix_u_ref) * subpix_v_ref;
const float w_ref_br = subpix_u_ref * subpix_v_ref; //cache_ptr:指向ref_patch_cache_的指针,前面仅开辟了内存空间,这里通过指针填值;
size_t pixel_counter = ;
float* cache_ptr = reinterpret_cast<float*>(ref_patch_cache_.data) + patch_area_*feature_counter;
for(int y=; y<patch_size_; ++y)
{
//指向参考帧像素的指针;
uint8_t* ref_img_ptr = (uint8_t*) ref_img.data + (v_ref_i+y-patch_halfsize_)*stride + (u_ref_i-patch_halfsize_);
for(int x=; x<patch_size_; ++x, ++ref_img_ptr, ++cache_ptr, ++pixel_counter)
{
// precompute interpolated reference patch color
//通过双线性插值,给ref_patch_cache_填值;
*cache_ptr = w_ref_tl*ref_img_ptr[] + w_ref_tr*ref_img_ptr[] + w_ref_bl*ref_img_ptr[stride] + w_ref_br*ref_img_ptr[stride+]; // we use the inverse compositional: thereby we can take the gradient always at the same position
// get gradient of warped image (~gradient at warped position)
//计算像素梯度值,0.5*(u[1]-u[-1]), 0.5*(v[1]-v[-1]),其中的每个像素值都使用双线性插值获得
float dx = 0.5f * ((w_ref_tl*ref_img_ptr[] + w_ref_tr*ref_img_ptr[] + w_ref_bl*ref_img_ptr[stride+] + w_ref_br*ref_img_ptr[stride+])
-(w_ref_tl*ref_img_ptr[-] + w_ref_tr*ref_img_ptr[] + w_ref_bl*ref_img_ptr[stride-] + w_ref_br*ref_img_ptr[stride]));
float dy = 0.5f * ((w_ref_tl*ref_img_ptr[stride] + w_ref_tr*ref_img_ptr[+stride] + w_ref_bl*ref_img_ptr[stride*] + w_ref_br*ref_img_ptr[stride*+])
-(w_ref_tl*ref_img_ptr[-stride] + w_ref_tr*ref_img_ptr[-stride] + w_ref_bl*ref_img_ptr[] + w_ref_br*ref_img_ptr[])); // cache the jacobian
//计算像素雅克比,即像素梯度*投影雅克比;
jacobian_cache_.col(feature_counter*patch_area_ + pixel_counter) =
(dx*frame_jac.row() + dy*frame_jac.row())*(focal_length / (<<level_));
}
}
}
//该参数置真,说明ref_patch_cache_已经填值;
have_ref_patch_cache_ = true;
}

  solve()函数和update()函数为基本的矩阵运算函数,reset()函数为参数重置函数,没什么可说的。startIteration()和finishIteration()函数可以忽略。

  总结来看,广泛使用指针,应该比直接取值速度更快,逆向组合算法,预先计算雅克比矩阵可以节省计算量,几处双线性插值方法对提高精度有帮助。相比SVO使用的G-N优化方法,LSD-SLAM中使用的是L-M算法,且明显增加了patch的模块计算部分。

SVO稀疏图像对齐代码分析的更多相关文章

  1. SVO 特征对齐代码分析

    SVO稀疏图像对齐之后使用特征对齐,即通过地图向当前帧投影,并使用逆向组合光流以稀疏图像对齐的结果为初始值,得到更精确的特征位置. 主要涉及文件: reprojector.cpp matcher.cp ...

  2. 完整全面的Java资源库(包括构建、操作、代码分析、编译器、数据库、社区等等)

    构建 这里搜集了用来构建应用程序的工具. Apache Maven:Maven使用声明进行构建并进行依赖管理,偏向于使用约定而不是配置进行构建.Maven优于Apache Ant.后者采用了一种过程化 ...

  3. STM32启动代码分析 IAR 比较好

    stm32启动代码分析 (2012-06-12 09:43:31) 转载▼     最近开始使用ST的stm32w108芯片(也是一款zigbee芯片).开始看他的启动代码看的晕晕呼呼呼的. 还好在c ...

  4. jQuery File Upload 插件 php代码分析

    jquery file upload php代码分析首先进入构造方法 __construct() 再进入 initialize()因为我是post方式传的数据  在进入initialize()中的po ...

  5. Linux kernel的中断子系统之(七):GIC代码分析

    返回目录:<ARM-Linux中断系统>. 总结: 原文地址:<linux kernel的中断子系统之(七):GIC代码分析> 参考代码:http://elixir.free- ...

  6. Android艺术——Bitmap高效加载和缓存代码分析(2)

    Bitmap的加载与缓存代码分析: 图片的压缩 比如有一张1024*768像素的图像要被载入内存,然而最终你要用到的图片大小其实只有128*96,那么我们会浪费很大一部分内存,这显然是没有必要的,下面 ...

  7. 【转载】word2vec原理推导与代码分析

    本文的理论部分大量参考<word2vec中的数学原理详解>,按照我这种初学者方便理解的顺序重新编排.重新叙述.题图来自siegfang的博客.我提出的Java方案基于kojisekig,我 ...

  8. start_kernel之前的汇编代码分析

    start_kernel之前的汇编代码分析 Boot中执行下面两句话之后,进入uclinux内核. theKernel = (void (*)(int, int, unsigned int))((ui ...

  9. C++反汇编代码分析–函数调用

    转载:http://shitouer.cn/2010/06/method-called/ 代码如下:#include “stdlib.h” int sum(int a,int b,int m,int ...

随机推荐

  1. Dotnetcore安装nuget包时不能使用content中的文件

    问题:用NUGET打包了一个asp.netcore的项目,试图安装到另一个asp.netcore项目中,除了自动添加引用外,还希望自动释放一些文件以供修改.这些操作以前在netframe中是正常的,脚 ...

  2. No package gcc48-c++ available

    yum install gcc48-c++ linux 下编译安装 rocksdb,发现没有这个 gcc48-c++,感觉这个48 应该是版本号,于是在 yum install gcc-c++,安装成 ...

  3. 1(1).有监督 VS 无监督

    对比一 : 有标签 vs 无标签 有监督机器学习又被称为“有老师的学习”,所谓的老师就是标签.有监督的过程为先通过已知的训练样本(如已知输入和对应的输出)来训练,从而得到一个最优模型,再将这个模型应用 ...

  4. SQL Server事务复制(sql 2008 r2)

    一.环境准备 1.两个虚拟服务器 主机1:XINXIBU01  作为发布和分发服务器   主 机2:XINXIBU02 192.168.1.160  作业阅服务器 2.SQL SERVER sql 2 ...

  5. C++之指针和引用

    指针和引用的异同点总结 异同点 指针 引用 1 指针是一个变量,本身是一个实体,指针中的内容是一个地址值 该值指向内存中的一个存储单元 引用只是一个别名,实质上指向同一对象 系统不为引用分配内存 2 ...

  6. Java的BIO和NIO很难懂?用代码实践给你看,再不懂我转行!

    本文原题“从实践角度重新理解BIO和NIO”,原文由Object分享,为了更好的内容表现力,收录时有改动. 1.引言 这段时间自己在看一些Java中BIO和NIO之类的东西,也看了很多博客,发现各种关 ...

  7. Springboot整合Mybatis实现级联一对多CRUD操作

    在关系型数据库中,随处可见表之间的连接,对级联的表进行增删改查也是程序员必备的基础技能.关于Spring Boot整合Mybatis在之前已经详细写过,不熟悉的可以回顾Spring Boot整合Myb ...

  8. POJ-2018 Authors Register Update your info Authors ranklist

    Best Cow Fences Time Limit: 1000MS   Memory Limit: 30000K Total Submissions: 16945   Accepted: 5425 ...

  9. QAnet Encoder

    #!/usr/bin/python3# -*- coding: utf-8 -*-'''date: 2019/8/19mail: cally.maxiong@gmail.comblog: http:/ ...

  10. Windows10+texlive2018+texstudio

    texlive2018+texstudio下载链接 链接: https://pan.baidu.com/s/1KjPJnw1kwMBCu3qGT9rIUg 提取码: g8ha 安装texlive 解压 ...