优化库之-----Ceres库学习笔记(一)

Ceres解决的是非线性优化问题,给定初值,得出最优解, 主要在SLAM中使用。

一般的可求解形如下面的最小二乘优化问题:

 例如求解最小值,本文后续以求解该例为主线。

Ceres的使用流程是:

建立仿函数,定义变量并初始化,建立优化问题,建立CostFunction,求解.

1. 建立仿函数

先在头文件书写一个类,定义残差的表达方式。这个类的书写方式采用仿函数的方法,仿函数(Functor)也叫函数对象,顾名思义就是让一个类用的时候像函数一样方便。

仿函数的简单示例code-A

class A{
public:
    bool operator () (int num){
    if(num>0){cout<<"positive!"<<endl;return 1;}
    else {cout<<"negative!"<<endl;return 0;}
    }
};

int main(int argc, char**argv){

A a;
a(-10);//是不是感觉很方便

} 

 更进一步,在Ceres中的用法code-B

struct CostFunctor {
   template <typename T>
   bool operator()(const T* const x, T* residual) const {
     residual[0] = 10.0 - x[0];
     return true;
   }
};

在Vins_fusion中是这样用的code-C

 struct TError
 {
     TError(double t_x, double t_y, double t_z, double var)   //TError类的构造函数,包括x,y,z三维坐标,与位置精度var;
                   :t_x(t_x), t_y(t_y), t_z(t_z), var(var){}
 
     template <typename T>
     bool operator()(const T* tj, T* residuals) const         //模板仿函数类定义,与code-A中的仿函数一样,区别是此处函数参数为模板类T.
     {
         residuals[0] = (tj[0] - T(t_x)) / T(var);
         residuals[1] = (tj[1] - T(t_y)) / T(var);
         residuals[2] = (tj[2] - T(t_z)) / T(var);
 
         return true;
     }
 ....... };

 2. 定义优化变量并初始化

  double initial_x = -255.0; // The variable to solve for with its initial value.
  double x = initial_x;

3. 建立优化问题

 Problem problem;  // Build the problem.

4. 建立CostFunction

  CostFunction* cost_function = new AutoDiffCostFunction<CostFunctor, 1, 1>(new CostFunctor);   //使用AutoDiffCostFunction,另外可用的还有NumericDiffCostFunction,Analytic Derivatives.官方推荐使用AutoDiffCostFunction
  problemA.AddResidualBlock(cost_function, nullptr, &x);                                        //<CostFunctor, 1, 1>两个参数的含义,输出维度与输入维度,输出维度是残差residual维度,输入维度是优化变量x维度.

5. 求解

  Solver::Options options;   //求解器参数设置
  options.linear_solver_type = ceres::DENSE_QR;    //线性求解器类型设置,Ceres的线性求解器类型有,DENSE_QR,DENSE_NORMAL_CHOLESKY,SPARSE_NORMAL_CHOLESK,CGNR,DENSE_SCHUR,SPARSE_SCHU,ITERATIVE_SCHUR
  options.minimizer_progress_to_stdout = true;     //将求解迭代过程用cout打印出来
  Solver::Summary summary;
  Solve(options, &problem, &summary);              //标准的求解语句
  std::cout << summary.BriefReport() << "\n";      //打印求解结果

 接下来用两个案例说明完整过程

案例1:

案例2:

 vins_fusion的位姿优化

案例1:

struct CostFunctor {
   template <typename T>
   bool operator()(const T* const x, T* residual) const {
     residual[0] = 10.0 - x[0];
     return true;
   }
};

int main(int argc, char** argv) {
  google::InitGoogleLogging(argv[0]);

  // The variable to solve for with its initial value.
  double initial_x = 5.0;
  double x = initial_x;

  // Build the problem.
  Problem problem;

  // Set up the only cost function (also known as residual). This uses
  // auto-differentiation to obtain the derivative (jacobian).
  CostFunction* cost_function =
      new AutoDiffCostFunction<CostFunctor, 1, 1>(new CostFunctor);
  problem.AddResidualBlock(cost_function, nullptr, &x);

  // Run the solver!
  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 << "x : " << initial_x
            << " -> " << x << "\n";
  return 0;
}
案例2:
            newGPS = false;
            printf("global optimization\n");
            TicToc globalOptimizationTime;
            ceres::Problem problem;
            ceres::Solver::Options options;
            options.linear_solver_type = ceres::SPARSE_NORMAL_CHOLESKY;
            //options.minimizer_progress_to_stdout = true;
            //options.max_solver_time_in_seconds = SOLVER_TIME * 3;
            options.max_num_iterations = 5;
            ceres::Solver::Summary summary;
            ceres::LossFunction *loss_function;
            loss_function = new ceres::HuberLoss(1.0);
            ceres::LocalParameterization* local_parameterization = new ceres::QuaternionParameterization();
            //add param
            mPoseMap.lock();
            int length = localPoseMap.size();
            // w^t_i   w^q_i
            double t_array[length][3];
            double q_array[length][4];
            map<double, vector<double>>::iterator iter;
            iter = globalPoseMap.begin();
            for (int i = 0; i < length; i++, iter++)
            {
                t_array[i][0] = iter->second[0];
                t_array[i][1] = iter->second[1];
                t_array[i][2] = iter->second[2];
                q_array[i][0] = iter->second[3];
                q_array[i][1] = iter->second[4];
                q_array[i][2] = iter->second[5];
                q_array[i][3] = iter->second[6];
                problem.AddParameterBlock(q_array[i], 4, local_parameterization);
                problem.AddParameterBlock(t_array[i], 3);
            }
            map<double, vector<double>>::iterator iterVIO, iterVIONext, iterGPS;
            int i = 0;
            for (iterVIO = localPoseMap.begin(); iterVIO != localPoseMap.end(); iterVIO++, i++)
            {
                //vio factor
                iterVIONext = iterVIO;
                iterVIONext++;
                if(iterVIONext != localPoseMap.end())
                {
                    Eigen::Matrix4d wTi = Eigen::Matrix4d::Identity();
                    Eigen::Matrix4d wTj = Eigen::Matrix4d::Identity();
                    wTi.block<3, 3>(0, 0) = Eigen::Quaterniond(iterVIO->second[3], iterVIO->second[4], 
                                                               iterVIO->second[5], iterVIO->second[6]).toRotationMatrix();
                    wTi.block<3, 1>(0, 3) = Eigen::Vector3d(iterVIO->second[0], iterVIO->second[1], iterVIO->second[2]);
                    wTj.block<3, 3>(0, 0) = Eigen::Quaterniond(iterVIONext->second[3], iterVIONext->second[4], 
                                                               iterVIONext->second[5], iterVIONext->second[6]).toRotationMatrix();
                    wTj.block<3, 1>(0, 3) = Eigen::Vector3d(iterVIONext->second[0], iterVIONext->second[1], iterVIONext->second[2]);
                    Eigen::Matrix4d iTj = wTi.inverse() * wTj;
                    Eigen::Quaterniond iQj;
                    iQj = iTj.block<3, 3>(0, 0);
                    Eigen::Vector3d iPj = iTj.block<3, 1>(0, 3);

                    ceres::CostFunction* vio_function = RelativeRTError::Create(iPj.x(), iPj.y(), iPj.z(),
                                                                                iQj.w(), iQj.x(), iQj.y(), iQj.z(),
                                                                                0.1, 0.01);
                    problem.AddResidualBlock(vio_function, NULL, q_array[i], t_array[i], q_array[i+1], t_array[i+1]);
                }

 

                //gps factor
                double t = iterVIO->first;
                iterGPS = GPSPositionMap.find(t);
                if (iterGPS != GPSPositionMap.end())
                {
                    ceres::CostFunction* gps_function = TError::Create(iterGPS->second[0], iterGPS->second[1], 
                                                                       iterGPS->second[2], iterGPS->second[3]);
                    //printf("inverse weight %f \n", iterGPS->second[3]);
                    problem.AddResidualBlock(gps_function, loss_function, t_array[i]);
                }

            }
            //mPoseMap.unlock();
            ceres::Solve(options, &problem, &summary);
std::cout << summary.BriefReport() << "\n";

            // update global pose
            //mPoseMap.lock();
            iter = globalPoseMap.begin();
            for (int i = 0; i < length; i++, iter++)
            {
                vector<double> globalPose{t_array[i][0], t_array[i][1], t_array[i][2],
                                          q_array[i][0], q_array[i][1], q_array[i][2], q_array[i][3]};
                iter->second = globalPose;
                if(i == length - 1)
                {
                    Eigen::Matrix4d WVIO_T_body = Eigen::Matrix4d::Identity(); 
                    Eigen::Matrix4d WGPS_T_body = Eigen::Matrix4d::Identity();
                    double t = iter->first;
                    WVIO_T_body.block<3, 3>(0, 0) = Eigen::Quaterniond(localPoseMap[t][3], localPoseMap[t][4], 
                                                                       localPoseMap[t][5], localPoseMap[t][6]).toRotationMatrix();
                    WVIO_T_body.block<3, 1>(0, 3) = Eigen::Vector3d(localPoseMap[t][0], localPoseMap[t][1], localPoseMap[t][2]);
                    WGPS_T_body.block<3, 3>(0, 0) = Eigen::Quaterniond(globalPose[3], globalPose[4], 
                                                                        globalPose[5], globalPose[6]).toRotationMatrix();
                    WGPS_T_body.block<3, 1>(0, 3) = Eigen::Vector3d(globalPose[0], globalPose[1], globalPose[2]);
                    WGPS_T_WVIO = WGPS_T_body * WVIO_T_body.inverse();
                }
            }
            updateGlobalPath();
            //printf("global time %f \n", globalOptimizationTime.toc());
            mPoseMap.unlock();

另外在SLAM中BA问题是一个基础问题,BA全拼是Bundle Adjustment ,译为光束法平差,BA的本质是一个优化模型,目的是最小化重投影误差。经典文献 Bundle Adjustment——A  Mordern  Synthsis

 

posted @ 2023-06-21 23:12  Derek_dhb  阅读(1289)  评论(0)    收藏  举报