ceres库AddResidualBlock函数解析

在定义损失函数时,我们会预先告诉parameter_blocks的个数,class ProjectionFactorXYZ : public ceres::SizedCostFunction<2, 7, 3> 表示2个损失函数,2个参数块,第一个参数块有7个数,第二个参数块有3个数。我们在传给ceres时,传进去的是参数块的首地址。又知道参数块里面的参数个数。所以ceres就可以一个个把参数拿出来。

ResidualBlock* ProblemImpl::AddResidualBlock(
    CostFunction* cost_function,   
    LossFunction* loss_function,
    const vector<double*>& parameter_blocks) {  //传进来的参数块(注意是参数块,不是参数)的地址放在vector<double*>里面,两个参数块则vector里面有两个
  CHECK_NOTNULL(cost_function);   //检查损失函数
  CHECK_EQ(parameter_blocks.size(),
           cost_function->parameter_block_sizes().size()); //检查传进来的参数块和预先定义对否一样,比如我们定义的是两个

  // Check the sizes match.
  const vector<int32>& parameter_block_sizes =
      cost_function->parameter_block_sizes(); //比如我们定义的参数块是2个,里面分别有 7 3个参数,parameter_block_sizes里面放的是7 3

  if (!options_.disable_all_safety_checks) { //如果要做安全检查
    CHECK_EQ(parameter_block_sizes.size(), parameter_blocks.size())
        << "Number of blocks input is different than the number of blocks "
        << "that the cost function expects."; //检查参数块是否一致,其实上面检查了一遍
    // Check for duplicate parameter blocks.  //检查是否参数块有重复项
    vector<double*> sorted_parameter_blocks(parameter_blocks); //按照地址给参数块排序
    sort(sorted_parameter_blocks.begin(), sorted_parameter_blocks.end());
    const bool has_duplicate_items =
        (std::adjacent_find(sorted_parameter_blocks.begin(),
                            sorted_parameter_blocks.end())
         != sorted_parameter_blocks.end());
    if (has_duplicate_items) {     //这里是检查是否有参数块的地址是一样的
      string blocks;
      for (int i = 0; i < parameter_blocks.size(); ++i) {
        blocks += StringPrintf(" %p ", parameter_blocks[i]);
      }

      LOG(FATAL) << "Duplicate parameter blocks in a residual parameter "
                 << "are not allowed. Parameter block pointers: ["
                 << blocks << "]";
    }
  }

  // Add parameter blocks and convert the double*'s to parameter blocks.
  //这里是把参数块里的数据取出来放到ceres自己定义的参数块里面去,对参数块做了检查
  vector<ParameterBlock*> parameter_block_ptrs(parameter_blocks.size());
  for (int i = 0; i < parameter_blocks.size(); ++i) {
    parameter_block_ptrs[i] =
        InternalAddParameterBlock(parameter_blocks[i],
                                  parameter_block_sizes[i]); //参数块的地址,参数块里的参数个数
  }

  if (!options_.disable_all_safety_checks) {
    // Check that the block sizes match the block sizes expected by the
    // cost_function.
    for (int i = 0; i < parameter_block_ptrs.size(); ++i) {
      CHECK_EQ(cost_function->parameter_block_sizes()[i],
               parameter_block_ptrs[i]->Size())
          << "The cost function expects parameter block " << i
          << " of size " << cost_function->parameter_block_sizes()[i]
          << " but was given a block of size "
          << parameter_block_ptrs[i]->Size();
    }
  }

  ResidualBlock* new_residual_block =
      new ResidualBlock(cost_function,
                        loss_function,
                        parameter_block_ptrs,
                        program_->residual_blocks_.size());

  // Add dependencies on the residual to the parameter blocks.
  if (options_.enable_fast_removal) {
    for (int i = 0; i < parameter_blocks.size(); ++i) {
      parameter_block_ptrs[i]->AddResidualBlock(new_residual_block);
    }
  }

  program_->residual_blocks_.push_back(new_residual_block);

  if (options_.enable_fast_removal) {
    residual_block_set_.insert(new_residual_block);
  }

  if (options_.cost_function_ownership == TAKE_OWNERSHIP) {
    // Increment the reference count, creating an entry in the table if
    // needed. Note: C++ maps guarantee that new entries have default
    // constructed values; this implies integers are zero initialized.
    ++cost_function_ref_count_[cost_function];
  }

  if (options_.loss_function_ownership == TAKE_OWNERSHIP &&
      loss_function != NULL) {
    ++loss_function_ref_count_[loss_function];
  }

  return new_residual_block;
}

parameter_block_map_是一个map,里面放着参数块的地址和对应生成的ParameterBlock。这个变量应该是便于在增加参数块时,判断该参数块是否之前已经加入。

ParameterBlock* ProblemImpl::InternalAddParameterBlock(double* values,
                                                       int size) {
  CHECK(values != NULL) << "Null pointer passed to AddParameterBlock "
                        << "for a parameter with size " << size;  //参数块地址不为空

  // Ignore the request if there is a block for the given pointer already.
  ParameterMap::iterator it = parameter_block_map_.find(values); //查找该参数模块是否已经加入了
  if (it != parameter_block_map_.end()) {
    if (!options_.disable_all_safety_checks) {
      int existing_size = it->second->Size();
      CHECK(size == existing_size)
          << "Tried adding a parameter block with the same double pointer, "
          << values << ", twice, but with different block sizes. Original "
          << "size was " << existing_size << " but new size is "
          << size;
    }
    return it->second; //如果已经加入则返回已经生成的paramblock
  }  //

  if (!options_.disable_all_safety_checks) {
    // Before adding the parameter block, also check that it doesn't alias any
    // other parameter blocks. //检查它是否是其他参数块的别名
    if (!parameter_block_map_.empty()) {//其实就是找到values附近地址的参数块,看是否和values参数块的内存有重叠
      ParameterMap::iterator lb = parameter_block_map_.lower_bound(values);
		//找到比values地址大一点的参数块
      // If lb is not the first block, check the previous block for aliasing.
      if (lb != parameter_block_map_.begin()) {
        ParameterMap::iterator previous = lb;
        --previous;
        CheckForNoAliasing(previous->first,
                           previous->second->Size(),
                           values,
                           size);
      }

      // If lb is not off the end, check lb for aliasing.
      if (lb != parameter_block_map_.end()) {
        CheckForNoAliasing(lb->first,
                           lb->second->Size(),
                           values,
                           size);
      }
    }
  }
//上面既检查了是否已经存在该参数块,又检查了是否有参数块和该参数块内存出现重叠,如果都没有,说明该参数块是好的,则生成ParameterBlock
  // Pass the index of the new parameter block as well to keep the index in
  // sync with the position of the parameter in the program's parameter vector.
  ParameterBlock* new_parameter_block =
      new ParameterBlock(values, size, program_->parameter_blocks_.size());

  // For dynamic problems, add the list of dependent residual blocks, which is
  // empty to start.
  if (options_.enable_fast_removal) {
    new_parameter_block->EnableResidualBlockDependencies();
  }
  parameter_block_map_[values] = new_parameter_block;
  program_->parameter_blocks_.push_back(new_parameter_block);
  return new_parameter_block;
}

CheckForNoAliasing是判断两个参数块有没有重叠,比如参数块1的首地址是a,参数个数是size_a, 参数块2的首地址是b,参数个数是size_b.
我们已经知道这两个参数块是挨在一起的,假设a<b ,那么如果b < a+size_a,说明这两个参数块内存有部分重叠了,这肯定就有问题了。

<think>好的,用户想了解如何使用Ceres来实现最小二乘法,我需要根据提供的引用内容来构建回答。首先,我需要回顾Ceres的基本使用方法,特别是拟合曲线的例子,因为这是常见的最小二乘应用场景。根据引用[1],Ceres的使用步骤包括定义参数块、残差块的计算方式、雅可比的计算方式,然后将它们添加到Problem对象中并求解。 接下来,用户可能需要一个具体的示例。我需要确保示例代码正确,并且符合Ceres的结构。可能需要创建一个曲线拟合的问题,比如拟合一个二次曲线y = ax² + bx + c。这里参数块是a、b、c,残差是观测值与预测值的差。 然后,关于导数的部分,根据引用[2]和[4],自动微分是推荐的方法,所以在示例中使用自动微分,而避免数值微分。这样代码会更高效,用户也能看到如何利用Ceres的自动微分功能,而不需要手动计算雅可比矩阵。 另外,引用[3]提到其他优化如g2o,但用户的问题是关于Ceres的,所以可以暂时忽略这部分,但可能需要比较两者的不同,但当前问题不需要。 在代码示例中,需要定义残差结构体,使用模板运算符()来计算残差,并利用Jet类型来自动计算导数。需要确保代码的每个部分都有注释,说明参数块、残差块和求解过程。 同时,用户可能会遇到的问题包括如何添加参数块,如何设置残差函数,以及如何处理数据点。在示例中,应该遍历所有数据点,为每个点添加残差块,并传入对应的数据。 最后,根据用户的要求生成相关问题,可能需要包括Ceres的安装、自动微分与解析微分的比较,以及处理大规模问题的方法等。这些问题应该基于引用内容中的信息,比如引用[1]提到安装,引用[4]讨论导数方法的选择,以及引用[3]提到的不同线性求解器可能影响大规模问题的性能。 需要检查步骤是否清晰,代码是否正确,是否符合Ceres的实际用法。同时确保回答结构符合用户要求,使用中文,数学公式正确,行内公式用$...$,独立公式用$$...$$。例如,在问题描述中的曲线模型是y = ax² + bx + c,应该用行内公式,而目标函数可能需要独立公式。 现在,确认所有引用都被正确使用,比如在自动微分部分引用[2],在参数块定义部分引用[1],在雅可比计算建议部分引用[4]。 最后,确保生成的三个相关问题与内容相关,并且能够引导用户进一步学习。例如,安装方法、导数选择、大规模优化,这些都是用户可能接下来会问的问题。</think>### 如何使用Ceres实现最小二乘法 #### 1. Ceres实现最小二乘法的核心步骤 - **定义参数块**:将待优化参数(如曲线拟合中的系数)封装为向量块,支持常规向量或特殊结构(如四元数)[^1]。 - **构造残差块**:编写残差计算逻辑,将观测值与模型预测值的差值作为残差,目标是最小化残差平方和。 - **导数计算选择**:优先使用自动微分(`AutoDiffCostFunction`),避免手动解析雅可比矩阵,复杂场景可考虑解析微分优化性能[^2][^4]。 - **构建求解问题**:将参数块和残差块加入`Problem`对象,调用`Solve()`函数进行优化。 #### 2. 曲线拟合示例代码 以下代码演示用Ceres拟合二次曲线$y = ax^2 + bx + c$: ```cpp #include <ceres/ceres.h> // 残差计算结构体 struct QuadraticResidual { QuadraticResidual(double x, double y) : x_(x), y_(y) {} template <typename T> bool operator()(const T* const abc, T* residual) const { // 模型: y = a*x² + b*x + c residual[0] = T(y_) - (abc[0] * x_ * x_ + abc[1] * x_ + abc[2]); return true; } private: const double x_; const double y_; }; int main() { // 初始化参数: a=0, b=0, c=0 double abc[3] = {0.0, 0.0, 0.0}; // 构建最小二乘问题 ceres::Problem problem; for (const auto& data_point : observed_data) { ceres::CostFunction* cost_function = new ceres::AutoDiffCostFunction<QuadraticResidual, 1, 3>( new QuadraticResidual(data_point.x, data_point.y)); problem.AddResidualBlock(cost_function, nullptr, abc); } // 配置求解器参数 ceres::Solver::Options options; options.minimizer_progress_to_stdout = true; // 执行优化 ceres::Solver::Summary summary; ceres::Solve(options, &problem, &summary); return 0; } ``` #### 3. 关键实现细节说明 1. **自动微分实现**:通过模板类`AutoDiffCostFunction`自动推导雅可比矩阵,无需手动计算导数[^2]。 2. **残差块定义**:每个数据点对应一个残差块,残差值$r_i = y_i - (ax_i^2 + bx_i + c)$,目标函数为$\min \sum r_i^2$。 3. **参数块管理**:`abc[3]`数组存储待优化参数,通过`AddResidualBlock`动态添加残差项。 #### 4. 性能优化建议 - **解析导数**:当模型存在特殊代数结构时,可继承`SizedCostFunction`实现`Evaluate()`手动计算雅可比矩阵,提升计算效率。 - **参数块划分**:将高维参数分解为多个低维块,利用稀疏性加速求解[^3]。 - **求解器选择**:通过`LinearSolverType`选项选择迭代法(如`CGNR`)或直接法(如`DENSE_QR`)[^3]。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值