1. 基本概念
- Ceres Solver是一個開源C ++庫,用於建模和解決大型複雜的優化問題。
- 用途
- 具有邊界約束的非線性最小二乘問題
- 一般無約束優化問題(等價於)
- 具有邊界約束的非線性最小二乘問題
- 元素描述
- : 殘差函數(Residual Function)
- :參數塊 (Parameter Block)
- :殘差塊(Residual Block)
- :損失函數(Loss Function),它是一個標量函數,用於減少異常值對非線性最小二乘問題解的影響
2. 使用流程
- 第一步:定義殘差函數:定義一個代價函數的結構體(),在結構體內重載()運算符
- 第二步:創建一個(把作爲其參數)
- 第三步:增加一個殘差塊(Problem.AddResidualBlock)
- 第四步:問題求解(Problem.Solve)
- 下面以曲線擬合爲例,有很多對(x,y)的觀測值,求參數m和c
2.1 定義殘差函數
struct ExponentialResidual {
ExponentialResidual(double x, double y)
: x_(x), y_(y) {}
// ()函數的參數爲待求值和殘差值
template <typename T> bool operator()(const T* const m,
const T* const c,
T* residual) const {
residual[0] = y_ - exp(m[0] * x_ + c[0]);
return true;
}
private:
const double x_; // 保存觀測值,本結構體創建進傳入
const double y_;
};
2.2 創建CostFunction 並增加殘差塊
-
爲每個觀測值創建一個CostFunction並加入Problem中
-
class CostFunction (殘差對象)
這是用戶和Ceres優化器之間的建模層。 函數的簽名(輸入參數塊的數量和大小以及輸出的數量)分別存儲在parameter_block_sizes_和num_residuals_中。 從此類繼承的用戶代碼應該使用相應的訪問器設置這兩個成員。 添加AddResidualBlock()時,將通過問題驗證此信息。 -
class AutoDiffCostFunction
是CostFunction的派生類,它自動計算殘差對每個參數的層數(即jacobian),它是一個模板類
template <typename CostFunctor,
int kNumResiduals, // Number of residuals, or ceres::DYNAMIC.
int N0, // Number of parameters in block 0.
int N1 = 0, // Number of parameters in block 1.
int N2 = 0, // Number of parameters in block 2.
int N3 = 0, // Number of parameters in block 3.
int N4 = 0, // Number of parameters in block 4.
int N5 = 0, // Number of parameters in block 5.
int N6 = 0, // Number of parameters in block 6.
int N7 = 0, // Number of parameters in block 7.
int N8 = 0, // Number of parameters in block 8.
int N9 = 0> // Number of parameters in block 9.
// Takes ownership of functor. Uses the template-provided value for the
// number of residuals ("kNumResiduals").
// 支持殘差個數爲動態的:ceres::DYNAMIC
explicit AutoDiffCostFunction(CostFunctor* functor)
// Takes ownership of functor. Ignores the template-provided
// kNumResiduals in favor of the "num_residuals" argument provided.
// This allows for having autodiff cost functions which return varying
// numbers of residuals at runtime.
// 不支持殘差個數爲動態的:ceres::DYNAMIC
AutoDiffCostFunction(CostFunctor* functor, int num_residuals)
- Problem.AddResidualBlock
// Add a residual block to the overall cost function. The cost
// function carries with it information about the sizes of the
// parameter blocks it expects. The function checks that these match
// the sizes of the parameter blocks listed in parameter_blocks. The
// program aborts if a mismatch is detected. loss_function can be
// NULL, in which case the cost of the term is just the squared norm
// of the residuals.
ResidualBlockId AddResidualBlock(CostFunction* cost_function,
LossFunction* loss_function,
const std::vector<double*>& parameter_blocks);
// Convenience methods for adding residuals with a small number of
// parameters. This is the common case. Instead of specifying the
// parameter block arguments as a vector, list them as pointers.
ResidualBlockId AddResidualBlock(CostFunction* cost_function,
LossFunction* loss_function,
double* x0);
ResidualBlockId AddResidualBlock(CostFunction* cost_function,
LossFunction* loss_function,
double* x0, double* x1);
ResidualBlockId AddResidualBlock(CostFunction* cost_function,
LossFunction* loss_function,
double* x0, double* x1, double* x2,
double* x3, double* x4, double* x5,
double* x6, double* x7, double* x8,
double* x9);
- Problem.AddParameterBlock
- 參數即爲要求值的變量
- 也就是殘差函數的「bool operator()」中除residual之外的參數
// Add a parameter block with appropriate size to the problem.
// Repeated calls with the same arguments are ignored. Repeated
// calls with the same double pointer but a different size results
// in undefined behaviour.
void AddParameterBlock(double* values, int size);
// Add a parameter block with appropriate size and parameterization
// to the problem. Repeated calls with the same arguments are
// ignored. Repeated calls with the same double pointer but a
// different size results in undefined behaviour.
void AddParameterBlock(double* values,
int size,
LocalParameterization* local_parameterization);
// Hold the indicated parameter block constant during optimization.
void SetParameterBlockConstant(double* values);
// Allow the indicated parameter block to vary during optimization.
void SetParameterBlockVariable(double* values);
- 實例
double m = 0.0;
double c = 0.0;
Problem problem;
for (int i = 0; i < kNumObservations; ++i) {
CostFunction* cost_function =
new AutoDiffCostFunction<ExponentialResidual, 1, 1, 1>(
new ExponentialResidual(data[2 * i], data[2 * i + 1]));
problem.AddResidualBlock(cost_function,
new CauchyLoss(0.5),
&m, &c);
}
2.3 問題求解
Solver::Options options;
options.linear_solver_type = ceres::DENSE_QR;
options.minimizer_progress_to_stdout = true;
Solver::Summary summary;
Solve(options, &problem, &summary);
std::cout << summary.BriefReport() << "\n";
std::cout << "Initial m: " << 0.0 << " c: " << 0.0 << "\n";
std::cout << "Final m: " << m << " c: " << c << "\n";
3. 完整描述
// For example, consider a scalar error e = k - x'y, where both x and y are
// two-dimensional column vector parameters, the prime sign indicates
// transposition, and k is a constant. The form of this error, which is the
// difference between a constant and an expression, is a common pattern in least
// squares problems. For example, the value x'y might be the model expectation
// for a series of measurements, where there is an instance of the cost function
// for each measurement k.
//
// The actual cost added to the total problem is e^2, or (k - x'k)^2; however,
// the squaring is implicitly done by the optimization framework.
//
// To write an auto-differentiable cost function for the above model, first
// define the object
class MyScalarCostFunctor {
MyScalarCostFunctor(double k): k_(k) {}
template <typename T>
bool operator()(const T* const x , const T* const y, T* e) const {
e[0] = T(k_) - x[0] * y[0] + x[1] * y[1];
return true;
}
private:
double k_;
};
// Note that in the declaration of operator() the input parameters x and y come
// first, and are passed as const pointers to arrays of T. If there were three
// input parameters, then the third input parameter would come after y. The
// output is always the last parameter, and is also a pointer to an array. In
// the example above, e is a scalar, so only e[0] is set.
//
// Then given this class definition, the auto differentiated cost function for
// it can be constructed as follows.
CostFunction* cost_function
= new AutoDiffCostFunction<MyScalarCostFunctor, 1, 2, 2>(
new MyScalarCostFunctor(1.0));
// | | |
// Dimension of residual -----+ | |
// Dimension of x ---------------+ |
// Dimension of y ------------------+
//
// In this example, there is usually an instance for each measumerent of k.
//
// In the instantiation above, the template parameters following
// "MyScalarCostFunctor", "1, 2, 2", describe the functor as computing a
// 1-dimensional output from two arguments, both 2-dimensional.
//
// AutoDiffCostFunction also supports cost functions with a
// runtime-determined number of residuals. For example:
CostFunction* cost_function
= new AutoDiffCostFunction<MyScalarCostFunctor, DYNAMIC, 2, 2>(
new CostFunctorWithDynamicNumResiduals(1.0), ^ ^ ^
runtime_number_of_residuals);
^ | | |
// | | | |
// | | | |
// Actual number of residuals ------+ | | |
// Indicate dynamic number of residuals --------+ | |
// Dimension of x ------------------------------------+ |
// Dimension of y ---------------------------------------+
//
// The framework can currently accommodate cost functions of up to 10
// independent variables, and there is no limit on the dimensionality
// of each of them.