最近有一个项目中需要用到曲线拟合,了解到Ceres的曲线拟合,看效果还不错,所以开始搭建环境。Ceres是google出品的一款基于C++的开源非线性优化库。依赖很多其他库,不过依赖的也都是开源的。
- 下载依赖库
Eigen - 官网 Eigen
glog - github
gflags - github
Ceres - 官网有最新版本及最新的稳定版,目前最新版是2.1.0。我下载的是1.14.0.这是一个坑,后面会讲到。
- 配置过程
1.Eigen
Eigen是由.h文件构成的一款支持矩阵运算的数学计算库,网上查询教程说不需要额外编译,仅解压缩后得到源码文件即可。这是我遇到的第一个坑,我看大家都建议下最新版,于是我官网下载的是3.3.9,可是这是需要编译的。
如果不编译,则编译ceres时会报错:
Could not find a package configuration file provided by "Eigen3" (requested version 3)?
CMake Error at CMakeLists.txt:415 (find_package): Could not find a package configuration file provided by "Eigen3" (requested version 3) with any of the following names: Eigen3Config.cmake eigen3 config.
提示找不到Eigen3,编译后消失。
2.glog
解压得到源码文件glog-master后,在同级目录下建立glog_build文件夹,用来放置编译后的文件。打开CMAKE客户端,对应选择源码文件路径和编译文件路径,如下图所示。
注意:cmake要是版本3.11以上,我重新下载了最新版本。
然后就可以通过Configure按钮选择配置属性,选择VS2015的Win32,如图。
之后点击Finish就可以触发Cmake进行配置,配置表单会有一些标红的项目,此时需要注意一点:BUILD_SHARED_LIBS选项需要勾选,用来生成dll文件,如图。
若没有这个项目,可以通过Add Entry来手动添加。
之后再次触发Configure,直到没有红色错误为止;再触发Generate按钮,如下提示则表示CMake顺利结束。
然后在glog_build文件夹下打开glog.sln文件,分别在Release x64和Debug x64模式下重新生成ALL_BUILD。
3.gflags
与glog一致,建立gflags_build文件夹,存放编译后的文件。Cmake的配置方式也与之前相同。再次提示一点BUILD_SHARED_LIBS选项需要勾选,用来生成dll文件。打开生成的gflags.sln文件,分别在Release x64和Debug x64模式下重新生成ALL_BUILD。
编译这个遇到一个报错:
Could not find a package configuration file provided by "gflags" (requested
version 2.2.0) with any of the following names:
提示需要版本2.2.0.于是我重新下载版本2.2.0
GitHub - gflags/gflags at v2.2.0
这个库不要求cmake的版本了,我用3.24编译报错了,切换回3.13又正常了,在用3.24编译也正常了。可能是顺序的原因。
4.Ceres
与前面两个配置方案一致,建立ceres_build文件夹,存放编译后的文件。Cmake的时候也要注意,BUILD_SHARED_LIBS选项需要勾选,用来生成dll文件。打开生成的Ceres.sln文件,分别在Release x64和Debug x64模式下重新生成ALL_BUILD。
遇到这个错:
Failed to find glog - Could not find glog include directory, set GLOG_INCLUDE_DIR to directory conta
编译到最后,生成了sln文件,可是点击ALL_BUILD重新生成就报错。我网上百度,没什么解决方案,我看官网文档说是从2.0开始支持了c++11-17,我觉得可能是版本问题,于是下载了2.0.0.刚开始是最新版本2.1.0,重新编译还是不行,到最后重新生成报错。我又下载了1.14.0.我在官网找不到历史版本的下载,通过网上百度终于找到一个下载地址:
下载ceres-solver版本
直接复制ceres-solver.org/ceres-solver-1.14.0.tar.gz在域名处,就可以下载。应该也是官网,只是我没找到入口。
最后完成后的目录:
- 包含文件和链接库整理
在每一个库中ALL_BUILD后面点击INSTALL生成后,会在C盘建立一个文件夹
在f:/ceres/新建install文件夹,把这四个文件夹拷贝到Install 文件夹内方便后面VS配置使用。需要说明的是,这一步不是必须的,只是把分散在各处的相关文件聚集在同一个公共位置而已。
6.VS配置
新建一个VS工程,属性配置如下(Debug与Release类似)。
示例代码:
在网上找了一个曲线拟合的例子:
#include "stdafx.h"
#include <iostream>
#include <opencv2/core/core.hpp>
#include "glog/logging.h"
#include <ceres/ceres.h>
#include <chrono>
#include <fstream>
using namespace std;
// 代价函数的计算模型
struct CURVE_FITTING_COST
{
CURVE_FITTING_COST(double x, double y) : _x(x), _y(y) {}
// 残差的计算
template <typename T>
bool operator() (
const T* const abc, // 模型参数,有3维
T* residual) const // 残差
{
residual[0] = T(_y) - ceres::exp(abc[0] * T(_x) *T(_x) + abc[1] * T(_x) + abc[2]); // y-exp(ax^2+bx+c)
return true;
}
const double _x, _y; // x,y数据
};
int main(int argc, char** argv)
{
double a = 1.0, b = 2.0, c = 1.0; // 真实参数值
int N = 100; // 数据点
double w_sigma = 1.0; // 噪声Sigma值
cv::RNG rng; // OpenCV随机数产生器
double abc[3] = { 0,0,0 }; // abc参数的估计值
stringstream ss;
vector<double> x_data, y_data; // 数据
cout << "generating data: " << endl;
for (int i = 0; i < N; i++)
{
double x = i / 100.0;
x_data.push_back(x);
y_data.push_back(
exp(a*x*x + b*x + c) + rng.gaussian(w_sigma)
);
cout << x_data[i] << " " << y_data[i] << endl;
ss << x_data[i] << " " << y_data[i] << endl;
}
ofstream file("points.txt");
file << ss.str();
// 构建最小二乘问题
ceres::Problem problem;
for (int i = 0; i < N; i++)
{
problem.AddResidualBlock( // 向问题中添加误差项
// 使用自动求导,模板参数:误差类型,输出维度,输入维度,维数要与前面struct中一致
new ceres::AutoDiffCostFunction<CURVE_FITTING_COST, 1, 3>(
new CURVE_FITTING_COST(x_data[i], y_data[i])
),
nullptr, // 核函数,这里不使用,为空
abc // 待估计参数
);
}
// 配置求解器
ceres::Solver::Options options; // 这里有很多配置项可以填
options.linear_solver_type = ceres::DENSE_QR; // 增量方程如何求解
options.minimizer_progress_to_stdout = true; // 输出到cout
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 = ";
for (auto a : abc) cout << a << " ";
cout << endl;
return 0;
}
输出的 y2 = np.exp(0.892*x*x+2.17039*x+0.944142);
在python显示了一下:
另:ceres生成遇到的问题:
- std::numeric_limits<double>::max()“max”宏的实参不足
error C2589: “(” : “::”右边的非法标记
解决方案:需要把max用括号括起来避免和windows定义的宏混淆
(std::numeric_limits::max)()
- error C2589: “(”:“::”右边的非法标记
x_plus_delta[i] = (std::max)(x_plus_delta[i], lower_bounds_[i]);
- ERROR macro is defined. Define GLOG_NO_ABBREVIATED_SEVERITIES before including logging.h. See the document for detail.
解决方法:
在工程加上预编译宏GLOG_NO_ABBREVIATED_SEVERITIES
到目前为止,我的ceres的ALL_BUILD依然有两个错误没有解决,不过都是example里面的项目,不影响我使用,在这记录一下就行了。
严重性 | 代码 | 说明 | 项目 | 文件 | 行 |
错误 | C2719 | 'yaw_radians': formal parameter with requested alignment of 8 won't be aligned | pose_graph_2d | f:\ceres\ceres-solver-1.14.0\examples\slam\pose_graph_2d\pose_graph_2d_error_term.h | 42 |
错误 | C1128 | 节数超过对象文件格式限制: 请使用 /bigobj 进行编译 | nist | c:\program files (x86)\eigen3\include\eigen3\eigen\src\core\coreevaluators.h | 536 |