http://ceres-solver.org/nnls_tutorial.html
ba问题讲解
https://blog.csdn.net/wzheng92/article/details/79714857
空间中一个点在成像平面的坐标系中投影成一个像素。这个投影称为初次投影。如果我们有多个相机,可以根据空间中同一点在不同相机中的投影(即像素坐标)来反向确定这个点的空间位置。然后使这个逆向计算出的空间点,再次向各个相机投影,如此我们会再次得到对应的像素。这个投影称为重投影。因为逆向计算是有误差的,相机矩阵也不是完全精准的。所以这个重投影和初次投影的位置往往是有微小偏差的。最理想的参数应该使空间点的初次投影和重投影的偏差尽可能小。如果我们有很多这样的数据,我们就可以通过优化的方法确定参数。这就是光束法平差的基本原理。
https://blog.csdn.net/OptSolution/article/details/64442962
编写 Ceres 的主要原因之一是我们需要解决大规模的束调整问题[HartleyZisserman],[Triggs]。
给定一组测量的图像特征位置和对应关系,束平差的目标是找到最小化重投影误差的 3D 点位置和相机参数。此优化问题通常表述为非线性最小二乘问题,其中误差为平方L2观察到的特征位置与相应 3D 点在相机图像平面上的投影之间差异的范数。Ceres 广泛支持解决束调整问题。



Ceres对BA的介绍如下:
给定一系列测得的图像,包含特征点位置和对应关系。BA的目标就是,通过最小化重投影误差,确定三维空间点的位置和相机参数。这个优化问题通常被描述为非线性最小二乘法问题,要最小化的目标函数即为测得图像中特征点位置和相机成像平面上的对应的投影之间的差的平方。Ceres例程使用了BAL数据集 。
像往常一样,第一步是定义一个模板化的仿函数来计算重投影误差/残差。仿函数的结构类似于ExponentialResidual,因为有一个该对象的实例负责每个图像观察。
BAL 问题中的每个残差都取决于一个三维点和一个九参数相机。定义相机的九个参数是:三个用于作为罗德里格斯轴角矢量的旋转,三个用于平移,一个用于焦距,两个用于径向畸变。该相机型号的详细信息可以在Bundler 主页和BAL 主页找到。
struct SnavelyReprojectionError {
SnavelyReprojectionError(double observed_x, double observed_y)
: observed_x(observed_x), observed_y(observed_y) {}
template <typename T>
bool operator()(const T* const camera,
const T* const point,
T* residuals) const {
// camera[0,1,2] are the angle-axis rotation.
T p[3];
ceres::AngleAxisRotatePoint(camera, point, p);
// camera[3,4,5] are the translation.
p[0] += camera[3]; p[1] += camera[4]; p[2] += camera[5];
// Compute the center of distortion. The sign change comes from
// the camera model that Noah Snavely's Bundler assumes, whereby
// the camera coordinate system has a negative z axis.
T xp = - p[0] / p[2];
T yp = - p[1] / p[2];
// Apply second and fourth order radial distortion.
const T& l1 = camera[7];
const T& l2 = camera[8];
T r2 = xp*xp + yp*yp;
T distortion = 1.0 + r2 * (l1 + l2 * r2);
// Compute final projected point position.
const T& focal = camera[6];
T predicted_x = focal * distortion * xp;
T predicted_y = focal * distortion * yp;
// The error is the difference between the predicted and observed position.
residuals[0] = predicted_x - T(observed_x);
residuals[1] = predicted_y - T(observed_y);
return true;
}
// Factory to hide the construction of the CostFunction object from
// the client code.
static ceres::CostFunction* Create(const double observed_x,
const double observed_y) {
return (new ceres::AutoDiffCostFunction<SnavelyReprojectionError, 2, 9, 3>(
new SnavelyReprojectionError(observed_x, observed_y)));
}
double observed_x;
double observed_y;
};
请注意,与之前的示例不同,这是一个非常重要的函数,计算其解析雅可比行列式有点麻烦。自动微分使生活变得更加简单。AngleAxisRotatePoint()可以在 中找到用于操纵旋转的函数 和其他函数include/ceres/rotation.h。
给定这个仿函数,束调整问题可以构造如下:
ceres::Problem problem;
for (int i = 0; i < bal_problem.num_observations(); ++i) {
ceres::CostFunction* cost_function =
SnavelyReprojectionError::Create(
bal_problem.observations()[2 * i + 0],
bal_problem.observations()[2 * i + 1]);
problem.AddResidualBlock(cost_function,
nullptr /* squared loss */,
bal_problem.mutable_camera_for_observation(i),
bal_problem.mutable_point_for_observation(i));
}

有关更复杂的包调整示例,该示例演示了 Ceres 更高级功能的使用,包括其各种线性求解器、强大的损失函数和流形,请参见 examples/bundle_adjuster.cc
// A minimal, self-contained bundle adjuster using Ceres, that reads
// files from University of Washington' Bundle Adjustment in the Large dataset:
// http://grail.cs.washington.edu/projects/bal
//
// This does not use the best configuration for solving; see the more involved
// bundle_adjuster.cc file for details.
#include <cmath>
#include <cstdio>
#include <iostream>
#include "ceres/ceres.h"
#include "ceres/rotation.h"
// Read a Bundle Adjustment in the Large dataset.
class BALProblem {
public:
~BALProblem() {
delete[] point_index_;
delete[] camera_index_;
delete[] observations_;
delete[] parameters_;
}
int num_observations() const { return num_observations_; }
const double* observations() const { return observations_; }
double* mutable_cameras() { return parameters_; }// // 一维数组 相机总数*9+点数*3
double* mutable_points() { return parameters_ + 9 * num_cameras_; }
double* mutable_camera_for_observation(int i) {
return mutable_cameras() + camera_index_[i] * 9;
}
double* mutable_point_for_observation(int i) {
return mutable_points() + point_index_[i] * 3;
}
bool LoadFile(const char* filename) {
FILE* fptr = fopen(filename, "r");
if (fptr == nullptr) {
return false;
};
FscanfOrDie(fptr, "%d", &num_cameras_);
FscanfOrDie(fptr, "%d", &num_points_);
FscanfOrDie(fptr, "%d", &num_observations_);//num_observations_ 观测点组数目 2D-3D
point_index_ = new int[num_observations_]; //二维像素点索引 总数*1
camera_index_ = new int[num_observations_]; //相机索引 总数*1
observations_ = new double[2 * num_observations_];//二维像素点 总数*2
num_parameters_ = 9 * num_cameras_ + 3 * num_points_; // 相机总数*9+点数*3
parameters_ = new double[num_parameters_]; // 一维数组 相机总数*9+点数*3
for (int i = 0; i < num_observations_; ++i) {
FscanfOrDie(fptr, "%d", camera_index_ + i);
FscanfOrDie(fptr, "%d", point_index_ + i);
for (int j = 0; j < 2; ++j) {
FscanfOrDie(fptr, "%lf", observations_ + 2 * i + j);
}
}
for (int i = 0; i < num_parameters_; ++i) {
FscanfOrDie(fptr, "%lf", parameters_ + i);
}
return true;
}
private:
template <typename T>
void FscanfOrDie(FILE* fptr, const char* format, T* value) {
int num_scanned = fscanf(fptr, format, value);
if (num_scanned != 1) {
LOG(FATAL) << "Invalid UW data file.";
}
}
int num_cameras_;
int num_points_;
int num_observations_;
int num_parameters_;
int* point_index_;
int* camera_index_;
double* observations_;
double* parameters_;
};
// Templated pinhole camera model for used with Ceres. The camera is
// parameterized using 9 parameters: 3 for rotation, 3 for translation, 1 for
// focal length and 2 for radial distortion. The principal point is not modeled
// (i.e. it is assumed be located at the image center).
/*
定义相机的九个参数是:三个用于作为罗德里格斯轴角矢量的旋转,三个用于平移,一个用于焦距,两个用于径向畸变。
给定一组测量的图像特征位置(u,v)和对应关系 旋转R(a1,a2,a3) 平移t(t1,t2,t3)
束平差的目标是找到最小化重投影误差的 3D 点位置(x,y,z)和相机参数 焦距f 径向畸变p1 p2
观测值像素坐标(u,v) 3D点位置(x,y,z)
预测值(u',v')= *逆深度-1/z*内参K(f,p1,p2)*{旋转R(a1,a2,a3)*3D点位置(x,y,z)+平移t(t1,t2,t3)}
残差 = 观测值(u,v)-预测值(u',v')
求解 旋转R(a1,a2,a3) 平移t(t1,t2,t3) 相机参数 焦距f 径向畸变p1 p2
*/
struct SnavelyReprojectionError {
// 每次输入的观测数据 空间点在成像平面上的位置,观测值像素坐标(u,v) observed_x observed_y
SnavelyReprojectionError(double observed_x, double observed_y)
: observed_x(observed_x), observed_y(observed_y) {}
template <typename T>
// 等待求解优化的数据
bool operator()(const T* const camera, // 等待求解优化的camera数据
const T* const point, // 等待求解优化的point数据
T* residuals) const { // 残差
// camera[0,1,2] are the angle-axis rotation. 旋转R(a1,a2,a3)
T p[3];
// 完整目标 预测值(u',v')= *逆深度-1/z*内参K(f,p1,p2)*{旋转R(a1,a2,a3)*3D点位置(x,y,z)+平移t(t1,t2,t3)}
// p(x,y,z)=内参K(f,p1,p2)*{旋转R(a1,a2,a3)*3D点位置(x,y,z)+平移t(t1,t2,t3)}
ceres::AngleAxisRotatePoint(camera, point, p);
// camera[3,4,5] are the translation. 平移t(t1,t2,t3)
p[0] += camera[3];
p[1] += camera[4];
p[2] += camera[5];
// Compute the center of distortion. The sign change comes from
// the camera model that Noah Snavely's Bundler assumes, whereby
// the camera coordinate system has a negative z axis.
T xp = -p[0] / p[2];
T yp = -p[1] / p[2];
// Apply second and fourth order radial distortion.
const T& l1 = camera[7];
const T& l2 = camera[8];
T r2 = xp * xp + yp * yp;
T distortion = 1.0 + r2 * (l1 + l2 * r2);
// Compute final projected point position.
const T& focal = camera[6];
T predicted_x = focal * distortion * xp;
T predicted_y = focal * distortion * yp;
// The error is the difference between the predicted and observed position.
residuals[0] = predicted_x - observed_x; //残差u = 预测值x-观测值x
residuals[1] = predicted_y - observed_y; //残差v = 预测值y-观测值y
return true;
}
// Factory to hide the construction of the CostFunction object from
// the client code.
static ceres::CostFunction* Create(const double observed_x,
const double observed_y) {
return (new ceres::AutoDiffCostFunction<SnavelyReprojectionError, 2, 9, 3>(
new SnavelyReprojectionError(observed_x, observed_y)));
}
double observed_x;
double observed_y;
};
int main(int argc, char** argv) {
google::InitGoogleLogging(argv[0]);
if (argc != 2) {
std::cerr << "usage: simple_bundle_adjuster <bal_problem>\n";
return 1;
}
BALProblem bal_problem;
//加载数据
if (!bal_problem.LoadFile(argv[1])) {
std::cerr << "ERROR: unable to open file " << argv[1] << "\n";
return 1;
}
const double* observations = bal_problem.observations();
// Create residuals for each observation in the bundle adjustment problem. The
// parameters for cameras and points are added automatically.
ceres::Problem problem;
for (int i = 0; i < bal_problem.num_observations(); ++i) {//遍历观测所有数据
// Each Residual block takes a point and a camera as input and outputs a 2
// dimensional residual. Internally, the cost function stores the observed
// image location and compares the reprojection against the observation.\
// 描述最小二乘的基本形式即代价函数
ceres::CostFunction* cost_function = SnavelyReprojectionError::Create(
observations[2 * i + 0], observations[2 * i + 1]); // 加入观测值像素坐标(u,v)
// 向问题中添加误差项
problem.AddResidualBlock(cost_function, // 残差定义和雅克比信息
nullptr /* // 核函数,这里不使用,为空 */,
bal_problem.mutable_camera_for_observation(i), // 输出要优化的camera数据 n*9*3
bal_problem.mutable_point_for_observation(i)); // 输出要优化的point数据
}
// Make Ceres automatically detect the bundle structure. Note that the
// standard solver, SPARSE_NORMAL_CHOLESKY, also works fine but it is slower
// for standard bundle adjustment problems.
// 配置求解器
ceres::Solver::Options options;
/*
因为这是一个大规模稀疏矩阵,所以可以将Solver::Options::linear_solver_type设置成SPARSE_NORMAL_CHOLESKY,之后调用Solve()。
另外Ceres还提供了三个专门的解算器供使用。这里的样例代码用了其中最简单的一个解算器,即DENSE_SCHUR
*/
options.linear_solver_type = ceres::DENSE_SCHUR;
//options.max_num_iterations = 4;//迭代次数
options.minimizer_progress_to_stdout = true;//输出到cout 为真时 内部错误输出到cout,我们可以看到错误的地方,默认情况下,会输出到日志文件中保存
ceres::Solver::Summary summary;// 优化信息
ceres::Solve(options, &problem, &summary);// 开始优化
std::cout << summary.FullReport() << "\n";
return 0;
}
Powered by Gitiles| Privacy
浙公网安备 33010602011771号