基于gici多传感器融合定位的图优化代码学习

前言

本文是基于gici-open项目对因子图优化GraphC++类 的学习,由于此项目的最小二乘估计部分采用了google的开源ceres库,可以从ceres的官方帮助文档处了解:Solving Non-linear Least Squares — Ceres Solver (ceres-solver.org)

在graph.h的406行处找到:

 /// @brief Solve the optimization problem.void solve(){Solve(options, problem_.get(), &summary);//调用ceres库的Solve函数进行融合参数估计}

**options ** 是ceres求解过程中的参数设置,比如设定迭代时间,迭代次数,使用什么样的方法

problem_ 其中定义了残差块即最小二乘估计的数据来源(参数块,残差块)以及描述参数块之间关系的代价函数(cost_function)、表达残差权值的损失函数(loss_function),虽然ceres开发者极力推荐自动求导求系数阵,但该项目开发者还是自己定义了方法去求导

&summary 是估计出来的结果

更多关于ceres的基础知识可以前往下面链接:

Ceres学习笔记建模篇001_代价函数基类CostFunction及其派生类SizedCostFunction介绍_ceres::sizedcostfunction-CSDN博客

1、构造函数Graph()

// 构造函数。
Graph::Graph(): residual_counter_(0)
{// 设置 Ceres Solver Problem 的选项。ceres::Problem::Options problemOptions;problemOptions.local_parameterization_ownership =ceres::Ownership::DO_NOT_TAKE_OWNERSHIP;problemOptions.loss_function_ownership =ceres::Ownership::DO_NOT_TAKE_OWNERSHIP;problemOptions.cost_function_ownership =ceres::Ownership::DO_NOT_TAKE_OWNERSHIP;// 创建一个新的 Ceres Solver Problem,并用指定的选项初始化它。problem_.reset(new ceres::Problem(problemOptions));}
  1. 成员初始化列表:
    • residual_counter_(0):用值 0 初始化成员变量 residual_counter_。该变量是 Graph 类的成员。
  2. Ceres Solver Problem 选项:
    • ceres::Problem::Options:这是一个用于配置 Ceres Solver Problem 类行为的选项结构。
    • 下面的几行设置了 Ceres Solver Problem 的一些选项:
      • problemOptions.local_parameterization_ownership:指定局部参数化的所有权策略。
      • problemOptions.loss_function_ownership:指定损失函数的所有权策略。
      • problemOptions.cost_function_ownership:指定代价函数的所有权策略。
  3. Ceres Solver Problem 初始化:
    • problem_.reset(new ceres::Problem(problemOptions)):这一行创建了一个新的 ceres::Problem 类的实例,并用指定的选项进行初始化。reset 函数用于管理 Problem 对象的所有权。

2、parameterBlockExists()

// Check whether a certain parameter block is part of the graph.
bool Graph::parameterBlockExists(uint64_t parameter_block_id) const
{  使用 id_to_parameter_block_map_ 查找特定参数块的 IDif (id_to_parameter_block_map_.find(parameter_block_id)== id_to_parameter_block_map_.end()){// 如果参数块的 ID 未找到,返回 false,表示参数块不存在于图中return false;}return true;
}

3、gttLhs()

//获取特定参数的海森矩阵块(即二阶导参数系数阵)
// Obtain the Hessian block for a specific parameter block.
void Graph::getLhs(uint64_t parameter_block_id, Eigen::MatrixXd& H)
{CHECK(parameterBlockExists(parameter_block_id))<< "parameter block not in graph.";ResidualBlockCollection res = residuals(parameter_block_id);//根据参数块id获取残差块H.setZero();//初始化海森矩阵for (size_t i = 0; i < res.size(); ++i)//循环处理每个残差块{// parameters:ParameterBlockCollection pars = parameters(res[i].residual_block_id);//获取残差块相应的参数块//为参数块和残差块分配内存double** parameters_raw = new double*[pars.size()];Eigen::VectorXd residuals_eigen(res[i].error_interface_ptr->residualDim());double* residuals_raw = residuals_eigen.data();//为雅克比矩阵分配内存double** jacobians_raw = new double*[pars.size()];std::vector<Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>,Eigen::aligned_allocator<Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic,Eigen::RowMajor> > > jacobiansEigen(pars.size());double** jacobians_minimal_raw = new double*[pars.size()];std::vector<Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>,Eigen::aligned_allocator<Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic,Eigen::RowMajor> > > jacobians_minimal_eigen(pars.size());int J = -1;for (size_t j = 0; j < pars.size(); ++j){ //对应雅克比矩阵与相应参数块// determine which is the relevant blockif (pars[j].second->id() == parameter_block_id)J = j;parameters_raw[j] = pars[j].second->parameters();jacobiansEigen[j].resize(res[i].error_interface_ptr->residualDim(),pars[j].second->dimension());jacobians_raw[j] = jacobiansEigen[j].data();jacobians_minimal_eigen[j].resize(res[i].error_interface_ptr->residualDim(),pars[j].second->minimalDimension());jacobians_minimal_raw[j] = jacobians_minimal_eigen[j].data();}// evaluate residual block评价残差块res[i].error_interface_ptr->EvaluateWithMinimalJacobians(parameters_raw,residuals_raw,jacobians_raw,jacobians_minimal_raw);// get block将雅克比矩阵J经过J'*J赋给海森矩阵H += jacobians_minimal_eigen[J].transpose() * jacobians_minimal_eigen[J];// cleanupdelete[] parameters_raw;delete[] jacobians_raw;delete[] jacobians_minimal_raw;}
}

residual(parameter_block_id)

// Get the residual blocks of a parameter block.
Graph::ResidualBlockCollection Graph::residuals(uint64_t parameter_block_id) const
{// get the residual blocks of a parameter blockIdToResidualBlockMultimap::const_iterator it1 = id_to_residual_block_multimap_.find(parameter_block_id);//根据参数块id找到残差块if (it1 == id_to_residual_block_multimap_.end())//如果未找到与参数块相关联的残差块,则返回一个空的集合return Graph::ResidualBlockCollection();  // emptyResidualBlockCollection returnResiduals;std::pair<IdToResidualBlockMultimap::const_iterator,IdToResidualBlockMultimap::const_iterator> range =id_to_residual_block_multimap_.equal_range(parameter_block_id);//获取与参数块ID相关联的残差块范围for (IdToResidualBlockMultimap::const_iterator it = range.first;it != range.second; ++it){//遍历残差块,并将每个残差块添加到returnResiduals集合中returnResiduals.push_back(it->second);}return returnResiduals;//返回包含与给定参数块ID相关联的所有残差块的集合
}

4、isMinimalJacobianCorrect()

// Check a Jacobian with numeric differences.通过数值差分检查雅可比矩阵是否正确
bool Graph::isMinimalJacobianCorrect(ceres::ResidualBlockId residual_block_id,double relTol) const
{ // 获取残差块的错误接口指针和参数块集合std::shared_ptr<const ErrorInterface> error_interface_ptr =errorInterfacePtr(residual_block_id);ParameterBlockCollection parameter_blocks = parameters(residual_block_id);// set up data structures for storage 设置用于存储雅克比矩阵的数据结构std::vector<Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>,Eigen::aligned_allocator<Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor> > >J(parameter_blocks.size());//J存储完整的雅克比矩阵std::vector<Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>,Eigen::aligned_allocator<Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor> > >J_min(parameter_blocks.size());//J_min存储简化的雅克比矩阵std::vector<Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>,Eigen::aligned_allocator<Eigen::Matrix<double, Eigen::Dynamic,Eigen::Dynamic, Eigen::RowMajor> > > J_min_numDiff(//存储简化的雅可比矩阵通过数值差分得到的结果parameter_blocks.size());std::vector<double*> parameters(parameter_blocks.size());std::vector<double*> jacobians(parameter_blocks.size());std::vector<double*> jacobians_minimal(parameter_blocks.size());for (size_t i = 0; i < parameter_blocks.size(); ++i){// fill inJ[i].resize(error_interface_ptr->residualDim(), //对第i个雅可比矩阵进行重新调整大小,使其与相应的参数块维度匹配parameter_blocks[i].second->dimension());J_min[i].resize(error_interface_ptr->residualDim(),//调整为与参数块的最小维度匹配parameter_blocks[i].second->minimalDimension());J_min_numDiff[i].resize(error_interface_ptr->residualDim(),//为了存储数值差分得到的雅可比矩阵parameter_blocks[i].second->minimalDimension());parameters[i] = parameter_blocks[i].second->parameters();//将第i个参数块的参数指针存储到 parameters 数组中jacobians[i] = J[i].data();//将第i个雅可比矩阵的数据指针存储到 jacobians 数组中jacobians_minimal[i] = J_min[i].data();}// calculate num diff Jacobiansconst double delta = 1e-8;//用于表示微小的变化量for (size_t i = 0; i < parameter_blocks.size(); ++i)//循环遍历每一个参数块{ //循环遍历当前参数块的最小维度for (size_t j = 0; j < parameter_blocks[i].second->minimalDimension(); ++j){Eigen::VectorXd residuals_p(error_interface_ptr->residualDim());//存储正变化量后的残差值Eigen::VectorXd residuals_m(error_interface_ptr->residualDim());//存储负变化量的残差值// apply positive deltaEigen::VectorXd parameters_p(parameter_blocks[i].second->dimension());//应用正变化量的参数向量Eigen::VectorXd parameters_m(parameter_blocks[i].second->dimension());//应用负变化量的参数向量Eigen::VectorXd plus(parameter_blocks[i].second->minimalDimension());//通过plus函数实现参数的变化plus.setZero();plus[j] = delta;parameter_blocks[i].second->plus(parameters[i], plus.data(),//将当前参数向量与给定的变化量相加得到新的参数向量parameters_p.data());parameters[i] = parameters_p.data();error_interface_ptr->EvaluateWithMinimalJacobians(parameters.data(), //计算应用了正变化量后的残差值residuals_p.data(),nullptr,nullptr);parameters[i] = parameter_blocks[i].second->parameters();  // reset 将参数向量重置回原始值// apply negative delta plus.setZero();plus[j] = -delta;parameter_blocks[i].second->plus(parameters[i], plus.data(),parameters_m.data());parameters[i] = parameters_m.data();error_interface_ptr->EvaluateWithMinimalJacobians(parameters.data(), //计算应用了负变化量后的残差值residuals_m.data(),nullptr,nullptr);parameters[i] = parameter_blocks[i].second->parameters();  // reset// calculate numeric difference 利用数值差分的定义(f(x+δ) - f(x-δ))/2δ计算得到当前参数块在当前方向上的数值差分雅可比矩阵J_min_numDiff[i].col(j) = (residuals_p - residuals_m) * 1.0 / (2.0 * delta);//存储数值差分}}// calculate analytic Jacobians and compare 计算解析雅可比矩阵并进行比较bool isCorrect = true; //表示解析雅可比矩阵是否正确Eigen::VectorXd residuals(error_interface_ptr->residualDim());//用于存储残差的值for (size_t i = 0; i < parameter_blocks.size(); ++i)//循环遍历参数块每一个参数块{// calc 计算当前参数块的解析雅可比矩阵,并存储在 jacobians 和 jacobians_minimal 中error_interface_ptr->EvaluateWithMinimalJacobians(parameters.data(),residuals.data(),jacobians.data(),jacobians_minimal.data());// checkdouble norm_minimal = J_min_numDiff[i].norm();//计算数值差分雅可比矩阵的范数Eigen::MatrixXd J_diff_minimal = J_min_numDiff[i] - J_min[i];//计算数值差分雅可比矩阵和解析雅可比矩阵之间的差异double max_diff_minimal =std::max(-J_diff_minimal.minCoeff(), J_diff_minimal.maxCoeff());//找出差异的最大值if (max_diff_minimal / norm_minimal > relTol)//如果相对差异超过了预先设定的相对容差 relTol{ //输出相关的信息,包括残差类型、数值差分雅可比矩阵、解析雅可比矩阵、相对误差和相对容差LOG(INFO) << "Minimal Jacobian inconsistent: "<< kErrorToStr.at(error_interface_ptr->typeInfo());LOG(INFO) << "num diff Jacobian[" << i << "]:\n" << J_min_numDiff[i];LOG(INFO) << "provided Jacobian[" << i << "]:\n" << J_min[i];LOG(INFO) << "relative error: " << max_diff_minimal / norm_minimal<< ", relative tolerance: " << relTol;isCorrect = false;//则将 isCorrect 设置为 false}}return isCorrect;
}

5、addParameterBlock

// Add a parameter block to the map 向图(ceres problem)中添加参数块
bool Graph::addParameterBlock(std::shared_ptr<ParameterBlock> parameter_block,int parameterization, const int /*group*/)
{CHECK(parameter_block != nullptr);VLOG(200) << "Adding parameter block with parameterization "<< parameterization << " and id " << BackendId(parameter_block->id());// check Id availability // 检查ID是否已经存在if (parameterBlockExists(parameter_block->id())){ // 如果ID已经存在,输出错误日志并返回falseLOG(ERROR) << "Parameter block with id " << BackendId(parameter_block->id())<< " exists already!";return false;}// 将参数块的ID和智能指针添加到地图中id_to_parameter_block_map_.insert(std::pair<uint64_t, std::shared_ptr<ParameterBlock> >(parameter_block->id(), parameter_block));// also add to ceres problem  将参数块也添加到ceres problem中switch (parameterization){case Parameterization::Trivial:{ // 如果参数化方式是Trivial,则将参数块添加到ceres problem中problem_->AddParameterBlock(parameter_block->parameters(),parameter_block->dimension());break;}case Parameterization::HomogeneousPoint:{ // 如果参数化方式是HomogeneousPoint,则将参数块添加到ceres problem中,并设置局部参数化方式problem_->AddParameterBlock(parameter_block->parameters(),parameter_block->dimension(),&homogeneous_point_local_parameterization_);parameter_block->setLocalParameterizationPtr(&homogeneous_point_local_parameterization_);break;}case Parameterization::Pose6d:{ // 如果参数化方式是Pose6d,则将参数块添加到ceres problem中,并设置局部参数化方式problem_->AddParameterBlock(parameter_block->parameters(),parameter_block->dimension(),&pose_local_parameterization_);parameter_block->setLocalParameterizationPtr(&pose_local_parameterization_);break;}default:{LOG(ERROR) << "Unknown parameterization!";return false;break;  // just for consistency...}}/*const LocalParamizationAdditionalInterfaces* ptr =dynamic_cast<const LocalParamizationAdditionalInterfaces*>(parameter_block->localParameterizationPtr());if(ptr)std::cout<<"verify local size "<< parameter_block->localParameterizationPtr()->LocalSize() << " = "<<int(ptr->verify(parameter_block->parameters()))<<std::endl;*/return true;
}

6、addResidualBlock

  // add in ceres 将成本函数、损失函数和参数块指针向量传递给它,并将返回残差块的IDreturn_id = problem_->AddResidualBlock(cost_function.get(), loss_function,parameter_blocks);if (FLAGS_v >=200)//如果日志级别大于等于200{std::stringstream s;s << "Adding residual block: "<< kErrorToStr.at(std::dynamic_pointer_cast<ErrorInterface>(cost_function)->typeInfo())<< " with id " << return_id<< " connected to the following parameter blocks:\n";for (auto block : parameter_block_ptrs){s << BackendId(block->id()) << "\n";}VLOG(200) << s.str();}// add in book-keepingstd::shared_ptr<ErrorInterface> error_interface_ptr = //存储cost_function的ErrorInterface接口的指针std::dynamic_pointer_cast<ErrorInterface>(cost_function);//将cost_function强制转换为ErrorInterface指针类型CHECK(error_interface_ptr!=0)<< "Supplied a cost function without ErrorInterface";residual_block_id_to_residual_block_spec_map_.insert( //关联了残差块的ID和一个ResidualBlockSpec对象std::pair< ceres::ResidualBlockId, ResidualBlockSpec>(//存储了残差块的ID、损失函数以及ErrorInterface指针return_id,ResidualBlockSpec(return_id, loss_function, error_interface_ptr)));// update book-keeping 更新参数块集合的映射bool insertion_success;std::tie(std::ignore, insertion_success) = //如果插入成功,则返回return_idresidual_block_id_to_parameter_block_collection_map_.insert(std::make_pair(return_id, parameter_block_collection));if (insertion_success == false){ //如果插入失败,则返回一个值为0的ceres::ResidualBlockId对象return ceres::ResidualBlockId(0);}//更新了涉及的参数块上的ResidualBlock指针的映射// update ResidualBlock pointers on involved ParameterBlocksfor (uint64_t parameter_id = 0; //遍历参数块parameter_id < parameter_block_collection.size(); ++parameter_id){id_to_residual_block_multimap_.insert(std::pair<uint64_t, ResidualBlockSpec>( //参数块的ID与对应的ResidualBlockSpec对象关联起来parameter_block_collection[parameter_id].first,ResidualBlockSpec(return_id, loss_function, error_interface_ptr)));}return return_id;
}

重载版本

//这个重载版本的函数使得向图中添加残差块时更加方便,可以直接传递多个参数块的智能指针,而不必手动创建参数块的指针向量
// Add a residual block. See respective ceres docu. If more are needed, see other interface.
ceres::ResidualBlockId Graph::addResidualBlock(std::shared_ptr< ceres::CostFunction> cost_function,ceres::LossFunction* loss_function,std::shared_ptr<ParameterBlock> x0, //x0到x9是参数块的智能指针,表示与该残差相关联的参数块std::shared_ptr<ParameterBlock> x1,std::shared_ptr<ParameterBlock> x2,std::shared_ptr<ParameterBlock> x3,std::shared_ptr<ParameterBlock> x4,std::shared_ptr<ParameterBlock> x5,std::shared_ptr<ParameterBlock> x6,std::shared_ptr<ParameterBlock> x7,std::shared_ptr<ParameterBlock> x8,std::shared_ptr<ParameterBlock> x9)
{CHECK(cost_function != nullptr);std::vector<std::shared_ptr<ParameterBlock> > parameter_block_ptrs;if (x0 != 0) //检查每个参数块是否为非空指针,如果是非空指针,则将其添加到parameter_block_ptrs向量中{parameter_block_ptrs.push_back(x0);}if (x1 != 0){parameter_block_ptrs.push_back(x1);}if (x2 != 0){parameter_block_ptrs.push_back(x2);}if (x3 != 0){parameter_block_ptrs.push_back(x3);}if (x4 != 0){parameter_block_ptrs.push_back(x4);}if (x5 != 0){parameter_block_ptrs.push_back(x5);}if (x6 != 0){parameter_block_ptrs.push_back(x6);}if (x7 != 0){parameter_block_ptrs.push_back(x7);}if (x8 != 0){parameter_block_ptrs.push_back(x8);}if (x9 != 0){parameter_block_ptrs.push_back(x9);}return Graph::addResidualBlock(cost_function, loss_function, parameter_block_ptrs);}

7、resetResidualBlock

// Replace the parameters connected to a residual block ID.
void Graph::resetResidualBlock(ceres::ResidualBlockId residual_block_id, //需要重置的残差块IDstd::vector<std::shared_ptr<ParameterBlock> >& parameter_block_ptrs)//参数块指针
{// remember the residual block spec:ResidualBlockSpec spec =//通过residual_block_id从映射中获取相应的残差块规范,将其存储在spec中residual_block_id_to_residual_block_spec_map_[residual_block_id];// remove residual from old parameter set 删除旧参数集中的残差ResidualBlockIdToParameterBlockCollectionMap::iterator it =//在参数块映射中查找给定残差块,并将其迭代器存储在it中residual_block_id_to_parameter_block_collection_map_.find(residual_block_id);CHECK(it!=residual_block_id_to_parameter_block_collection_map_.end())<< "residual block not in graph.";for (ParameterBlockCollection::iterator parameter_it = it->second.begin();//循环遍历参数块parameter_it != it->second.end(); ++parameter_it){uint64_t parameter_id = parameter_it->second->id();//获取当前参数块的idstd::pair<IdToResidualBlockMultimap::iterator,//在一个多重映射中查找具有给定参数ID的残差块,并将其范围存储在range中IdToResidualBlockMultimap::iterator> range = id_to_residual_block_multimap_.equal_range(parameter_id);CHECK(range.first!=id_to_residual_block_multimap_.end())<< "book-keeping is broken";for (IdToResidualBlockMultimap::iterator it2 = range.first;//遍历range范围内的残差块it2 != range.second;){if (residual_block_id == it2->second.residual_block_id){//将其中与给定residual_block_id相同的残差块从多重映射中移除,从而将残差块与参数的关联删除it2 = id_to_residual_block_multimap_.erase(it2);  // remove book-keeping}else{it2++;}}}
//更新与残差块相关联的参数块集合,并确保与这些参数块相关联的残差块指针也被更新ParameterBlockCollection parameter_block_collection;for (size_t i = 0; i < parameter_block_ptrs.size(); ++i)//遍历parameter_block_ptrs中的参数块指针{parameter_block_collection.push_back(ParameterBlockSpec(parameter_block_ptrs.at(i)->id(),//为每个参数块创建一个ParameterBlockSpec对象parameter_block_ptrs.at(i)));}// update book-keepingit->second = parameter_block_collection;//更新参数块// update ResidualBlock pointers on involved ParameterBlocks更新与参数块关联的残差块指针for (uint64_t parameter_id = 0;//循环遍历参数块parameter_id < parameter_block_collection.size(); ++parameter_id){ //将参数块与当前的残差块规范spec相关联,并将该关联插入到id_to_residual_block_multimap_中id_to_residual_block_multimap_.insert(std::pair<uint64_t, ResidualBlockSpec>(parameter_block_collection[parameter_id].first, spec));}
}

8、removeResidualBlock

// Remove a residual block.从图中移除一个残差块
bool Graph::removeResidualBlock(ceres::ResidualBlockId residual_block_id)
{VLOG(200) << "Removing residual block with ID " << residual_block_id;//通过给定的残差块id在映射中查找对应的参数块,并将其迭代器存储在it中ResidualBlockIdToParameterBlockCollectionMap::iterator it =residual_block_id_to_parameter_block_collection_map_.find(residual_block_id);if (it == residual_block_id_to_parameter_block_collection_map_.end()){LOG(ERROR) << "Residual block " << residual_block_id << " not in graph!";return false;}//使用Ceres Solver提供的RemoveResidualBlock函数,从Ceres中移除给定的残差块problem_->RemoveResidualBlock(residual_block_id);  // remove in ceresfor (ParameterBlockCollection::iterator parameter_it = it->second.begin();parameter_it != it->second.end(); ++parameter_it)//遍历参数块{uint64_t parameter_id = parameter_it->second->id();//获取参数块的idstd::pair<IdToResidualBlockMultimap::iterator,//在多重映射中查找与当前参数块ID相关联的所有残差块,并将范围存储在range中IdToResidualBlockMultimap::iterator> range = id_to_residual_block_multimap_.equal_range(parameter_id);CHECK(range.first!=id_to_residual_block_multimap_.end())<< "book-keeping is broken";for (IdToResidualBlockMultimap::iterator it2 = range.first;it2 != range.second;)//遍历与当前参数块关联的所有残差块,并将其中与给定残差块ID相同的残差块从多重映射中移除{if (residual_block_id == it2->second.residual_block_id){it2 = id_to_residual_block_multimap_.erase(it2);  // remove book-keeping}else{it2++;}}}residual_block_id_to_parameter_block_collection_map_.erase(it);  // remove book-keeping移除残差块的记录residual_block_id_to_residual_block_spec_map_.erase(residual_block_id);  // remove book-keeping移除残差块规范return true;
}

9、computeCovariance

计算给定参数块的协方差矩阵

// Get covariance estimation of given parameter blocks
bool Graph::computeCovariance(const std::vector<uint64_t>& parameter_block_ids,Eigen::MatrixXd& covariance,bool use_dense_svd)
{// Get parameters 获取参数信息std::vector<const double*> parameters;//参数指针std::vector<size_t> parameter_block_sizes;//参数维度std::vector<size_t> parameter_block_starts;//存储每个参数块在总参数向量中的起始位置size_t parameter_size = 0;//存储总参数向量的大小for (size_t i = 0; i < parameter_block_ids.size(); i++) {//遍历参数块auto it = id_to_parameter_block_map_.find(parameter_block_ids[i]);//获取对应的参数块if (it == id_to_parameter_block_map_.end()) {LOG(ERROR) << "Parameter block does not exist!";return false;}auto& parameter_block = it->second;parameters.push_back(parameter_block->parameters());// 将参数信息存储到向量中parameter_block_sizes.push_back(parameter_block->dimension());parameter_block_starts.push_back(parameter_size);parameter_size += parameter_block->dimension();}// Make pairs 使用了嵌套的循环来生成所有可能的参数块对,以便后续计算协方差std::vector<std::pair<const double*, const double*> > covariance_blocks;for (size_t i = 0; i < parameters.size(); i++) {for (size_t j = i; j < parameters.size(); j++) {covariance_blocks.push_back(std::make_pair(parameters[i], parameters[j]));//可以看做是上三角矩阵}}// Compute covarianceceres::Covariance::Options options;//配置ceres计算的参数// it can deal with rank deficient problem but very slowif (use_dense_svd) {//使用密集SVD算法进行计算options.algorithm_type = ceres::DENSE_SVD;options.null_space_rank = -1; //处理秩亏问题}ceres::Covariance covariance_handle(options);//创建一个Ceres协方差计算器对象if (!covariance_handle.Compute(covariance_blocks, problem_.get())) {//进行协方差计算,如果计算失败,则记录警告并退出LOG(WARNING) << "Failed to compute covariance!";return false;}
//根据参数块的维度和起始位置,从协方差计算器对象中获取对应的协方差块,并将其填充到最终的协方差矩阵中// Get covariancecovariance.resize(parameter_size, parameter_size);for (size_t i = 0; i < parameters.size(); i++) {for (size_t j = i; j < parameters.size(); j++) {size_t size_i = parameter_block_sizes[i];//参数块大小size_t size_j = parameter_block_sizes[j];size_t start_i = parameter_block_starts[i];//参数块起始指针size_t start_j = parameter_block_starts[j];const double* para_i = parameters[i];const double* para_j = parameters[j];Eigen::MatrixXd cov_i_j = Eigen::MatrixXd::Zero(size_i, size_j);//协方差块置0covariance_handle.GetCovarianceBlock(para_i, para_j, cov_i_j.data());//从解算器中获取指定协方差块covariance.block(start_i, start_j, size_i, size_j) = cov_i_j;//上三角矩阵covariance.block(start_j, start_i, size_j, size_i) = cov_i_j.transpose();//协方差矩阵转置对称}}return true;
}

10、computeTotalCost

// Evaluate all residual blocks and get total cost评估所有残差块和获取总的成本
double Graph::computeTotalCost(bool apply_loss_function)
{ResidualBlockCollection residual_collection = residuals();//获取图中的所有残差块double total_cost_squared = 0.0;//用于累积所有残差块的成本的平方和for (auto& residual : residual_collection)//遍历所有残差块{//调用Ceres Solver提供的EvaluateResidualBlock函数,评估当前残差块的残差,并将结果存储在cost向量中Eigen::VectorXd cost = Eigen::VectorXd::Zero(residual.error_interface_ptr->residualDim());problem_->EvaluateResidualBlock(residual.residual_block_id, apply_loss_function, cost.data(), nullptr, nullptr);const double cost_norm = cost.norm();//计算当前残差块的代价的范数total_cost_squared += cost_norm * cost_norm;//将范数的平方累积到 total_cost_squared}return sqrt(total_cost_squared);//返回总代价的平方根
}

resdiuals

// Get all the residual blocks
Graph::ResidualBlockCollection Graph::residuals() const
{ResidualBlockCollection returnResiduals;for (auto it : residual_block_id_to_residual_block_spec_map_) {returnResiduals.push_back(it.second);}return returnResiduals;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/675021.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

springboot(ssm大学生计算机基础网络教学系统 在线课程系统Java系统

springboot(ssm大学生计算机基础网络教学系统 在线课程系统Java系统 开发语言&#xff1a;Java 框架&#xff1a;springboot&#xff08;可改ssm&#xff09; vue JDK版本&#xff1a;JDK1.8&#xff08;或11&#xff09; 服务器&#xff1a;tomcat 数据库&#xff1a;mys…

JavaScript事件

事件 事件-表单 元素获得焦点 onfocus&#xff1a; onfocus 事件在对象获得焦点时发生。鼠标点击获取焦点是发生 onblur&#xff1a; onblur 事件发生在对象失去焦点时&#xff0c;比如说你有个文本框&#xff0c;你鼠标点击进去&#xff0c;只有在离开的时候才会执行onblu…

【原创】Qt库open62541 MinGW编译

一、前言 为了统一公司的驱动层开发&#xff0c;准备采用OpcUA的方式转发底层数据&#xff0c;而服务器有Windows Server&#xff0c;也有CentOS&#xff0c;因此想用Qt开发一个基于MinGW的OpcUA Server&#xff0c;这样就能跨平台部署。这里记录一下&#xff0c;希望对你也有用…

Compose | UI组件(十五) | Scaffold - 脚手架

文章目录 前言一、Scaffold脚手架简介二、Scaffold的主要组件三、如何使用Scaffold四、Compose中Scaffold脚手架的具体例子例子1&#xff1a;基本Scaffold布局例子2&#xff1a;带有Drawer的Scaffold布局例子3&#xff1a;带有Snackbar的Scaffold布局 总结 前言 Compose中的Sca…

Python循环语句——for循环临时变量作用域

一、引言 在Python编程中&#xff0c;变量是程序运行的核心。其中&#xff0c;临时变量扮演着重要的角色&#xff0c;用于存储中间结果或临时数据。然而&#xff0c;这些临时变量并非随意存在&#xff0c;它们受到作用域的限制。了解临时变量的作用域对于编写高效、可维护的代…

神经网络 | 常见的激活函数

Hi&#xff0c;大家好&#xff0c;我是半亩花海。本文主要介绍神经网络中必要的激活函数的定义、分类、作用以及常见的激活函数的功能。 目录 一、激活函数定义 二、激活函数分类 三、常见的几种激活函数 1. Sigmoid 函数 &#xff08;1&#xff09;公式 &#xff08;2&a…

代码随想录算法训练营第三十天 回溯算法总结、332.重新安排行程、51. N皇后、37. 解数独

代码随想录算法训练营第三十天 | 回溯算法总结、**332.重新安排行程、**51. N皇后、37. 解数独 回溯算法总结 回溯就是递归的副产品&#xff0c;只要有递归就会有回溯 回溯就是一个暴力搜索法&#xff0c;并不是什么高效的算法 回溯算法的题目分类&#xff1a; 组合&#…

LeetCode 第28天

93. 复原 IP 地址 这题挺难的&#xff0c;实际上我觉得分割字符串的题都挺难的&#xff0c;即使知道了回溯算法&#xff0c;也是无从下手。因为要对字符串进行处理&#xff0c;关于分割点不知道怎么处理。关键部分理解在代码里。 class Solution { private: // 判断分割的子串…

GPT每日面试题—如何理解JS原型链

充分利用ChatGPT的优势&#xff0c;帮助我们快速准备前端面试。今日问题&#xff1a;如何理解JS原型链&#xff1f; Q&#xff1a;如果在前端面试中&#xff0c;被问到如何理解JS原型链&#xff0c;怎么回答比较好&#xff1f; A&#xff1a;当面试官问到如何理解 JavaScript …

12.03 校招 实习 内推 面经

绿*泡*泡VX&#xff1a; neituijunsir 交流裙 &#xff0c;内推/实习/校招汇总表格 1、自动驾驶一周资讯 - 英伟达自动驾驶中国团队扩招&#xff1b;地平线与安波福首个合作成果取得定点&#xff1b;通用汽车自动驾驶Cruise首席执行官辞职 自动驾驶一周资讯 - 英伟达自动驾…

问题:创业者在组建创业团队时,在个人特征和动机方面更应该注重创业者的( ) #知识分享#微信#媒体

问题&#xff1a;创业者在组建创业团队时&#xff0c;在个人特征和动机方面更应该注重创业者的&#xff08; &#xff09; 参考答案如图所示

hook函数——useState

useState useState是React中的一个Hook函数&#xff0c;用于在函数组件中添加状态。基本使用语法如下&#xff1a; const [state, setState] useState(initialState) state&#xff1a;表示当前状态的值setState&#xff1a;更新状态的函数initialState&#xff1a;初始状态…

为什么是0.1uF电容?

旁路电容是电子设计中常用的电容器之一&#xff0c;主要用于过滤电源噪声和稳定电源电压。在实际应用中&#xff0c;0.1uF电容器是最常用的旁路电容值之一&#xff0c;那么为什么常用旁路电容是0.1uF而不是其他值&#xff1f;这个值又是怎么来的呢&#xff1f;本文将深入探讨这…

基于微信小程序的校园二手交易平台

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

【Java基础常见面试题】- Java SE vs Java EE

Java SE vs Java EE Java SE&#xff08;Java Platform&#xff0c;Standard Edition&#xff09;: Java 平台标准版&#xff0c;Java 编程语言的基础&#xff0c;它包含了支持 Java 应用程序开发和运行的核心类库以及虚拟机等核心组件。Java SE 可以用于构建桌面应用程序或简…

UDP 用户数据报协议

目录 1 UDP 1.1 UDP 的主要特点 1.1.1 UDP 是面向报文的 1.1.2 UDP 通信和端口号的关系 1.2 UDP 的首部格式 1.2.1 UDP 基于端口的分用 1.3 UDP抓包 1 UDP UDP 只在 IP 的数据报服务之上增加了一些功能&#xff1a; 1.复用和分用 2.差错检测 1.1 UDP 的主要特点 1.无连…

PSM-Net根据Stereo图像生成depth图像

一、新建文件夹 在KITTI数据集下新建depth_0目录 二、激活anaconda环境 conda activate pt14py37三、修改submission.py文件 3.1 KITTI数据集路径 parser.add_argument(--datapath, default/home/njust/KITTI_DataSet/00/, helpselect model)3.2 深度图像输出路径 save…

Vision Pro新机测评!“这才是MR硬件该有的模样!”

期盼很久的Vision Pro终于到了&#xff0c;小编迫不及待地体验了一把&#xff0c;效果相当非常震撼&#xff0c;操作非常丝滑&#xff0c;画面非常清晰…来不急解释了&#xff0c;快和小编一起来看一下吧~ 新机一到公司&#xff0c;为解大家对Vision Pro 的“相思之苦”&#x…

一篇文章理解时间复杂度和空间复杂度

今天也是很开心的学到了数据结构&#xff0c;也是打算把我自己对知识的理解给写出来了。第一篇数据结构开始咯。开始之前我们先理解一个概念。 什么是算法效率&#xff1f; 算法效率是指算法执行的速度或完成任务所需的资源&#xff08;如时间和空间&#xff09;的度量。它通…

Pymysql之Cursor常用API

Cursor常用API 1、cursor.execute(query, argsNone)&#xff1a;执行sql语句。 参数: query (str)&#xff1a;sql语句。 args (tuple, list or dict)&#xff1a;sql语句中如果有变量&#xff0c;或者格式化输出&#xff0c;会在这里填充数据。 Returns&#xff1a;返…