LSD-SLAM的Tracking是算法框架中三大部分之一。其主要实现函数为SlamSystem::trackFrame
这个函数的代码主要分为如下几个步骤实现:
- 构造新的图像帧:把当前图像构建为新的图像帧
- 更新参考帧:如果当前参考帧不是最近的关键帧,则更新参考帧
- 初始化位姿:把上一帧与参考帧的位姿当做初始位姿
- SE3求解:调用SE3Tracker计算当前帧和参考帧间的位姿变换
- 判断是否跟踪失败:根据跟踪的像素点个数多少以及跟踪质量来判断
- 关键帧筛选:通过计算得分确定是否构造新的关键帧
接下来主要介绍SE3求解
欧式变换矩阵 R,t 跟踪求解 SE3Tracker.cpp
这里 是 SE3跟踪求解 欧式变换矩阵求解
* LSD-SLAM在位姿估计中采用了直接法,也就是通过最小化光度误差来求解两帧图像间的位姿变化。
* 并且采用了LM算法迭代求解。
* 误差函数 E(se3) = SUM((Iref(pi) - I(W(pi,Dref(pi),se3))^2)
* (参考帧下的像素值-参考帧3d点变换到当前帧下的像素值)^2
* 也就是 所有匹配点 的光度误差和
* LSD-SLAM在求解两帧图像间的SE3变换主要在SE3Tracker类中进行实现。
求解步骤:
* 1. 首先在参考帧下根据深度信息计算出3d点云
* 2. 其次,计算参考帧下的点变换到当前帧下的亚像素值,进而计算初始 光度匹配误差err
* 3. 再次,利用误差对单个3d点的导数信息计算单个误差的可信程度,进行加权后方差归一化,
* 得到误差置信度权重w,为了减少外点(outliers)对算法的影响
* 4. 然后,利用误差函数对误差向量求导数,求得各个点的误差之间的协方差矩阵,
也是雅克比矩阵J,计算系数A 和 b
* 5. 最后计算 变换矩阵 se3的 更新量 dse3
* dse3 = -(J转置*J)逆矩阵*J转置*w*err 直接求逆矩阵计算量较大
* 也可以写成:
* J转置*J * dse3 = -J转置*w*error
* 紧凑形式:
* A * dse3 = b
* 使用 LDLT分解 A = LDU=LDL转置=LDLT,求解线性方程组 A * x = b
* 记录 u = LD, D为对角矩阵 L为下三角矩阵 L转置为上三角矩阵
* v = L转置
* 将A分解为上面两个矩阵 相乘 A = u * v
* Ax = b就可以化为u(v*x) = u*y = b
* 先求解 y
* 得到 y = v*x
* 再求解 x 即 dse3 ,是欧式变换矩阵se3的更新量
*
* 6. 然后对 变换矩阵 左乘 se3指数映射 进行 更新
* se3 指数映射到SE3 通过 李群乘法直接 对 变换矩阵 左乘更新
该类中有四个函数比较重要,分别为
* 1. SE3Tracker::trackFrame 主调函数
* 2. SE3Tracker::calcResidualAndBuffers 被调用计算
* 初始误差 err 参考帧3d点变换到当前帧图像坐标系下的残差(灰度误差) 和 梯度(对应点像素梯度)
* 3. SE3Tracker::calcWeightsAndResidual 被调用计算
* 误差可信度权重w 并对误差方差归一化得到加权平均后的误差
* 4. SE3Tracker::calculateWarpUpdate 被调用计算
* 误差关系矩阵 协方差矩阵 雅克比矩阵J 以及A=J转置*J b=-J转置*w*error 求接线性方程组
SE3Tracker::trackFrame
* 图像金字塔迭代level-4到level-1
* Step1: 对参考帧当前层构造点云(reference->makePointCloud)
* 利用逆深度信息 和 相应的像素坐标 以及相机内参数得到 在参考帧坐标下的3d点
* (X,Y,Z) = D * (u,v,1)*K逆 =1/(1/D)* (u*fx_inv + cx_inv, v*fy_inv + cy_inv, 1)
* Step2: 计算变换到当前帧的灰度匹配残差和 变换点的灰度梯度(calcResidualAndBuffers)
* 计算参考帧3d点变换到当前帧图像坐标系下的残差(灰度误差) 和 梯度(对应点像素梯度)
* 1. 参考帧3d点 R,t变换到 当前相机坐标系下
* Eigen::Matrix3f rotMat = referenceToFrame.rotationMatrix();// 旋转矩阵
* Eigen::Vector3f transVec = referenceToFrame.translation();// 平移向量
* Eigen::Vector3f Wxp = rotMat * (*refPoint) + transVec;// 欧式变换
* 2. 再 投影到 当前相机 像素平面下
* float u_new = (Wxp[0]/Wxp[2])*fx_l + cx_l;// float 浮点数类型
* float v_new = (Wxp[1]/Wxp[2])*fy_l + cy_l;
* 3. 根据浮点数坐标 四周的四个整数点坐标的梯度 使用位置差加权进行求和得到亚像素灰度值 和 亚像素 梯度值
* x=u_new;
* y=v_new;
* int ix = (int)x;// 取整数 左上方点
* int iy = (int)y;
* float dx = x - ix;// 差值
* float dy = y - iy;
* float dxdy = dx*dy;
* // map 像素梯度 指针
* const Eigen::Vector4f* bp = mat +ix+iy*width;// 左上方点 像素位置 梯度的 指针
* // 使用 左上方点 右上方点 右下方点 左下方点 的灰度梯度信息
* // 以及 相应位置差值作为权重值 线性加权获取 亚像素梯度信息
* resInterp=dxdy * *(const Eigen::Vector3f*)(bp+1+width) // 右下方点
* + (dy-dxdy) * *(const Eigen::Vector3f*)(bp+width)// 左下方点
* + (dx-dxdy) * *(const Eigen::Vector3f*)(bp+1) // 右上方点
* + (1-dx-dy+dxdy) * *(const Eigen::Vector3f*)(bp);// 左上方点
*
* 需要注意的是,在给梯度变量赋值的时候,这里乘了焦距
* 这里求得 亚像素梯度之后 又乘上了 相机内参数,
* 因为在求 误差偏导数时需要 需要用到 dx*fx dy*fy
* *(buf_warped_dx+idx) = fx_l * resInterp[0];// 当前帧匹配点亚像素 梯度 gx = dx*fx
* *(buf_warped_dy+idx) = fy_l * resInterp[1];// 匹配点亚像素 梯度 gy = dy*fy
* *(buf_warped_residual+idx) = residual;// 对应 匹配点 像素误差
* 这里的 dx = resInterp[0], dy = resInterp[0] 亚像素梯度
*
* (buf_d+idx) = 1.0f / (*refPoint)[2]; // 参考帧 Z轴 倒数 = 参考帧逆深度
* *(buf_idepthVar+idx) = (*refColVar)[1];// 参考帧逆深度方差
* 4. 记录变换
* a. 对参考帧灰度 进行一次仿射变换后 和 当前帧亚像素灰度做差得到 残差(灰度误差)
* 现在求得的残差只是纯粹的光度误差
* float c1 = affineEstimation_a * (*refColVar)[0] + affineEstimation_b;// 参考帧 灰度[0] 仿射变换后
* float c2 = resInterp[2];// 当前帧 亚像素 第三维度是图像 灰度值
* float residual = c1 - c2;// 匹配点对 的灰度误差值
*
* 疑问1:代码中对参考帧的灰度做了一个仿射变换的处理,这里的原理是什么?
* 在SVO代码中的确有考虑到两帧见位姿相差过大,
* 因此通过在空间上的仿射变换之后再求灰度的操作。
* 但是在这里的代码中没有看出具体原理。
* b. 两帧下 3d点坐标的 Z轴深度值 的变化比例
* float depthChange = (*refPoint)[2] / Wxp[2];
* usageCount += depthChange < 1 ? depthChange : 1;//记录深度改变的比例
* c. 匹配点总数
* buf_warped_size = idx;// 匹配点数量=包括好的匹配点和不好的匹配点数量
* Step3: 计算 方差归一化加权残差和 使用误差方差 对 误差归一化 后求和 (calcWeightsAndResidual)
* 计算误差权重,归一化方差以及Huber-weight,最终把这个系数存在数组 buf_weight_p 中
* 每个像素点都有一个光度匹配误差,而每个点匹配的可信度根据其偏导数可以求得,
* 加权每一个误差 然后求所有点 光度匹配误差的均值
* 误差导数
drpdd = dr =
-(dx*fx*(tx*z' - tz*x')/(z'^2*d) + dy*fy*(ty*z' - tz*y')/(z'^2*d))
* = -(gx*(tx*z' - tz*x')/(z'^2*d) + gy*(ty*z' - tz*y')/(z'^2*d))
* dx, dy 为参考帧3d点映射到当前帧图像平面后的梯度
* fx, fy 相机内参数
* tx,ty,tz 为参考帧到当前帧的平移变换(忽略旋转变换)
t = translation()
* x',y',z' 为参考帧3d点变换到当前帧坐标系下的3d点
x' = Wxp[0] , y' = Wxp[1] , z' = Wxp[2]
* d = d = *(buf_d) 为参考帧3d点在参考帧下的逆深度
* gx = *(buf_warped_dx)
* gy = *(buf_warped_dy)
* 方差平方 float s = settings.var_weight * *(buf_idepthVar+i);// 参考帧 逆深度方差 平方
* 误差权重为加权方差平方的倒数
float w_p = 1.0f / ((cameraPixelNoise2) + s * drpdd * drpdd);// 误差权重 方差倒数
* 初步误差
rp =*( buf_warped_residual) ; //初步误差
* 加权的误差
float weighted_rp = fabs(rp*sqrtf(w_p));// |r|加权的误差
* Huber-weight权重
* float wh = fabs(weighted_rp < (settings.huber_d/2) ? 1 : (settings.huber_d/2) / weighted_rp);
* 记录 *(buf_weight_p+i) = wh * w_p;
// 乘上 Huber-weight 权重 得到最终误差权重避免外点的影响
* 求和 sumRes += wh * w_p * rp*rp; // 加权误差和
* 取均值 return sumRes / buf_warped_size;// 返回加权误差均值
lastErr = sumRes / buf_warped_size;
* Step4: 计算雅克比向量以及A和b(calculateWarpUpdate)
* J转置*J * dse3 = -J转置*w*lastErr
* 对于参考帧中的一个3D点pi位置处的残差求雅可比,这里需要使用链式求导法则
* Ji = 1/z'*dx*fx + 0* dy *fy
* 0* dx *fx + 1/z' *dy *fy
* -1 * -x'/z'^2 *dx*fx - y'/z'^2 *dy*fy
* -x'*y'/z'^2 *dx*fx - (1+y'^2/z'^2) *dy*fy
* (1+x'^2/z'^2)*dx*fx + x'*y'/z'^2*dy*fy
* - y'/z' *dx*fx + x'/z' * dy*fy
*
* A = J *J转置*(*(buf_weight_p+i))
* b = -J转置*(*(buf_weight_p+i))*lastErr
*
* Step5: 计算得到收敛的delta,并且更新SE3(dse3 = A.ldlt().solve(b))
* for(int i=0;i<6;i++) A(i,i) *= 1+LM_lambda;// (A+λI) 列文伯格马尔夸克更新算法 LM 调整量
* Vector6 inc = A.ldlt().solve(b);// LDLT矩阵分解求解 dse3 李代数更新量
Sophus::SE3f new_referenceToFrame = Sophus::SE3f::exp((inc)) * referenceToFrame;
*
* 重复Step2-Step5直到收敛或者达到最大迭代次数
*
*计算下一层金字塔
#include "SE3Tracker.h"
#include <opencv2/highgui/highgui.hpp>
#include "DataStructures/Frame.h"
#include "Tracking/TrackingReference.h"
#include "util/globalFuncs.h"
#include "IOWrapper/ImageDisplay.h"
#include "Tracking/least_squares.h"
#include <Eigen/Core>
namespace lsd_slam
{
// 指令集优化 X86 SSE指令集优化 ARM NENO指令集优化
// 通过callOptimized这个宏调用 calcResidualAndBuffers 这个函数进行优化操作
#if defined(ENABLE_NEON)
#define callOptimized(function, arguments) function##NEON arguments
#else
#if defined(ENABLE_SSE)
#define callOptimized(function, arguments) (USESSE ? function##SSE arguments : function arguments)
#else
#define callOptimized(function, arguments) function arguments
#endif
#endif
// 使用图像存储 参数和 相机内参数初始化
SE3Tracker::SE3Tracker(int w, int h, Eigen::Matrix3f K)
{
// 图像尺寸
width = w;
height = h;
// 相机内参数
this->K = K;
fx = K(0,0);
fy = K(1,1);
cx = K(0,2);
cy = K(1,2);
// 系统全局配置文件 稠密深度跟踪器设置类
// 定义了一些Tracking相关的设定,比如最大迭代次数,
// 认为收敛的阈值(百分比形式,有些数据认为98%收敛,有些是99%),
// 还有huber距离所需参数等
settings = DenseDepthTrackerSettings();
//settings.maxItsPerLvl[0] = 2;
// 相机内参数 逆 倒数
KInv = K.inverse();
fxi = KInv(0,0);
fyi = KInv(1,1);
cxi = KInv(0,2);
cyi = KInv(1,2);
// 分配内存
buf_warped_residual = (float*)Eigen::internal::aligned_malloc(w*h*sizeof(float));
// 梯度
buf_warped_dx = (float*)Eigen::internal::aligned_malloc(w*h*sizeof(float));
buf_warped_dy = (float*)Eigen::internal::aligned_malloc(w*h*sizeof(float));
// 坐标
buf_warped_x = (float*)Eigen::internal::aligned_malloc(w*h*sizeof(float));
buf_warped_y = (float*)Eigen::internal::aligned_malloc(w*h*sizeof(float));
buf_warped_z = (float*)Eigen::internal::aligned_malloc(w*h*sizeof(float));
// 逆深度
buf_d = (float*)Eigen::internal::aligned_malloc(w*h*sizeof(float));
// 逆深度方差
buf_idepthVar = (float*)Eigen::internal::aligned_malloc(w*h*sizeof(float));
// 计算权重
buf_weight_p = (float*)Eigen::internal::aligned_malloc(w*h*sizeof(float));
buf_warped_size = 0;
// 可视化Tracking的迭代过程 需要的 图像
debugImageWeights = cv::Mat(height,width,CV_8UC3);
debugImageResiduals = cv::Mat(height,width,CV_8UC3);// 残差
debugImageSecondFrame = cv::Mat(height,width,CV_8UC3);
debugImageOldImageWarped = cv::Mat(height,width,CV_8UC3);
debugImageOldImageSource = cv::Mat(height,width,CV_8UC3);
lastResidual = 0;
iterationNumber = 0;
pointUsage = 0;
lastGoodCount = lastBadCount = 0;// 计数器
diverged = false;
}
// 类析构函数
SE3Tracker::~SE3Tracker()
{
// cv::Mat 对象 自带的 释放存储空间的 方法
debugImageResiduals.release();
debugImageWeights.release();
debugImageSecondFrame.release();
debugImageOldImageSource.release();
debugImageOldImageWarped.release();
// Eigen对象的 内存释放 方法
Eigen::internal::aligned_free((void*)buf_warped_residual);
Eigen::internal::aligned_free((void*)buf_warped_dx);// 梯度
Eigen::internal::aligned_free((void*)buf_warped_dy);
Eigen::internal::aligned_free((void*)buf_warped_x);// 坐标
Eigen::internal::aligned_free((void*)buf_warped_y);
Eigen::internal::aligned_free((void*)buf_warped_z);
Eigen::internal::aligned_free((void*)buf_d);// 逆深度
Eigen::internal::aligned_free((void*)buf_idepthVar);// 逆深度方差
Eigen::internal::aligned_free((void*)buf_weight_p);// 权重
}
// tracks a frame.
// first_frame has depth, second_frame DOES NOT have depth.
// 第一帧 参考帧 3d点 按照 欧式变换矩阵 以及当前帧的第4层相机内参数 变换到图像平面下
// 看映射后的坐标是否合理,以及看两个坐标系下点坐标的 Z轴深度信息的变化
float SE3Tracker::checkPermaRefOverlap(
Frame* reference,
SE3 referenceToFrameOrg)
{
Sophus::SE3f referenceToFrame = referenceToFrameOrg.cast<float>();// 欧式变换矩阵
// 上锁
boost::unique_lock<boost::mutex> lock2 = boost::unique_lock<boost::mutex>(reference->permaRef_mutex);
int w2 = reference->width(QUICK_KF_CHECK_LVL)-1;// 第4层
int h2 = reference->height(QUICK_KF_CHECK_LVL)-1;
Eigen::Matrix3f KLvl = reference->K(QUICK_KF_CHECK_LVL);
float fx_l = KLvl(0,0);// 第四层 相机内参数
float fy_l = KLvl(1,1);
float cx_l = KLvl(0,2);
float cy_l = KLvl(1,2);
Eigen::Matrix3f rotMat = referenceToFrame.rotationMatrix();// 旋转矩阵
Eigen::Vector3f transVec = referenceToFrame.translation();// 平移向量
const Eigen::Vector3f* refPoint = reference->permaRef_posData;// 参考帧3d点 存储的起始指针
const Eigen::Vector3f* refPoint_max = reference->permaRef_posData + reference->permaRefNumPts;// 最大位置
float usageCount = 0;
for(;refPoint<refPoint_max; refPoint++)
{
Eigen::Vector3f Wxp = rotMat * (*refPoint) + transVec;// 变换到当前图像坐标系下 的坐标
float u_new = (Wxp[0]/Wxp[2])*fx_l + cx_l;// 对应图像上的像素坐标
float v_new = (Wxp[1]/Wxp[2])*fy_l + cy_l;
if((u_new > 0 && v_new > 0 && u_new < w2 && v_new < h2))//3d点 映射到 图像平面上 2d点坐标需要在合理范围内
{
float depthChange = (*refPoint)[2] / Wxp[2];// 两个坐标系下 逆深度的变换比例
usageCount += depthChange < 1 ? depthChange : 1;// 按照最大值1 记录总和
}
}
pointUsage = usageCount / (float)reference->permaRefNumPts;// 变换 均值
return pointUsage;
}
// tracks a frame.
// first_frame has depth, second_frame DOES NOT have depth.
SE3 SE3Tracker::trackFrameOnPermaref(
Frame* reference,
Frame* frame,
SE3 referenceToFrameOrg)
{
Sophus::SE3f referenceToFrame = referenceToFrameOrg.cast<float>();
boost::shared_lock<boost::shared_mutex> lock = frame->getActiveLock();
boost::unique_lock<boost::mutex> lock2 = boost::unique_lock<boost::mutex>(reference->permaRef_mutex);
affineEstimation_a = 1; affineEstimation_b = 0;
NormalEquationsLeastSquares ls;
diverged = false;
trackingWasGood = true;
callOptimized(calcResidualAndBuffers, (reference->permaRef_posData, reference->permaRef_colorAndVarData, 0, reference->permaRefNumPts, frame, referenceToFrame, QUICK_KF_CHECK_LVL, false));
if(buf_warped_size < MIN_GOODPERALL_PIXEL_ABSMIN * (width>>QUICK_KF_CHECK_LVL)*(height>>QUICK_KF_CHECK_LVL))
{
diverged = true;
trackingWasGood = false;
return SE3();
}
if(useAffineLightningEstimation)
{
affineEstimation_a = affineEstimation_a_lastIt;
affineEstimation_b = affineEstimation_b_lastIt;
}
float lastErr = callOptimized(calcWeightsAndResidual,(referenceToFrame));
float LM_lambda = settings.lambdaInitialTestTrack;
for(int iteration=0; iteration < settings.maxItsTestTrack; iteration++)
{
callOptimized(calculateWarpUpdate,(ls));
int incTry=0;
while(true)
{
// solve LS system with current lambda
Vector6 b = -ls.b;
Matrix6x6 A = ls.A;
for(int i=0;i<6;i++) A(i,i) *= 1+LM_lambda;
Vector6 inc = A.ldlt().solve(b);
incTry++;
// apply increment. pretty sure this way round is correct, but hard to test.
Sophus::SE3f new_referenceToFrame = Sophus::SE3f::exp((inc)) * referenceToFrame;
// re-evaluate residual
callOptimized(calcResidualAndBuffers, (reference->permaRef_posData, reference->permaRef_colorAndVarData, 0, reference->permaRefNumPts, frame, new_referenceToFrame, QUICK_KF_CHECK_LVL, false));
if(buf_warped_size < MIN_GOODPERALL_PIXEL_ABSMIN * (width>>QUICK_KF_CHECK_LVL)*(height>>QUICK_KF_CHECK_LVL))
{
diverged = true;
trackingWasGood = false;
return SE3();
}
float error = callOptimized(calcWeightsAndResidual,(new_referenceToFrame));
// accept inc?
if(error < lastErr)
{
// accept inc
referenceToFrame = new_referenceToFrame;
if(useAffineLightningEstimation)
{
affineEstimation_a = affineEstimation_a_lastIt;
affineEstimation_b = affineEstimation_b_lastIt;
}
// converged?
if(error / lastErr > settings.convergenceEpsTestTrack)
iteration = settings.maxItsTestTrack;
lastErr = error;
if(LM_lambda <= 0.2)
LM_lambda = 0;
else
LM_lambda *= settings.lambdaSuccessFac;
break;
}
else
{
if(!(inc.dot(inc) > settings.stepSizeMinTestTrack))
{
iteration = settings.maxItsTestTrack;
break;
}
if(LM_lambda == 0)
LM_lambda = 0.2;
else
LM_lambda *= std::pow(settings.lambdaFailFac, incTry);
}
}
}
lastResidual = lastErr;
trackingWasGood = !diverged
&& lastGoodCount / (frame->width(QUICK_KF_CHECK_LVL)*frame->height(QUICK_KF_CHECK_LVL)) > MIN_GOODPERALL_PIXEL
&& lastGoodCount / (lastGoodCount + lastBadCount) > MIN_GOODPERGOODBAD_PIXEL;
return toSophus(referenceToFrame);
}
////////////////////////////////////////////////////////////////////////////////////////
////////////////// 最重要的跟踪匹配 函数////////////////////////////////////////////
// SE3Tracker::trackFrame函数的主体是一个for循环,从图像金字塔的高层level-4开始遍历直到底层level-1。
// 在循环内实现加权高斯牛顿优化算法(Weighted Gauss-Newton Optimization),
// 其实就是增加了鲁棒函数的高斯牛顿。
// tracks a frame.
// first_frame has depth, second_frame DOES NOT have depth.
SE3 SE3Tracker::trackFrame(
TrackingReference* reference,// 参考帧,第一帧 含有 逆深度信息
Frame* frame,// 当前帧 第二帧 没有深度 需要匹配后 获取
const SE3& frameToReference_initialEstimate)// 初始变换矩阵 fram 到参考帧 的 变换矩阵
// 对于初始位姿,LSD-SLAM中使用了上一帧和参考帧间的位姿作为当前位姿估计的初值
{
// 内存上锁
boost::shared_lock<boost::shared_mutex> lock = frame->getActiveLock();
diverged = false;
trackingWasGood = true;
affineEstimation_a = 1; affineEstimation_b = 0;// 仿射变换参数
// 保存 跟踪 信息
if(saveAllTrackingStages)
{
saveAllTrackingStages = false;
saveAllTrackingStagesInternal = true;
}
// 初始化 跟踪迭代信息
if (plotTrackingIterationInfo)
{
const float* frameImage = frame->image();// 当前帧 第0层 图像
for (int row = 0; row < height; ++ row)// 每行
for (int col = 0; col < width; ++ col)// 每列
// 按照初始图像像素灰度值 设置 颜色
setPixelInCvMat(&debugImageSecondFrame,getGrayCvPixel(frameImage[col+row*width]), col, row, 1);
}
// ============ track frame ============
// 首先将初始估计记录下来(记录了参考帧到当前帧的 欧式变换矩阵)
// 参考帧 到 当前帧 fram 的 变换矩阵
Sophus::SE3f referenceToFrame = frameToReference_initialEstimate.inverse().cast<float>();
NormalEquationsLeastSquares ls;// 然后定义一个6自由度矩阵的误差判别计算对象ls
int numCalcResidualCalls[PYRAMID_LEVELS];// cell数量 5层 金字塔
int numCalcWarpUpdateCalls[PYRAMID_LEVELS];//
float last_residual = 0;// 最终的残差
// 函数的主体是一个for循环
for(int lvl=SE3TRACKING_MAX_LEVEL-1;lvl >= SE3TRACKING_MIN_LEVEL;lvl--)// 在每一层金字塔上进行跟踪
{
// 从最高层的 金字塔图像(尺寸最小) 开始 向下计算
// 从图像金字塔的高层level-4开始遍历直到底层level-1
numCalcResidualCalls[lvl] = 0;
numCalcWarpUpdateCalls[lvl] = 0;
// 【1】 有参考帧逆深度信息和对应像素点坐标 计算 在参考帧坐标系下的3d 点
reference->makePointCloud(lvl);
//【2】计算参考帧3d点变换到当前帧图像坐标系下的残差(灰度误差) 和 梯度(对应点像素梯度)
// 计算匹配 点对 像素误差 在这个函数中,把 buf_warped 相关的参数全部更新,并且更新了上次的相似变换参数等
callOptimized(calcResidualAndBuffers, (reference->posData[lvl], reference->colorAndVarData[lvl], SE3TRACKING_MIN_LEVEL == lvl ? reference->pointPosInXYGrid[lvl] : 0, reference->numData[lvl], frame, referenceToFrame, lvl, (plotTracking && lvl == SE3TRACKING_MIN_LEVEL)));
// buf_warped_size 记录了 匹配点数量 包括好的匹配点 和 不好的匹配点 数量 (只要投影后在图像范围内就可以)
if(buf_warped_size < MIN_GOODPERALL_PIXEL_ABSMIN * (width>>lvl)*(height>>lvl))
{// 如果匹配点数量过少,已经少于了这一层图像像素数量的1%),那么我们认为差别太大,Tracking失败,返回一个空的SE3
diverged = true;
trackingWasGood = false;// Tracking失败
return SE3();// 返回一个空的SE3
}
// 使用 仿射变换参数
// 如果使用了,那么把通过calcResidualAndBuffers函数更新的affineEstimation_a_lastIt以及affineEstimation_b_lastIt,赋值给仿射变换系数
if(useAffineLightningEstimation)
{
affineEstimation_a = affineEstimation_a_lastIt;
affineEstimation_b = affineEstimation_b_lastIt;
}
// 【3】使用误差协方差(误差求导) 对方差归一化 后求和 调用calcWeightsAndResidual得到误差,并记录调用次数
// 以及误差权重矩阵 避免外点的影响
float lastErr = callOptimized(calcWeightsAndResidual,(referenceToFrame));
numCalcResidualCalls[lvl]++;
float LM_lambda = settings.lambdaInitial[lvl];// λ LM优化的 调整量
// 【4】每一层进行 LM优化 (A+λI)x=b 在最大迭代次数范围内进行优化
for(int iteration=0; iteration < settings.maxItsPerLvl[lvl]; iteration++)
{
// 计算 加权平均误差 以及误差权重
callOptimized(calculateWarpUpdate,(ls));
numCalcWarpUpdateCalls[lvl]++;
iterationNumber = iteration;// 迭代次数
int incTry=0;
while(true)
{
// solve LS system with current lambda
Vector6 b = -ls.b;
Matrix6x6 A = ls.A;
// 列文伯格马尔夸克优化算法更新
for(int i=0;i<6;i++) A(i,i) *= 1+LM_lambda;// (A+λI) LM 调整量
Vector6 inc = A.ldlt().solve(b);// LDLT矩阵分解求解 dse3 李代数更新量
incTry++;
// 对 变换矩阵 左乘 se3指数映射 进行 更新
// apply increment. pretty sure this way round is correct, but hard to test.
Sophus::SE3f new_referenceToFrame = Sophus::SE3f::exp((inc)) * referenceToFrame;
//Sophus::SE3f new_referenceToFrame = referenceToFrame * Sophus::SE3f::exp((inc));
// re-evaluate residual
callOptimized(calcResidualAndBuffers, (reference->posData[lvl], reference->colorAndVarData[lvl], SE3TRACKING_MIN_LEVEL == lvl ? reference->pointPosInXYGrid[lvl] : 0, reference->numData[lvl], frame, new_referenceToFrame, lvl, (plotTracking && lvl == SE3TRACKING_MIN_LEVEL)));
if(buf_warped_size < MIN_GOODPERALL_PIXEL_ABSMIN* (width>>lvl)*(height>>lvl))
{// 匹配点数量记录 包括好的匹配点 和 不好的匹配点 数量
// 匹配点数量少于阈值 优化失败
diverged = true;
trackingWasGood = false;// 优化失败
return SE3();// 返回空
}
// 新变换下的误差
float error = callOptimized(calcWeightsAndResidual,(new_referenceToFrame));
numCalcResidualCalls[lvl]++;
// accept inc?
// 误差变小了
if(error < lastErr)// 误差变小了
{
// accept inc
// 更新 新求解的 变换矩阵
referenceToFrame = new_referenceToFrame;
if(useAffineLightningEstimation)
{// 更新 仿射变换系数
affineEstimation_a = affineEstimation_a_lastIt;
affineEstimation_b = affineEstimation_b_lastIt;
}
// 打印调试信息
if(enablePrintDebugInfo && printTrackingIterationInfo)
{
// debug output
printf("(%d-%d): ACCEPTED increment of %f with lambda %.1f, residual: %f -> %f\n",
lvl,iteration, sqrt(inc.dot(inc)), LM_lambda, lastErr, error);
printf(" p=%.4f %.4f %.4f %.4f %.4f %.4f\n",
referenceToFrame.log()[0],referenceToFrame.log()[1],referenceToFrame.log()[2],
referenceToFrame.log()[3],referenceToFrame.log()[4],referenceToFrame.log()[5]);
}
// converged?
if(error / lastErr > settings.convergenceEps[lvl])
{
if(enablePrintDebugInfo && printTrackingIterationInfo)
{
printf("(%d-%d): FINISHED pyramid level (last residual reduction too small).\n",
lvl,iteration);
}
iteration = settings.maxItsPerLvl[lvl];
}
last_residual = lastErr = error;
if(LM_lambda <= 0.2)
LM_lambda = 0;
else
LM_lambda *= settings.lambdaSuccessFac;
break;
}
// 误差反而没有减少
else
{
if(enablePrintDebugInfo && printTrackingIterationInfo)
{
printf("(%d-%d): REJECTED increment of %f with lambda %.1f, (residual: %f -> %f)\n",
lvl,iteration, sqrt(inc.dot(inc)), LM_lambda, lastErr, error);
}
if(!(inc.dot(inc) > settings.stepSizeMin[lvl]))
{
if(enablePrintDebugInfo && printTrackingIterationInfo)
{
printf("(%d-%d): FINISHED pyramid level (stepsize too small).\n",
lvl,iteration);
}
iteration = settings.maxItsPerLvl[lvl];
break;
}
// 调整 LM 参数
if(LM_lambda == 0)
LM_lambda = 0.2;
else
LM_lambda *= std::pow(settings.lambdaFailFac, incTry);
}
}
}
}
if(plotTracking)
Util::displayImage("TrackingResidual", debugImageResiduals, false);
if(enablePrintDebugInfo && printTrackingIterationInfo)
{
printf("Tracking: ");
for(int lvl=PYRAMID_LEVELS-1;lvl >= 0;lvl--)
{
printf("lvl %d: %d (%d); ",
lvl,
numCalcResidualCalls[lvl],
numCalcWarpUpdateCalls[lvl]);
}
printf("\n");
}
saveAllTrackingStagesInternal = false;
lastResidual = last_residual;
trackingWasGood = !diverged
&& lastGoodCount / (frame->width(SE3TRACKING_MIN_LEVEL)*frame->height(SE3TRACKING_MIN_LEVEL)) > MIN_GOODPERALL_PIXEL
&& lastGoodCount / (lastGoodCount + lastBadCount) > MIN_GOODPERGOODBAD_PIXEL;
if(trackingWasGood)
reference->keyframe->numFramesTrackedOnThis++;
frame->initialTrackedResidual = lastResidual / pointUsage;
frame->pose->thisToParent_raw = sim3FromSE3(toSophus(referenceToFrame.inverse()),1);
frame->pose->trackingParent = reference->keyframe->pose;
return toSophus(referenceToFrame.inverse());
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// 每个像素点都有一个光度匹配误差,而每个点匹配的可信度根据其偏导数可以求得,
// 加权每一个误差 然后求所有点 光度匹配误差的均值
// 再者 计算误差的权重,在优化更新函数中作为权重
// 使用 误差方差对方差归一化 后求和
// 计算权重 和 像素匹配误差
// 该函数的功能是计算归一化方差的光度误差系数,也就是计算公式(14),
// 并且乘以了Huber-weight,最终把这个系数存在数组buf_weight_p中。
// 可能是考虑参考帧到当前帧的位姿变换比较小,
// 所以作者只考虑了位移t而忽略旋转R。
// 这样使得式子(14)中的偏导的形式简单了很多。
// 这里主要求 误差函数的导数 即得到 误差的协方差矩阵
#if defined(ENABLE_SSE)
float SE3Tracker::calcWeightsAndResidualSSE(
const Sophus::SE3f& referenceToFrame)
{
const __m128 txs = _mm_set1_ps((float)(referenceToFrame.translation()[0]));
const __m128 tys = _mm_set1_ps((float)(referenceToFrame.translation()[1]));
const __m128 tzs = _mm_set1_ps((float)(referenceToFrame.translation()[2]));
const __m128 zeros = _mm_set1_ps(0.0f);
const __m128 ones = _mm_set1_ps(1.0f);
const __m128 depthVarFacs = _mm_set1_ps((float)settings.var_weight);// float depthVarFac = var_weight; // the depth var is over-confident. this is a constant multiplier to remedy that.... HACK
const __m128 sigma_i2s = _mm_set1_ps((float)cameraPixelNoise2);
const __m128 huber_res_ponlys = _mm_set1_ps((float)(settings.huber_d/2));
__m128 sumResP = zeros;
float sumRes = 0;
for(int i=0;i<buf_warped_size-3;i+=4)
{
// float px = *(buf_warped_x+i); // x'
// float py = *(buf_warped_y+i); // y'
// float pz = *(buf_warped_z+i); // z'
// float d = *(buf_d+i); // d
// float rp = *(buf_warped_residual+i); // r_p
// float gx = *(buf_warped_dx+i); // \delta_x I
// float gy = *(buf_warped_dy+i); // \delta_y I
// float s = depthVarFac * *(buf_idepthVar+i); // \sigma_d^2
// calc dw/dd (first 2 components):
__m128 pzs = _mm_load_ps(buf_warped_z+i); // z'
__m128 pz2ds = _mm_rcp_ps(_mm_mul_ps(_mm_mul_ps(pzs, pzs), _mm_load_ps(buf_d+i))); // 1 / (z' * z' * d)
__m128 g0s = _mm_sub_ps(_mm_mul_ps(pzs, txs), _mm_mul_ps(_mm_load_ps(buf_warped_x+i), tzs));
g0s = _mm_mul_ps(g0s,pz2ds); //float g0 = (tx * pz - tz * px) / (pz*pz*d);
//float g1 = (ty * pz - tz * py) / (pz*pz*d);
__m128 g1s = _mm_sub_ps(_mm_mul_ps(pzs, tys), _mm_mul_ps(_mm_load_ps(buf_warped_y+i), tzs));
g1s = _mm_mul_ps(g1s,pz2ds);
// float drpdd = gx * g0 + gy * g1; // ommitting the minus
__m128 drpdds = _mm_add_ps(
_mm_mul_ps(g0s, _mm_load_ps(buf_warped_dx+i)),
_mm_mul_ps(g1s, _mm_load_ps(buf_warped_dy+i)));
//float w_p = 1.0f / (sigma_i2 + s * drpdd * drpdd);
__m128 w_ps = _mm_rcp_ps(_mm_add_ps(sigma_i2s,
_mm_mul_ps(drpdds,
_mm_mul_ps(drpdds,
_mm_mul_ps(depthVarFacs,
_mm_load_ps(buf_idepthVar+i))))));
//float weighted_rp = fabs(rp*sqrtf(w_p));
__m128 weighted_rps = _mm_mul_ps(_mm_load_ps(buf_warped_residual+i),
_mm_sqrt_ps(w_ps));
weighted_rps = _mm_max_ps(weighted_rps, _mm_sub_ps(zeros,weighted_rps));
//float wh = fabs(weighted_rp < huber_res_ponly ? 1 : huber_res_ponly / weighted_rp);
__m128 whs = _mm_cmplt_ps(weighted_rps, huber_res_ponlys); // bitmask 0xFFFFFFFF for 1, 0x000000 for huber_res_ponly / weighted_rp
whs = _mm_or_ps(
_mm_and_ps(whs, ones),
_mm_andnot_ps(whs, _mm_mul_ps(huber_res_ponlys, _mm_rcp_ps(weighted_rps))));
// sumRes.sumResP += wh * w_p * rp*rp;
if(i+3 < buf_warped_size)
sumResP = _mm_add_ps(sumResP,
_mm_mul_ps(whs, _mm_mul_ps(weighted_rps, weighted_rps)));
// *(buf_weight_p+i) = wh * w_p;
_mm_store_ps(buf_weight_p+i, _mm_mul_ps(whs, w_ps) );
}
sumRes = SSEE(sumResP,0) + SSEE(sumResP,1) + SSEE(sumResP,2) + SSEE(sumResP,3);
return sumRes / ((buf_warped_size >> 2)<<2);
}
#endif
#if defined(ENABLE_NEON)
float SE3Tracker::calcWeightsAndResidualNEON(
const Sophus::SE3f& referenceToFrame)
{
float tx = referenceToFrame.translation()[0];
float ty = referenceToFrame.translation()[1];
float tz = referenceToFrame.translation()[2];
float constants[] = {
tx, ty, tz, settings.var_weight,
cameraPixelNoise2, settings.huber_d/2, -1, -1 // last values are currently unused
};
// This could also become a constant if one register could be made free for it somehow
float cutoff_res_ponly4[4] = {10000, 10000, 10000, 10000}; // removed
float* cur_buf_warped_z = buf_warped_z;
float* cur_buf_warped_x = buf_warped_x;
float* cur_buf_warped_y = buf_warped_y;
float* cur_buf_warped_dx = buf_warped_dx;
float* cur_buf_warped_dy = buf_warped_dy;
float* cur_buf_warped_residual = buf_warped_residual;
float* cur_buf_d = buf_d;
float* cur_buf_idepthVar = buf_idepthVar;
float* cur_buf_weight_p = buf_weight_p;
int loop_count = buf_warped_size / 4;
int remaining = buf_warped_size - 4 * loop_count;
float sum_vector[] = {0, 0, 0, 0};
float sumRes=0;
#ifdef DEBUG
loop_count = 0;
remaining = buf_warped_size;
#else
if (loop_count > 0)
{
__asm__ __volatile__
(
// Extract constants
"vldmia %[constants], {q8-q9} \n\t" // constants(q8-q9)
"vdup.32 q13, d18[0] \n\t" // extract sigma_i2 x 4 to q13
"vdup.32 q14, d18[1] \n\t" // extract huber_res_ponly x 4 to q14
//"vdup.32 ???, d19[0] \n\t" // extract cutoff_res_ponly x 4 to ???
"vdup.32 q9, d16[0] \n\t" // extract tx x 4 to q9, overwrite!
"vdup.32 q10, d16[1] \n\t" // extract ty x 4 to q10
"vdup.32 q11, d17[0] \n\t" // extract tz x 4 to q11
"vdup.32 q8, d17[1] \n\t" // extract depthVarFac x 4 to q8, overwrite!
"veor q15, q15, q15 \n\t" // set sumRes.sumResP(q15) to zero (by xor with itself)
".loopcalcWeightsAndResidualNEON: \n\t"
"vldmia %[buf_idepthVar]!, {q7} \n\t" // s(q7)
"vldmia %[buf_warped_z]!, {q2} \n\t" // pz(q2)
"vldmia %[buf_d]!, {q3} \n\t" // d(q3)
"vldmia %[buf_warped_x]!, {q0} \n\t" // px(q0)
"vldmia %[buf_warped_y]!, {q1} \n\t" // py(q1)
"vldmia %[buf_warped_residual]!, {q4} \n\t" // rp(q4)
"vldmia %[buf_warped_dx]!, {q5} \n\t" // gx(q5)
"vldmia %[buf_warped_dy]!, {q6} \n\t" // gy(q6)
"vmul.f32 q7, q7, q8 \n\t" // s *= depthVarFac
"vmul.f32 q12, q2, q2 \n\t" // pz*pz (q12, temp)
"vmul.f32 q3, q12, q3 \n\t" // pz*pz*d (q3)
"vrecpe.f32 q3, q3 \n\t" // 1/(pz*pz*d) (q3)
"vmul.f32 q12, q9, q2 \n\t" // tx*pz (q12)
"vmls.f32 q12, q11, q0 \n\t" // tx*pz - tz*px (q12) [multiply and subtract] {free: q0}
"vmul.f32 q0, q10, q2 \n\t" // ty*pz (q0) {free: q2}
"vmls.f32 q0, q11, q1 \n\t" // ty*pz - tz*py (q0) {free: q1}
"vmul.f32 q12, q12, q3 \n\t" // g0 (q12)
"vmul.f32 q0, q0, q3 \n\t" // g1 (q0)
"vmul.f32 q12, q12, q5 \n\t" // gx * g0 (q12) {free: q5}
"vldmia %[cutoff_res_ponly4], {q5} \n\t" // cutoff_res_ponly (q5), load for later
"vmla.f32 q12, q6, q0 \n\t" // drpdd = gx * g0 + gy * g1 (q12) {free: q6, q0}
"vmov.f32 q1, #1.0 \n\t" // 1.0 (q1), will be used later
"vmul.f32 q12, q12, q12 \n\t" // drpdd*drpdd (q12)
"vmul.f32 q12, q12, q7 \n\t" // s*drpdd*drpdd (q12)
"vadd.f32 q12, q12, q13 \n\t" // sigma_i2 + s*drpdd*drpdd (q12)
"vrecpe.f32 q12, q12 \n\t" // w_p = 1/(sigma_i2 + s*drpdd*drpdd) (q12) {free: q7}
// float weighted_rp = fabs(rp*sqrtf(w_p));
"vrsqrte.f32 q7, q12 \n\t" // 1 / sqrtf(w_p) (q7)
"vrecpe.f32 q7, q7 \n\t" // sqrtf(w_p) (q7)
"vmul.f32 q7, q7, q4 \n\t" // rp*sqrtf(w_p) (q7)
"vabs.f32 q7, q7 \n\t" // weighted_rp (q7)
// float wh = fabs(weighted_rp < huber_res_ponly ? 1 : huber_res_ponly / weighted_rp);
"vrecpe.f32 q6, q7 \n\t" // 1 / weighted_rp (q6)
"vmul.f32 q6, q6, q14 \n\t" // huber_res_ponly / weighted_rp (q6)
"vclt.f32 q0, q7, q14 \n\t" // weighted_rp < huber_res_ponly ? all bits 1 : all bits 0 (q0)
"vbsl q0, q1, q6 \n\t" // sets elements in q0 to 1(q1) where above condition is true, and to q6 where it is false {free: q6}
"vabs.f32 q0, q0 \n\t" // wh (q0)
// sumRes.sumResP += wh * w_p * rp*rp
"vmul.f32 q4, q4, q4 \n\t" // rp*rp (q4)
"vmul.f32 q4, q4, q12 \n\t" // w_p*rp*rp (q4)
"vmla.f32 q15, q4, q0 \n\t" // sumRes.sumResP += wh*w_p*rp*rp (q15) {free: q4}
// if(weighted_rp > cutoff_res_ponly)
// wh = 0;
// *(buf_weight_p+i) = wh * w_p;
"vcle.f32 q4, q7, q5 \n\t" // mask in q4: ! (weighted_rp > cutoff_res_ponly)
"vmul.f32 q0, q0, q12 \n\t" // wh * w_p (q0)
"vand q0, q0, q4 \n\t" // set q0 to 0 where condition for q4 failed (i.e. weighted_rp > cutoff_res_ponly)
"vstmia %[buf_weight_p]!, {q0} \n\t"
"subs %[loop_count], %[loop_count], #1 \n\t"
"bne .loopcalcWeightsAndResidualNEON \n\t"
"vstmia %[sum_vector], {q15} \n\t"
: /* outputs */ [buf_warped_z]"+&r"(cur_buf_warped_z),
[buf_warped_x]"+&r"(cur_buf_warped_x),
[buf_warped_y]"+&r"(cur_buf_warped_y),
[buf_warped_dx]"+&r"(cur_buf_warped_dx),
[buf_warped_dy]"+&r"(cur_buf_warped_dy),
[buf_d]"+&r"(cur_buf_d),
[buf_warped_residual]"+&r"(cur_buf_warped_residual),
[buf_idepthVar]"+&r"(cur_buf_idepthVar),
[buf_weight_p]"+&r"(cur_buf_weight_p),
[loop_count]"+&r"(loop_count)
: /* inputs */ [constants]"r"(constants),
[cutoff_res_ponly4]"r"(cutoff_res_ponly4),
[sum_vector]"r"(sum_vector)
: /* clobber */ "memory", "cc",
"q0", "q1", "q2", "q3", "q4", "q5", "q6", "q7", "q8", "q9", "q10", "q11", "q12", "q13", "q14", "q15"
);
sumRes += sum_vector[0] + sum_vector[1] + sum_vector[2] + sum_vector[3];
}
#endif
for(int i=buf_warped_size-remaining; i<buf_warped_size; i++)
{
float px = *(buf_warped_x+i); // x'
float py = *(buf_warped_y+i); // y'
float pz = *(buf_warped_z+i); // z'
float d = *(buf_d+i); // d
float rp = *(buf_warped_residual+i); // r_p
float gx = *(buf_warped_dx+i); // \delta_x I
float gy = *(buf_warped_dy+i); // \delta_y I
float s = settings.var_weight * *(buf_idepthVar+i); // \sigma_d^2
// calc dw/dd (first 2 components):
float g0 = (tx * pz - tz * px) / (pz*pz*d);
float g1 = (ty * pz - tz * py) / (pz*pz*d);
// calc w_p
float drpdd = gx * g0 + gy * g1; // ommitting the minus
float w_p = 1.0f / (cameraPixelNoise2 + s * drpdd * drpdd);
float weighted_rp = fabs(rp*sqrtf(w_p));
float wh = fabs(weighted_rp < (settings.huber_d/2) ? 1 : (settings.huber_d/2) / weighted_rp);
sumRes += wh * w_p * rp*rp;
*(buf_weight_p+i) = wh * w_p;
}
return sumRes / buf_warped_size;
}
#endif
///////////////////////////////////////////////////////////////////
// 每个像素点都有一个光度匹配误差,而每个点匹配的可信度根据其偏导数可以求得,
// 加权每一个误差 然后求所有点 光度匹配误差的均值
// 再者 计算误差的权重,在优化更新函数中作为权重
// 使用 误差方差对方差归一化 后求和
// 计算权重 和 像素匹配误差
// 该函数的功能是计算归一化方差的光度误差系数,也就是计算公式(14),
// 并且乘以了Huber-weight,最终把这个系数存在数组buf_weight_p中。
// 可能是考虑参考帧到当前帧的位姿变换比较小,
// 所以作者只考虑了位移t而忽略旋转R。
// 这样使得式子(14)中的偏导的形式简单了很多。
// 这里主要求 误差函数的导数 即得到 误差的协方差矩阵
float SE3Tracker::calcWeightsAndResidual(
const Sophus::SE3f& referenceToFrame)// 参考帧 到 当前帧 欧式变换矩阵
{
// 参考帧 到 当前帧 平移向量
float tx = referenceToFrame.translation()[0];
float ty = referenceToFrame.translation()[1];
float tz = referenceToFrame.translation()[2];
float sumRes = 0;
// 每个像素点都有一个光度匹配误差,而每个点匹配的可信度根据其偏导数可以求得,
// 加权每一个误差 然后求所有点 光度匹配误差的均值
for(int i=0;i<buf_warped_size;i++)// 对于初步匹配得 每一个 匹配点
{
// 参考帧 映射到 当前帧 坐标系下的 3d 坐标
float px = *(buf_warped_x+i); // x'
float py = *(buf_warped_y+i); // y'
float pz = *(buf_warped_z+i); // z'
float d = *(buf_d+i); // d 参考帧 Z轴 倒数 = 参考帧 逆深度
float rp = *(buf_warped_residual+i); // r_p 匹配点对 像素匹配误差
float gx = *(buf_warped_dx+i); // \delta_x I gx = dx*fx当前帧 亚像素梯度值gx仿射变换后( 乘以 相机内参数)
float gy = *(buf_warped_dy+i); // \delta_y I gy = gy*fy
float s = settings.var_weight * *(buf_idepthVar+i); // \sigma_d^2 参考帧 逆深度 方差 平方
// 参考帧3D点通过Rt映射到当前帧坐标系下在通过相机内参数映射到当前帧图像平面上
// 的到所谓的光度误差 该误差对参考帧p点处的逆深度D求偏导数得到
// dE = -(dx*fx * (tx*z' - tz*x')/(z'^2*d) + dy*fy * (ty*z' - tz*y')/(z'^2*d))
// calc dw/dd (first 2 components):
float g0 = (tx * pz - tz * px) / (pz*pz*d);//
float g1 = (ty * pz - tz * py) / (pz*pz*d);
// 误差 偏导数的结果 calc w_p
// 正如源码所注释,这里省略了负号
float drpdd = gx * g0 + gy * g1; // ommitting the minus
float w_p = 1.0f / ((cameraPixelNoise2) + s * drpdd * drpdd);// 误差权重 方差倒数
float weighted_rp = fabs(rp*sqrtf(w_p));// |r|加权的误差
// 为了减少外点(outliers)对算法的影响,论文中使用了迭代变权重最小二乘
// 其实在实现的时候,这里给的权重就是Huber-weight。
float wh = fabs(weighted_rp < (settings.huber_d/2) ? 1 : (settings.huber_d/2) / weighted_rp);
//sumRes += wh * w_p * rp*rp;
//*(buf_weight_p+i) = wh * w_p;// // 乘上 Huber-weight 权重 的最终误差
*(buf_weight_p+i) = wh * w_p;// // 乘上 Huber-weight 权重 的最终误差
sumRes += *(buf_weight_p+i) * rp*rp;// 加权误差和
}
return sumRes / buf_warped_size;// 返回加权误差均值
}
/////////////////////////////////////////////////////////////////////////////////////////////////
// 如果要可视化Tracking的迭代过程,那么第一步自然是把debug相关的参数都设置进去,
// 否则直接进行下一步,这个操作是通过调用calcResidualAndBuffers_debugStart()实现的
void SE3Tracker::calcResidualAndBuffers_debugStart()
{
// 是否需要 可视化Tracking的迭代过程
if(plotTrackingIterationInfo || saveAllTrackingStagesInternal)
{
int other = saveAllTrackingStagesInternal ? 255 : 0;
// 填充图像
fillCvMat(&debugImageResiduals,cv::Vec3b(other,other,255));
fillCvMat(&debugImageWeights,cv::Vec3b(other,other,255));
fillCvMat(&debugImageOldImageSource,cv::Vec3b(other,other,255));
fillCvMat(&debugImageOldImageWarped,cv::Vec3b(other,other,255));
}
}
// 完成匹配误差计算 保存误差信息图片
void SE3Tracker::calcResidualAndBuffers_debugFinish(int w)
{
// 显示跟踪 优化迭代信息
if(plotTrackingIterationInfo)
{
Util::displayImage( "Weights", debugImageWeights );
Util::displayImage( "second_frame", debugImageSecondFrame );
Util::displayImage( "Intensities of second_frame at transformed positions", debugImageOldImageSource );
Util::displayImage( "Intensities of second_frame at pointcloud in first_frame", debugImageOldImageWarped );
Util::displayImage( "Residuals", debugImageResiduals );
// wait for key and handle it
bool looping = true;
while(looping)
{
int k = Util::waitKey(1);
if(k == -1)
{
if(autoRunWithinFrame)
break;
else
continue;
}
char key = k;
if(key == ' ')
looping = false;
else
handleKey(k);// 处理
}
}
// 保存跟踪迭代信息 图像
if(saveAllTrackingStagesInternal)
{
char charbuf[500];
snprintf(charbuf,500,"save/%sresidual-%d-%d.png",packagePath.c_str(),w,iterationNumber);// 格式化字符串 图片名
cv::imwrite(charbuf,debugImageResiduals);// 写图片 参差
snprintf(charbuf,500,"save/%swarped-%d-%d.png",packagePath.c_str(),w,iterationNumber);
cv::imwrite(charbuf,debugImageOldImageWarped);
snprintf(charbuf,500,"save/%sweights-%d-%d.png",packagePath.c_str(),w,iterationNumber);
cv::imwrite(charbuf,debugImageWeights);// 权重图片
printf("saved three images for lvl %d, iteration %d\n",w,iterationNumber);// 打印信息
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////// 跟踪完成获取 变换矩阵 和 3d 点后 反投影到当前帧 计算 灰度匹配误差 ////////////////////////////
// calcResidualAndBuffers X86平台 SSE指令集优化
#if defined(ENABLE_SSE)
float SE3Tracker::calcResidualAndBuffersSSE(
const Eigen::Vector3f* refPoint,
const Eigen::Vector2f* refColVar,
int* idxBuf,
int refNum,
Frame* frame,
const Sophus::SE3f& referenceToFrame,
int level,
bool plotResidual)
{
return calcResidualAndBuffers(refPoint, refColVar, idxBuf, refNum, frame, referenceToFrame, level, plotResidual);
}
#endif
// calcResidualAndBuffers ARM平台 NENO指令集优化
#if defined(ENABLE_NEON)
float SE3Tracker::calcResidualAndBuffersNEON(
const Eigen::Vector3f* refPoint,
const Eigen::Vector2f* refColVar,
int* idxBuf,
int refNum,
Frame* frame,
const Sophus::SE3f& referenceToFrame,
int level,
bool plotResidual)
{
return calcResidualAndBuffers(refPoint, refColVar, idxBuf, refNum, frame, referenceToFrame, level, plotResidual);
}
#endif
// #ifdef的使用和#if defined()的用法一致
// #ifndef又和#if !defined()的用法一致。
// 优化相关的函数,参数共有8个,在后面可以看到,其他函数通过 callOptimized 这个宏调用了这个函数进行优化操作
// 匹配帧通过 变换矩阵 和 相机内参数 投影到 当前图像平面上(值为float小数)
// 而原图像上的像素点坐标为 整数值,每一个像素位置对应有,梯度信息
// 那么 浮点数像素坐标对应的像素 是多少呢,根据浮点数坐标 四周的四个整数点坐标的梯度 加权和 后计算 灰度匹配误差
// 根据变换矩阵和原3D点和灰度值 计算匹配点对之间得 像素匹配误差 以及 匹配点对 好坏标志图
/*
input:
refPoint 参考帧 3d坐标 起始 地址指针
refColVar 参考帧 灰度和 逆深度方差 起始地址
idxBuf 匹配点好坏标志图 指针
frame 当前帧 指针
referenceToFrame 参考帧 变换到 当前帧下得 欧式变换矩阵 引用
level 金字塔层级
plotResidual 匹配误差图标志
*/
float SE3Tracker::calcResidualAndBuffers(
const Eigen::Vector3f* refPoint,// 参考帧 3d坐标 起始 地址
const Eigen::Vector2f* refColVar,// 参考帧 灰度和 逆深度方差 起始地址
int* idxBuf,// 匹配点好坏标志图 指针
int refNum,// 参考帧 3d点数量
Frame* frame,// 当前帧
const Sophus::SE3f& referenceToFrame,// 参考帧 变换到 当前帧下得 欧式变换矩阵
int level,// 金字塔层级
bool plotResidual)//显示 匹配误差图标志
{
// 如果要可视化Tracking的迭代过程,那么第一步自然是把debug相关的参数都设置进去,
// 否则直接进行下一步,这个操作是通过调用calcResidualAndBuffers_debugStart()实现的
calcResidualAndBuffers_debugStart();
// 然后判断是否可视化残差,如果需要可视化,那么初始化残差
if(plotResidual)
debugImageResiduals.setTo(0);
// 本地参数设置
int w = frame->width(level);// 图像尺寸
int h = frame->height(level);
Eigen::Matrix3f KLvl = frame->K(level);// 相机内参数
float fx_l = KLvl(0,0);
float fy_l = KLvl(1,1);
float cx_l = KLvl(0,2);
float cy_l = KLvl(1,2);
Eigen::Matrix3f rotMat = referenceToFrame.rotationMatrix();// 旋转矩阵
Eigen::Vector3f transVec = referenceToFrame.translation();// 平移向量
const Eigen::Vector3f* refPoint_max = refPoint + refNum;// 参考帧的 3d点坐标 存储的最大位置
const Eigen::Vector4f* frame_gradients = frame->gradients(level);// 当前帧 像素梯度 获取 关键点
// 然后定义后续所要使用的变量
int idx=0;
float sumResUnweighted = 0;
bool* isGoodOutBuffer = idxBuf != 0 ? frame->refPixelWasGood() : 0;// 匹配点 对 匹配效果好坏
int goodCount = 0;
int badCount = 0;
float sumSignedRes = 0;
float sxx=0,syy=0,sx=0,sy=0,sw=0;
float usageCount = 0;
for(;refPoint<refPoint_max; refPoint++, refColVar++, idxBuf++)// 对于每一个 参考帧的 3d点坐标
{
// 参考帧3d点 R,t变换到 当前相机坐标系下
Eigen::Vector3f Wxp = rotMat * (*refPoint) + transVec;
// 在 投影到 当前相机 像素平面下
float u_new = (Wxp[0]/Wxp[2])*fx_l + cx_l;// float 浮点数类型
float v_new = (Wxp[1]/Wxp[2])*fy_l + cy_l;
// step 1a: coordinates have to be in image:
// (inverse test to exclude NANs)
// 投影后点在 图像和合理范围内 判断当前点是否投影到图像中
if(!(u_new > 1 && v_new > 1 && u_new < w-2 && v_new < h-2))
{
if(isGoodOutBuffer != 0)// 指针部位空
isGoodOutBuffer[*idxBuf] = false;// 并标记
continue;
}
// 使用 当前点 右方点 右下方点 正下方点 的灰度梯度信息 以及 相应位置差值作为权重值 线性加权获取加权梯度信息
// 然后差值得到亚像素精度级别的 梯度(注意深度的第三个维度是图像数据)
// 浮点数像素坐标对应的像素 是多少呢,根据浮点数坐标 四周的四个整数点坐标的梯度 加权和
Eigen::Vector3f resInterp = getInterpolatedElement43(frame_gradients, u_new, v_new, w);
// 之后把参考图像数据做一次 仿射操作,
// 疑问1:代码中对参考帧的灰度做了一个仿射变换的处理,这里的原理是什么?
//在SVO代码中的确有考虑到两帧见位姿相差过大,因此通过在空间上的仿射变换之后再求灰度的操作。
// 但是在这里的代码中没有看出具体原理。
float c1 = affineEstimation_a * (*refColVar)[0] + affineEstimation_b;// 参考帧 灰度[0] ; 逆深度方差[1]
float c2 = resInterp[2];// 当前帧 亚像素 第三维度是图像 灰度值
float residual = c1 - c2;// 匹配点对 的灰度 误差值
// 通过差值自适应算得权重 也就是说,误差越大(匹配效果差),权重越小
float weight = fabsf(residual) < 5.0f ? 1 : 5.0f / fabsf(residual);// 误差倒数为权重,误差小于5的分子为1 , 大于5的分子为5
// 这些参数用来迭代计算 下一次 的 仿射变换系数
sxx += c1*c1*weight;// 参考帧 灰度值平方和 带误差倒数 权重
syy += c2*c2*weight;// 匹配帧 亚像素 灰度值平方 和 带误差倒数 权重
sx += c1*weight;// 参考帧 灰度值 和 带误差倒数 权重
sy += c2*weight;// 匹配帧 亚像素 灰度值 和 带误差倒数 权重
sw += weight;// 误差倒数 权重 和
// 然后判断这个匹配点是好是坏,也是个自适应的阈值,这个阈值为一个最大的差异常数,加上 梯度值平和 乘以一个比例系数,
// 这个和 匹配点对 的灰度误差值平方 比较,如果残差的平方小于它,那么认为这个点的估计比较好,然后再把这个判断赋值给isGoodOutBuffer[*idxBuf]
// 匹配点对 的灰度 误差值 平和 小于 与 当前帧 点 的x和y方向梯度平和 相关数 时 为好的匹配点
bool isGood = residual*residual / (MAX_DIFF_CONSTANT + MAX_DIFF_GRAD_MULT*(resInterp[0]*resInterp[0] + resInterp[1]*resInterp[1])) < 1;
if(isGoodOutBuffer != 0)
isGoodOutBuffer[*idxBuf] = isGood;// 记录 匹配点对好坏标志
// 之后记录计算得到的这一帧改变的值
*(buf_warped_x+idx) = Wxp(0);// 参考帧 3d点 通过R,t变换矩阵 变换到 当前图像坐标系下的坐标值 X
*(buf_warped_y+idx) = Wxp(1);// Y
*(buf_warped_z+idx) = Wxp(2);// Z
// 这里求得 亚像素梯度之后 又乘上了 相机内参数,因为在求 误差偏导数时需要 需要用到 dx*fx dy*fy
// calcWeightsAndResidual 求误差导数来求 误差的协方差矩阵 后归一化误差
*(buf_warped_dx+idx) = fx_l * resInterp[0];// 当前帧匹配点亚像素 梯度 gx = dx*fx
*(buf_warped_dy+idx) = fy_l * resInterp[1];// 匹配点亚像素 梯度 gy = dy*fy
*(buf_warped_residual+idx) = residual;// 对应 匹配点 像素误差
*(buf_d+idx) = 1.0f / (*refPoint)[2];// 参考帧 Z轴 倒数 = 参考帧逆深度
*(buf_idepthVar+idx) = (*refColVar)[1];// 参考帧逆深度方差
idx++;// 点 ++
// 之后再记录 匹配点像素误差的平方和,以 及匹配点像素误差值(带符号)
if(isGood)
{
sumResUnweighted += residual*residual;// 较好的 匹配点像素误差的平方和
sumSignedRes += residual;// 较好的 匹配点像素误差和
goodCount++;// 较好的匹配点对 计数
}
else
badCount++;// 不好的匹配点对 计数
// 匹配点 变换前后 深度得变换 checkPermaRefOverlap()函数也有类似的计算
float depthChange = (*refPoint)[2] / Wxp[2]; // if depth becomes larger: pixel becomes "smaller", hence count it less.
usageCount += depthChange < 1 ? depthChange : 1;// 记录深度改变的比例
// 调试 如果设置了画图,就把他们可视化出来
// DEBUG STUFF
if(plotTrackingIterationInfo || plotResidual)
{
// for debug plot only: find x,y again.
// horribly inefficient, but who cares at this point...
Eigen::Vector3f point = KLvl * (*refPoint);// 参考帧下 3d点对应的 像素点2d坐标
int x = point[0] / point[2] + 0.5f;
int y = point[1] / point[2] + 0.5f;
if(plotTrackingIterationInfo)
{
// 设置参考帧和 当前帧 关键点的 图像 使用 亚像素灰度值作为 点颜色
setPixelInCvMat(&debugImageOldImageSource,getGrayCvPixel((float)resInterp[2]),u_new+0.5,v_new+0.5,(width/w));// 当前图像下的 关键点2d 坐标
setPixelInCvMat(&debugImageOldImageWarped,getGrayCvPixel((float)resInterp[2]),x,y,(width/w));// 参考帧下得 关键点2d 坐标
}
if(isGood)// 匹配点对灰度像素匹配误差较小 显示 匹配误差图像
setPixelInCvMat(&debugImageResiduals,getGrayCvPixel(residual+128),x,y,(width/w));// 误差越小 颜色越靠中间颜色
else
setPixelInCvMat(&debugImageResiduals,cv::Vec3b(0,0,255),x,y,(width/w));// 误差较大的点 显示蓝色 rgb
}
}
buf_warped_size = idx;// 匹配点数量= 包括好的匹配点 和 不好的匹配点 数量
pointUsage = usageCount / (float)refNum;// 深度改变均值
lastGoodCount = goodCount;// 好的匹配点计数
lastBadCount = badCount;// 不好得匹配点计数
lastMeanRes = sumSignedRes / goodCount;// 匹配点像素误差均值
// 计算迭代之后得到的 下一次得仿射变换系数
affineEstimation_a_lastIt = sqrtf((syy - sy*sy/sw) / (sxx - sx*sx/sw));
affineEstimation_b_lastIt = (sy - affineEstimation_a_lastIt*sx)/sw;
calcResidualAndBuffers_debugFinish(w);// 主要用来 保存调试信息 图像
return sumResUnweighted / goodCount; // 匹配点像素误差的平方和 均值
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 计算雅克比矩阵J 线性方程系数A,和偏置b,使用LDLT矩阵分解求解方程得到 李代数更新量 dse3
// 利用误差函数对误差向量求导数,求得各个点的误差之间的 协方差矩阵,也是雅克比矩阵J
// 是计算雅可比以及最小二乘方程的系数
// 对于参考帧中的一个3D点pi位置处的残差求雅可比,这里需要使用链式求导法则
// Ji = 1/z' *dx *fx + 0* dy *fy
// 0*dx *fx + 1/z' *dy *fy
// -1 * - x'/z'^2 *dx*fx - y'/z'^2 *dy*fy
// - x'*y'/z'^2 *dx*fx - (1+y'^2/z'^2) *dy*fy
// (1+x'^2/z'^2)*dx*fx + x'*y'/z'^2*dy*fy
// - y'/z' *dx*fx + x'/z' * dy*fy
// A = J *J转置*(*(buf_weight_p+i))
// b = -J转置*(*(buf_weight_p+i))*lastErr
// Vector6 inc = A.ldlt().solve(b);// LDLT矩阵分解求解 dse3 李代数更新量
#if defined(ENABLE_SSE)
Vector6 SE3Tracker::calculateWarpUpdateSSE(
NormalEquationsLeastSquares &ls)
{
ls.initialize(width*height);
// printf("wupd SSE\n");
for(int i=0;i<buf_warped_size-3;i+=4)
{
Vector6 v1,v2,v3,v4;
__m128 val1, val2, val3, val4;
// redefine pz
__m128 pz = _mm_load_ps(buf_warped_z+i);
pz = _mm_rcp_ps(pz); // pz := 1/z
__m128 gx = _mm_load_ps(buf_warped_dx+i);
val1 = _mm_mul_ps(pz, gx); // gx / z => SET [0]
//v[0] = z*gx;
v1[0] = SSEE(val1,0);
v2[0] = SSEE(val1,1);
v3[0] = SSEE(val1,2);
v4[0] = SSEE(val1,3);
__m128 gy = _mm_load_ps(buf_warped_dy+i);
val1 = _mm_mul_ps(pz, gy); // gy / z => SET [1]
//v[1] = z*gy;
v1[1] = SSEE(val1,0);
v2[1] = SSEE(val1,1);
v3[1] = SSEE(val1,2);
v4[1] = SSEE(val1,3);
__m128 px = _mm_load_ps(buf_warped_x+i);
val1 = _mm_mul_ps(px, gy);
val1 = _mm_mul_ps(val1, pz); // px * gy * z
__m128 py = _mm_load_ps(buf_warped_y+i);
val2 = _mm_mul_ps(py, gx);
val2 = _mm_mul_ps(val2, pz); // py * gx * z
val1 = _mm_sub_ps(val1, val2); // px * gy * z - py * gx * z => SET [5]
//v[5] = -py * z * gx + px * z * gy;
v1[5] = SSEE(val1,0);
v2[5] = SSEE(val1,1);
v3[5] = SSEE(val1,2);
v4[5] = SSEE(val1,3);
// redefine pz
pz = _mm_mul_ps(pz,pz); // pz := 1/(z*z)
// will use these for the following calculations a lot.
val1 = _mm_mul_ps(px, gx);
val1 = _mm_mul_ps(val1, pz); // px * z_sqr * gx
val2 = _mm_mul_ps(py, gy);
val2 = _mm_mul_ps(val2, pz); // py * z_sqr * gy
val3 = _mm_add_ps(val1, val2);
val3 = _mm_sub_ps(_mm_setr_ps(0,0,0,0),val3); //-px * z_sqr * gx -py * z_sqr * gy
//v[2] = -px * z_sqr * gx -py * z_sqr * gy; => SET [2]
v1[2] = SSEE(val3,0);
v2[2] = SSEE(val3,1);
v3[2] = SSEE(val3,2);
v4[2] = SSEE(val3,3);
val3 = _mm_mul_ps(val1, py); // px * z_sqr * gx * py
val4 = _mm_add_ps(gy, val3); // gy + px * z_sqr * gx * py
val3 = _mm_mul_ps(val2, py); // py * py * z_sqr * gy
val4 = _mm_add_ps(val3, val4); // gy + px * z_sqr * gx * py + py * py * z_sqr * gy
val4 = _mm_sub_ps(_mm_setr_ps(0,0,0,0),val4); //val4 = -val4.
//v[3] = -px * py * z_sqr * gx +
// -py * py * z_sqr * gy +
// -gy; => SET [3]
v1[3] = SSEE(val4,0);
v2[3] = SSEE(val4,1);
v3[3] = SSEE(val4,2);
v4[3] = SSEE(val4,3);
val3 = _mm_mul_ps(val1, px); // px * px * z_sqr * gx
val4 = _mm_add_ps(gx, val3); // gx + px * px * z_sqr * gx
val3 = _mm_mul_ps(val2, px); // px * py * z_sqr * gy
val4 = _mm_add_ps(val4, val3); // gx + px * px * z_sqr * gx + px * py * z_sqr * gy
//v[4] = px * px * z_sqr * gx +
// px * py * z_sqr * gy +
// gx; => SET [4]
v1[4] = SSEE(val4,0);
v2[4] = SSEE(val4,1);
v3[4] = SSEE(val4,2);
v4[4] = SSEE(val4,3);
// step 6: integrate into A and b:
ls.update(v1, *(buf_warped_residual+i+0), *(buf_weight_p+i+0));
if(i+1>=buf_warped_size) break;
ls.update(v2, *(buf_warped_residual+i+1), *(buf_weight_p+i+1));
if(i+2>=buf_warped_size) break;
ls.update(v3, *(buf_warped_residual+i+2), *(buf_weight_p+i+2));
if(i+3>=buf_warped_size) break;
ls.update(v4, *(buf_warped_residual+i+3), *(buf_weight_p+i+3));
}
Vector6 result;
// solve ls
ls.finish();
ls.solve(result);
return result;
}
#endif
#if defined(ENABLE_NEON)
Vector6 SE3Tracker::calculateWarpUpdateNEON(
NormalEquationsLeastSquares &ls)
{
// weightEstimator.reset();
// weightEstimator.estimateDistributionNEON(buf_warped_residual, buf_warped_size);
// weightEstimator.calcWeightsNEON(buf_warped_residual, buf_warped_weights, buf_warped_size);
ls.initialize(width*height);
float* cur_buf_warped_z = buf_warped_z;
float* cur_buf_warped_x = buf_warped_x;
float* cur_buf_warped_y = buf_warped_y;
float* cur_buf_warped_dx = buf_warped_dx;
float* cur_buf_warped_dy = buf_warped_dy;
Vector6 v1,v2,v3,v4;
float* v1_ptr;
float* v2_ptr;
float* v3_ptr;
float* v4_ptr;
for(int i=0;i<buf_warped_size;i+=4)
{
v1_ptr = &v1[0];
v2_ptr = &v2[0];
v3_ptr = &v3[0];
v4_ptr = &v4[0];
__asm__ __volatile__
(
"vldmia %[buf_warped_z]!, {q10} \n\t" // pz(q10)
"vrecpe.f32 q10, q10 \n\t" // z(q10)
"vldmia %[buf_warped_dx]!, {q11} \n\t" // gx(q11)
"vmul.f32 q0, q10, q11 \n\t" // q0 = z*gx // = v[0]
"vldmia %[buf_warped_dy]!, {q12} \n\t" // gy(q12)
"vmul.f32 q1, q10, q12 \n\t" // q1 = z*gy // = v[1]
"vldmia %[buf_warped_x]!, {q13} \n\t" // px(q13)
"vmul.f32 q5, q13, q12 \n\t" // q5 = px * gy
"vmul.f32 q5, q5, q10 \n\t" // q5 = q5 * z = px * gy * z
"vldmia %[buf_warped_y]!, {q14} \n\t" // py(q14)
"vmul.f32 q3, q14, q11 \n\t" // q3 = py * gx
"vmls.f32 q5, q3, q10 \n\t" // q5 = px * gy * z - py * gx * z // = v[5] (vmls: multiply and subtract from result)
"vmul.f32 q10, q10, q10 \n\t" // q10 = 1/(pz*pz)
"vmul.f32 q6, q13, q11 \n\t"
"vmul.f32 q6, q6, q10 \n\t" // q6 = val1 in SSE version = px * z_sqr * gx
"vmul.f32 q7, q14, q12 \n\t"
"vmul.f32 q7, q7, q10 \n\t" // q7 = val2 in SSE version = py * z_sqr * gy
"vadd.f32 q2, q6, q7 \n\t"
"vneg.f32 q2, q2 \n\t" // q2 = -px * z_sqr * gx -py * z_sqr * gy // = v[2]
"vmul.f32 q8, q6, q14 \n\t" // val3(q8) = px * z_sqr * gx * py
"vadd.f32 q9, q12, q8 \n\t" // val4(q9) = gy + px * z_sqr * gx * py
"vmul.f32 q8, q7, q14 \n\t" // val3(q8) = py * py * z_sqr * gy
"vadd.f32 q9, q8, q9 \n\t" // val4(q9) = gy + px * z_sqr * gx * py + py * py * z_sqr * gy
"vneg.f32 q3, q9 \n\t" // q3 = v[3]
"vst4.32 {d0[0], d2[0], d4[0], d6[0]}, [%[v1]]! \n\t" // store v[0] .. v[3] for 1st value and inc pointer
"vst4.32 {d0[1], d2[1], d4[1], d6[1]}, [%[v2]]! \n\t" // store v[0] .. v[3] for 2nd value and inc pointer
"vst4.32 {d1[0], d3[0], d5[0], d7[0]}, [%[v3]]! \n\t" // store v[0] .. v[3] for 3rd value and inc pointer
"vst4.32 {d1[1], d3[1], d5[1], d7[1]}, [%[v4]]! \n\t" // store v[0] .. v[3] for 4th value and inc pointer
"vmul.f32 q8, q6, q13 \n\t" // val3(q8) = px * px * z_sqr * gx
"vadd.f32 q9, q11, q8 \n\t" // val4(q9) = gx + px * px * z_sqr * gx
"vmul.f32 q8, q7, q13 \n\t" // val3(q8) = px * py * z_sqr * gy
"vadd.f32 q4, q9, q8 \n\t" // q4 = v[4]
"vst2.32 {d8[0], d10[0]}, [%[v1]] \n\t" // store v[4], v[5] for 1st value
"vst2.32 {d8[1], d10[1]}, [%[v2]] \n\t" // store v[4], v[5] for 2nd value
"vst2.32 {d9[0], d11[0]}, [%[v3]] \n\t" // store v[4], v[5] for 3rd value
"vst2.32 {d9[1], d11[1]}, [%[v4]] \n\t" // store v[4], v[5] for 4th value
: /* outputs */ [buf_warped_z]"+r"(cur_buf_warped_z),
[buf_warped_x]"+r"(cur_buf_warped_x),
[buf_warped_y]"+r"(cur_buf_warped_y),
[buf_warped_dx]"+r"(cur_buf_warped_dx),
[buf_warped_dy]"+r"(cur_buf_warped_dy),
[v1]"+r"(v1_ptr),
[v2]"+r"(v2_ptr),
[v3]"+r"(v3_ptr),
[v4]"+r"(v4_ptr)
: /* inputs */
: /* clobber */ "memory", "cc", // TODO: is cc necessary?
"q0", "q1", "q2", "q3", "q4", "q5", "q6", "q7", "q8", "q9", "q10", "q11", "q12", "q13", "q14"
);
// step 6: integrate into A and b:
if(!(i+3>=buf_warped_size))
{
ls.update(v1, *(buf_warped_residual+i+0), *(buf_weight_p+i+0));
ls.update(v2, *(buf_warped_residual+i+1), *(buf_weight_p+i+1));
ls.update(v3, *(buf_warped_residual+i+2), *(buf_weight_p+i+2));
ls.update(v4, *(buf_warped_residual+i+3), *(buf_weight_p+i+3));
}
else
{
ls.update(v1, *(buf_warped_residual+i+0), *(buf_weight_p+i+0));
if(i+1>=buf_warped_size) break;
ls.update(v2, *(buf_warped_residual+i+1), *(buf_weight_p+i+1));
if(i+2>=buf_warped_size) break;
ls.update(v3, *(buf_warped_residual+i+2), *(buf_weight_p+i+2));
if(i+3>=buf_warped_size) break;
ls.update(v4, *(buf_warped_residual+i+3), *(buf_weight_p+i+3));
}
}
Vector6 result;
// solve ls
ls.finish();
ls.solve(result);
return result;
}
#endif
// 计算雅克比矩阵J 线性方程系数A,和偏置b,使用LDLT矩阵分解求解方程得到 李代数更新量 dse3
// 利用误差函数对误差向量求导数,求得各个点的误差之间的 协方差矩阵,也是雅克比矩阵J
// 是计算雅可比以及最小二乘方程的系数
// 对于参考帧中的一个3D点pi位置处的残差求雅可比,这里需要使用链式求导法则
// Ji = 1/z' *dx *fx + 0* dy *fy
// 0*dx *fx + 1/z' *dy *fy
// -1 * - x'/z'^2 *dx*fx - y'/z'^2 *dy*fy
// - x'*y'/z'^2 *dx*fx - (1+y'^2/z'^2) *dy*fy
// (1+x'^2/z'^2)*dx*fx + x'*y'/z'^2*dy*fy
// - y'/z' *dx*fx + x'/z' * dy*fy
// A = J *J转置*(*(buf_weight_p+i))
// b = -J转置*(*(buf_weight_p+i))*lastErr
// Vector6 inc = A.ldlt().solve(b);// LDLT矩阵分解求解 dse3 李代数更新量
Vector6 SE3Tracker::calculateWarpUpdate(
NormalEquationsLeastSquares &ls)
{
// weightEstimator.reset();
// weightEstimator.estimateDistribution(buf_warped_residual, buf_warped_size);
// weightEstimator.calcWeights(buf_warped_residual, buf_warped_weights, buf_warped_size);
//
ls.initialize(width*height);
for(int i=0;i<buf_warped_size;i++)
{
float px = *(buf_warped_x+i);//x'
float py = *(buf_warped_y+i);//y'
float pz = *(buf_warped_z+i);//z'
float r = *(buf_warped_residual+i);//误差
float gx = *(buf_warped_dx+i);// dx*fx
float gy = *(buf_warped_dy+i);//dy*fx
// step 3 + step 5 comp 6d error vector
float z = 1.0f / pz;// 1/z'
float z_sqr = 1.0f / (pz*pz);//1/z'^2
Vector6 v;
v[0] = z*gx + 0;
v[1] = 0 + z*gy;
v[2] = (-px * z_sqr) * gx +
(-py * z_sqr) * gy;
v[3] = (-px * py * z_sqr) * gx +
(-(1.0 + py * py * z_sqr)) * gy;
v[4] = (1.0 + px * px * z_sqr) * gx +
(px * py * z_sqr) * gy;
v[5] = (-py * z) * gx +
(px * z) * gy;
// step 6: integrate into A and b:
// J转置*J * dse3 = -J转置*w*error
// v为求得的雅克比矩阵J r为误差 *(buf_weight_p+i)为误差权重
ls.update(v, r, *(buf_weight_p+i));// 这里计算的是 A 和 b的和 以及加权误差平方和 error
}
// 欧式变换矩阵se3 更新后的量
Vector6 result;
// solve ls
// 对求得的 A 和 b的和以及加权误差平方和 error 求均值
ls.finish();
// LDLT求解线性方程组 A*x=b x = A.ldlt().solve(b);
ls.solve(result);
return result;
}
}