SVO稀疏图像对齐代码分析
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稀疏图像对齐代码分析的更多相关文章
- SVO 特征对齐代码分析
SVO稀疏图像对齐之后使用特征对齐,即通过地图向当前帧投影,并使用逆向组合光流以稀疏图像对齐的结果为初始值,得到更精确的特征位置. 主要涉及文件: reprojector.cpp matcher.cp ...
- 完整全面的Java资源库(包括构建、操作、代码分析、编译器、数据库、社区等等)
构建 这里搜集了用来构建应用程序的工具. Apache Maven:Maven使用声明进行构建并进行依赖管理,偏向于使用约定而不是配置进行构建.Maven优于Apache Ant.后者采用了一种过程化 ...
- STM32启动代码分析 IAR 比较好
stm32启动代码分析 (2012-06-12 09:43:31) 转载▼ 最近开始使用ST的stm32w108芯片(也是一款zigbee芯片).开始看他的启动代码看的晕晕呼呼呼的. 还好在c ...
- jQuery File Upload 插件 php代码分析
jquery file upload php代码分析首先进入构造方法 __construct() 再进入 initialize()因为我是post方式传的数据 在进入initialize()中的po ...
- Linux kernel的中断子系统之(七):GIC代码分析
返回目录:<ARM-Linux中断系统>. 总结: 原文地址:<linux kernel的中断子系统之(七):GIC代码分析> 参考代码:http://elixir.free- ...
- Android艺术——Bitmap高效加载和缓存代码分析(2)
Bitmap的加载与缓存代码分析: 图片的压缩 比如有一张1024*768像素的图像要被载入内存,然而最终你要用到的图片大小其实只有128*96,那么我们会浪费很大一部分内存,这显然是没有必要的,下面 ...
- 【转载】word2vec原理推导与代码分析
本文的理论部分大量参考<word2vec中的数学原理详解>,按照我这种初学者方便理解的顺序重新编排.重新叙述.题图来自siegfang的博客.我提出的Java方案基于kojisekig,我 ...
- start_kernel之前的汇编代码分析
start_kernel之前的汇编代码分析 Boot中执行下面两句话之后,进入uclinux内核. theKernel = (void (*)(int, int, unsigned int))((ui ...
- C++反汇编代码分析–函数调用
转载:http://shitouer.cn/2010/06/method-called/ 代码如下:#include “stdlib.h” int sum(int a,int b,int m,int ...
随机推荐
- [转]UiPath Keyboard Shortcuts
本文转自:https://docs.uipath.com/studio/docs/keyboard-shortcuts The complete list of keyboard shortcuts ...
- JWT基础知识
1> JWT定义 JSON Web Token (JWT) 是一种开放的行业标准(RFC 7519),用于安全传送认证信息. 是目前流行的跨域认证的解决方案. 2> JWT数据结构 由三部 ...
- SQL server 安装成功到使用Sa SQL server验证登录等一系列问题
使用 Windows 身份验证方式登录 出现错误 无法连接到 本地服务器 解决问题: SQL server配置管理器:服务远程过程调用失败 https://blog.csdn.net/gfjjggg/ ...
- log file switch (checkpoint incomplete) - 容易被误诊的event
本文转自 https://blogs.oracle.com/database4cn/log-file-switch-checkpoint-incomplete-%e5%ae%b9%e6%98%93%e ...
- 攻防世界Web新手练习区(1-6)
第一题 view_source 获取在线场景查看网页 打开页面之后首先考虑查看源代码,发现不能右击 根据题目的提示考虑使用view-source查看源代码,发现flag 第二题 get_post 获取 ...
- qt 自定义控件窗口提升
- C++ std::array 基本用法
#include <iostream> #include <string> #include <array> using namespace std; // htt ...
- C++ std::vector 基本用法
#include <iostream> #include <vector> using namespace std; int main() { // 初始化的方式 std::v ...
- ccf-csp201809题解
目录 ccf-csp201809题解 1. 201809-1 卖菜 题目描述 解析 通过代码 2. 201809-2 买菜 题目描述 解析 通过代码 3.201809-3 元素选择器 题目描述 解析 ...
- 使用python - selenium模拟登陆b站
思路 输入用户名密码点击登陆 获取验证码的原始图片与有缺口的图片 找出两张图片的缺口起始处 拖动碎片 功能代码段 # 使用到的库 from selenium import webdriver from ...