Ceres优化库—自动求导与解析求导的实现
Ceres提供了三种求导方式:自动求导、解析求导、数值求导。
1.自动求导
(1) 自动求导是通过定义一个仿函数,然后传给AutoDiffCostFunction, 只要给出残差和待优化变量的表达式,Ceres就能自动地求出导数。
(2) 所谓仿函数,其实是一个类,只不过这个类的作用像函数,所以叫仿函数。原理就是类实现了operator()函数。
(3) operator()函数必须是模板函数,因为Ceres内部求导要用到。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| struct MyAutoCostFunction { MyAutoCostFunction(double x, double y) : _x(x), _y(y) {}
template<typename T> bool operator()(const T *const abcd, T *residual) const { residual[0] = T(_y) - (pow(abcd[0] , T(_x)) + abcd[1] * T(_x) * T(_x) + abcd[2] * T(_x) + abcd[3]); return true; } const double _x, _y; };
|
2.解析求导
解析求导的含义就是直接给出导数的解析形式,而不是ceres去推导 。解析求导也兼容自动求导,给出雅可比时, ceres会直接使用该雅可比,不给出雅可比时, ceres就会自动去求导。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| class AnalyticCostFunction : public ceres::SizedCostFunction<1, 4> { public: AnalyticCostFunction(const double x, const double y) : _x(x), _y(y) {} virtual ~AnalyticCostFunction() {} virtual bool Evaluate(double const* const* parameters, double* residuals, double** jacobians) const { const double a = parameters[0][0]; const double b = parameters[0][1]; const double c = parameters[0][2]; const double d = parameters[0][3]; residuals[0] = double(_y) - (pow(a, double(_x)) + b * double(_x) * double(_x) + c * double(_x) + d); if (jacobians != NULL && jacobians[0] != NULL) { jacobians[0][0] = -1 * pow(a, double(_x - 1)) * double(_x); jacobians[0][1] = -1 * double(_x) * double(_x); jacobians[0][2] = -1 * double(_x); jacobians[0][3] = -1; } return true; }
private: double _x, _y; };
|
3.自动求导与解析求导的对比
自动求导实现方便,但效率会比解析求导低 (比较 A-LOAM 和 F-LOAM );
实际使用中,能够自动求导且效率没有形成障碍的,优先使用自动求导;
除这两种方法外,还有数值求导,相比于自动求导只是没有使用模板函数,SLAM问题中不常见,不过多介绍。
4.完整代码
ceres::Solve(options, &problem, &summary);有三个参数,option是求解器设置,包括方程的求解方式,最大迭代次数等设置;problem就是设置优化问题,添加残差块;summary是结果报告,输出最终优化次数,cost等信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
| #include<cmath> #include <iostream> #include <opencv2/core/core.hpp> #include <ceres/ceres.h> #include <chrono>
using namespace std;
struct MyAutoCostFunction { MyAutoCostFunction(double x, double y) : _x(x), _y(y) {}
template<typename T> bool operator()(const T *const abcd, T *residual) const { residual[0] = T(_y) - (pow(abcd[0] , T(_x)) + abcd[1] * T(_x) * T(_x) + abcd[2] * T(_x) + abcd[3]); return true; } const double _x, _y; };
class AnalyticCostFunction : public ceres::SizedCostFunction<1, 4> { public: AnalyticCostFunction(const double x, const double y) : _x(x), _y(y) {} virtual ~AnalyticCostFunction() {} virtual bool Evaluate(double const* const* parameters, double* residuals, double** jacobians) const { const double a = parameters[0][0]; const double b = parameters[0][1]; const double c = parameters[0][2]; const double d = parameters[0][3]; residuals[0] = double(_y) - (pow(a, double(_x)) + b * double(_x) * double(_x) + c * double(_x) + d); if (jacobians != NULL && jacobians[0] != NULL) { jacobians[0][0] = -1 * pow(a, double(_x - 1)) * double(_x); jacobians[0][1] = -1 * double(_x) * double(_x); jacobians[0][2] = -1 * double(_x); jacobians[0][3] = -1; } return true; }
private: double _x, _y; };
int main(int argc, char **argv) { double ar = 2.0, br = 2.0, cr = 1.0, dr = 2.0; double ae = 9.0, be = -3.0, ce = -1.0, de = 4.0; int N = 100; double w_sigma = 0.0; double inv_sigma = 1.0 / w_sigma; cv::RNG rng;
vector<double> x_data, y_data; for (int i = 0; i < N; i++) { double x = i / 100.0; x_data.push_back(x); y_data.push_back((pow(ar , x) + br * x * x + cr * x + dr) + rng.gaussian(w_sigma * w_sigma)); }
double abcd[4] = {ae, be, ce, de};
ceres::Problem problem; for (int i = 0; i < N; i++) { auto costFunction = new ceres::AutoDiffCostFunction<MyAutoCostFunction, 1, 4>(new MyAutoCostFunction(x_data[i], y_data[i]));
problem.AddResidualBlock( costFunction, nullptr, abcd );
}
ceres::Solver::Options options; options.linear_solver_type = ceres::DENSE_NORMAL_CHOLESKY; options.minimizer_progress_to_stdout = true; options.max_num_iterations = 100;
ceres::Solver::Summary summary; chrono::steady_clock::time_point t1 = chrono::steady_clock::now(); ceres::Solve(options, &problem, &summary); chrono::steady_clock::time_point t2 = chrono::steady_clock::now(); chrono::duration<double> time_used = chrono::duration_cast<chrono::duration<double>>(t2 - t1); cout << "solve time cost = " << time_used.count() << " seconds. " << endl;
cout << summary.BriefReport() << endl; cout << "estimated a,b,c,d = ";
for (auto a:abcd) cout << a << " "; cout << endl;
return 0; }
|
Cmake
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| cmake_minimum_required(VERSION 2.8) project(learnCeres)
set(CMAKE_BUILD_TYPE Release) set(CMAKE_CXX_FLAGS "-std=c++14 -O3")
# OpenCV find_package(OpenCV REQUIRED) include_directories(${OpenCV_INCLUDE_DIRS})
# Ceres find_package(Ceres REQUIRED) include_directories(${CERES_INCLUDE_DIRS})
add_executable(learnCeres learnCeres.cpp) target_link_libraries(learnCeres ${OpenCV_LIBS} ${CERES_LIBRARIES})
|