Ceres 获取雅可比及海塞矩阵

Reference:

  1. 基于ceres-solver 的边缘化的实现
  2. Ceres 官方文档
  3. Ceres jacobian and hessian evaluation

相关文章:

  1. Ceres-Solver 官方文档-其一
  2. Ceres-Solver 官方文档-其二

在执行边缘化过程中,我们需要构建整个问题对应的 Hessian 矩阵,然后将要被边缘化的状态变量移动到 Hessian 矩阵的右下角或者左下角,执行舒尔补。因此我们可以将边缘化问题拆解成两个子问题:Hessian 矩阵的构建和被边缘化的状态变量移动到 Hessian 矩阵的右下或左下角。

而本文仅记录使用 Ceres 的接口获取优化问题中 Jacobian 和 Hessian 矩阵的过程。

1. Ceres 接口

Problem::Evaluate

Ceres 中提供了输出 cost、residual、gradient 以及 jacobian 的接口:

bool Problem::Evaluate(const Problem::EvaluateOptions &options, 
                       double* cost, 
                       vector<double>* residuals,
                       vector<double>* gradient,
                       CRSMatrix* jacobian)

该接口用于评估 Problem,任何的输出空指针都可以为 NULL。使用哪些残差块和参数块由的 Problem::EvaluateOptions 结构体控制。

  1. Note 1
    计算将使用存储在构造问题时使用的参数块指针所指向的内存位置中的值,例如在以下代码中:

    Problem problem;
    double x = 1;
    problem.Add(new MyCostFunction, nullptr, &x);
    
    double cost = 0.0;
    problem.Evaluate(Problem::EvaluateOptions(), &cost, nullptr, nullptr, nullptr);
    

    cost 在 x=1 处评估。如果想求 x=2 时的值,那么:

    x = 2;
    problem.Evaluate(Problem::EvaluateOptions(), &cost, nullptr, nullptr, nullptr);
    
  2. Note 2
    如果没有使用 Manifold(local parameterizations),那么梯度向量(以及雅可比矩阵的列数)的大小是所有参数块大小的总和,参数块的排序由定义 problem 时状态变量的存储顺序决定。如果参数块具有流形,则它将 “TangentSize” 条目贡献给梯度向量(以及雅可比矩阵的列数)。

  3. Note 3
    问题正在被解决时不能调用此函数,例如,在求解过程中,不能在一个迭代结束时从 IterationCallback 调用它。

CRSMatrix

struct CERES_EXPORT CRSMatrix {
  CRSMatrix() : num_rows(0), num_cols(0) {}

  int num_rows;
  int num_cols;

  std::vector<int> cols;
  std::vector<int> rows;
  std::vector<double> values;
};

CRSMatrix 是一种压缩的行稀疏矩阵,主要用于将雅可比矩阵传递给用户。

一个压缩的行矩阵将其内容存储在三个数组中:行、列和值。

  • rows 是一个 num_rows+1 大小的数组,指向 cols 和 values 数组。对于每一行 i:
  • cols[rows[i]] ... cols[rows[i + 1] - 1] 是第 i 行非零列的下标。
  • values[rows[i]] .. values[rows[i + 1] - 1] 是对应条目的值。
  • cols 和 values 包含的条目与矩阵中非零的条目一样多。

比方说有一个 3x4 的稀疏矩阵:

[ 0 10  0  4 ]
[ 0  2 -3  2 ]
[ 1  2  0  0 ]

那么三个数组会是:

          -row0-  ---row1---  -row2-
rows   = [ 0,      2,          5,     7]
cols   = [ 1,  3,  1,  2,  3,  0,  1]
values = [10,  4,  2, -3,  2,  1,  2]

这样做存储是因为这是稀疏矩阵,以这种方式存储相比于每一个值都存下来会更省空间。

  • rows 以 0 开始,第 0 行有两个值,所以后面一个的值为 2,第 1 行有三个值,所以再后面一个值为 5;
  • 与上面的 0、2、5、7 对应,cols 与 valus 第 0 个开始为第 0 行的数,第 2 个开始为第 1 行的数,…。

CRSMatrix 转 Eigen

ceres::CRSMatrix 转 Eigen 参考以下链接:https://github.com/vitalemonate/testCRSMatrix

Eigen::MatrixXd CRSMatrix2EigenMatrix(ceres::CRSMatrix *jacobian_crs_matrix)
{
    Eigen::MatrixXd J(jacobian_crs_matrix->num_rows, jacobian_crs_matrix->num_cols);
    J.setZero();

    std::vector<int> jacobian_crs_matrix_rows, jacobian_crs_matrix_cols;
    std::vector<double> jacobian_crs_matrix_values;
    jacobian_crs_matrix_rows = jacobian_crs_matrix->rows;
    jacobian_crs_matrix_cols = jacobian_crs_matrix->cols;
    jacobian_crs_matrix_values = jacobian_crs_matrix->values;

    int cur_index_in_cols_and_values = 0;
    // rows is a num_rows + 1 sized array
    int row_size = static_cast<int>(jacobian_crs_matrix_rows.size()) - 1;
    // outer loop traverse rows, inner loop traverse cols and values
    for (int row_index = 0; row_index < row_size; ++row_index)
    {
        while (cur_index_in_cols_and_values < jacobian_crs_matrix_rows[row_index + 1])
        {
            J(row_index, jacobian_crs_matrix_cols[cur_index_in_cols_and_values]) = jacobian_crs_matrix_values[cur_index_in_cols_and_values];
            cur_index_in_cols_and_values++;
        }
    }
    return J;
}

2. 雅可比和海塞矩阵示例

void Estimator::optimization(){
    // ... 
    TicToc t_solver;
    ceres::Solver::Summary summary;
    ceres::Solve(options, &problem, &summary);
    //cout << summary.BriefReport() << endl;
    ROS_DEBUG("Iterations : %d", static_cast<int>(summary.iterations.size()));
    ROS_DEBUG("solver costs: %f", t_solver.toc());

    evaluateBA(problem, summary);
    cout << "evaluateBA end" << endl;
}

void Estimator::evaluateBA(ceres::Problem& problem, const ceres::Solver::Summary& summary){
    cout << summary.FullReport() << endl;

    ceres::Problem::EvaluateOptions EvalOpts;
    ceres::CRSMatrix jacobian_crs_matrix;
    problem.Evaluate(EvalOpts, nullptr, nullptr, nullptr, &jacobian_crs_matrix);

    TicToc t_convert_J;
    Eigen::MatrixXd J = CRSMatrix2EigenMatrix(&jacobian_crs_matrix);
    cout << "convert sparse matrix cost " << t_convert_J.toc() << endl;

    TicToc t_construct_H;
    Eigen::MatrixXd H = J.transpose()*J;
    cout << "construct H cost " << t_construct_H.toc() << endl;
}

注意这里计算出来的 Hessian 矩阵是考虑了各代价函数的信息矩阵的,如果修改各代价函数信息矩阵代销,这里得到的 Hessian 也会相应发生变化。

Ceres Solver是一个用于非线性最小二乘问题的开源C++库,它可以用于解各种优化问题,包括二维变换矩阵解。 以下是一个使用Ceres Solver解二维变换矩阵的示例代码: ```c++ #include <ceres/ceres.h> #include <ceres/rotation.h> #include <iostream> // 定义二维点的结构体 struct Point2D { double x; double y; }; // 定义二维变换矩阵的参数结构体 struct TransformParams { double angle; // 旋转角度 double scale; // 缩放因子 double tx; // 水平平移 double ty; // 垂直平移 }; // 定义Ceres Solver的Cost Function struct TransformCostFunctor { TransformCostFunctor(const Point2D& from, const Point2D& to) : from_(from), to_(to) {} template<typename T> bool operator()(const T* const params, T* residual) const { T rotated_x = cos(params[0]) * from_.x - sin(params[0]) * from_.y; T rotated_y = sin(params[0]) * from_.x + cos(params[0]) * from_.y; T scaled_x = params[1] * rotated_x; T scaled_y = params[1] * rotated_y; residual[0] = scaled_x + params[2] - to_.x; residual[1] = scaled_y + params[3] - to_.y; return true; } private: const Point2D from_; const Point2D to_; }; int main() { // 定义源点和目标点 Point2D from = { 2.0, 3.0 }; Point2D to = { 4.0, 5.0 }; // 定义Ceres Solver的Problem和参数块 ceres::Problem problem; double params[4] = { 0.0, 1.0, 0.0, 0.0 }; ceres::ParameterBlock* param_block = problem.AddParameterBlock(params, 4); // 定义Cost Function并添加到Problem中 ceres::CostFunction* cost_function = new ceres::AutoDiffCostFunction<TransformCostFunctor, 2, 4>(new TransformCostFunctor(from, to)); problem.AddResidualBlock(cost_function, nullptr, param_block); // 设置Solver的选项并ceres::Solver::Options options; options.minimizer_progress_to_stdout = true; ceres::Solver::Summary summary; ceres::Solve(options, &problem, &summary); // 输出结果 std::cout << summary.FullReport() << std::endl; std::cout << "Transform matrix: " << std::endl; std::cout << params[1] * cos(params[0]) << " " << -params[1] * sin(params[0]) << " " << params[2] << std::endl; std::cout << params[1] * sin(params[0]) << " " << params[1] * cos(params[0]) << " " << params[3] << std::endl; std::cout << "Residual: " << summary.final_cost << std::endl; return 0; } ``` 在上述代码中,我们首先定义了一个二维点的结构体`Point2D`和一个二维变换矩阵的参数结构体`TransformParams`。然后我们定义了一个Ceres Solver的Cost Function`TransformCostFunctor`,它的作用是计算从源点变换到目标点需要的变换矩阵,并计算该变换矩阵下源点和目标点之间的残差。在`TransformCostFunctor`的`operator()`函数中,我们首先根据旋转角度将源点旋转,然后根据缩放因子进行缩放,并根据平移量进行平移,最后计算残差。 在`main()`函数中,我们首先定义了源点和目标点,并初始化了参数块。然后我们定义了一个Cost Function并将其添加到Problem中。最后我们设置了Solver的选项并调用`ceres::Solve`函数解问题。解完成后,我们可以通过`ceres::Solver::Summary`结构体中的信息输出解结果和残差。 需要注意的是,上述代码只是解了一个二维变换矩阵的例子,实际应用中可能需要更复杂的Cost Function和更多的参数。Ceres Solver提供了丰富的工具和接口,可以用于各种非线性优化问题的解。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值