ceres学习总结

ceres是用来求解优化问题的工具库。

使用时至少需要构建目标函数,优化变量。

如果自动求导困难,则需要给出雅克比矩阵。(一般是采用了第三方的库,比如eigen和sophus的一些运算)

如果优化变量不对加法封闭,则需要给出优化变量的更新方法。

==============================================================================

对于简单优化问题总体流程:(可以采用自动求导)

1、构建代价函数模型,描述如何计算残差

2、构建问题(要给一个初值)

3、配置求解器

4、结果输出

以两张图片的pnp优化为例:

//ceres pnp
#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/features2d/features2d.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/calib3d/calib3d.hpp>
#include <Eigen/Core>
#include <ceres/ceres.h>
#include <ceres/rotation.h>
#include <chrono>
cv::Mat K = (cv::Mat_<double>(3, 3) << 520.9, 0, 325.1, 0, 521.0, 249.7, 0, 0, 1);
cv::Point2d pixel2cam ( const cv::Point2d& p, const cv::Mat& K );
void find_feature_matches (
    const cv::Mat& img_1, const cv::Mat& img_2,
    std::vector<cv::KeyPoint>& keypoints_1,
    std::vector<cv::KeyPoint>& keypoints_2,
    std::vector<cv::DMatch>& matches );

struct cost_function  
{
    cost_function(cv::Point3f p1,cv::Point2f p2):pworld(p1),pimg(p2) {}
    template <typename T>
    bool operator()(const T* const ceres_r, const T* const ceres_t, T* residual) const
    { 

        T p_world[3];
        T p_cam[3];
        p_world[0] = T(pworld.x);
        p_world[1] = T(pworld.y);
        p_world[2] = T(pworld.z);
        ceres::AngleAxisRotatePoint(ceres_r,p_world,p_cam);
        p_cam[0] = p_cam[0] + ceres_t[0];
        p_cam[1] = p_cam[1] + ceres_t[1];
        p_cam[2] = p_cam[2] + ceres_t[2];
        // std::cout<< p_cam[0] << p_cam[1] << p_cam[2] <<std::endl;
        const T x = p_cam[0] / p_cam[2];
        const T y = p_cam[1] / p_cam[2];
        const T u = x * T(K.at<double>(0, 0)) + T(K.at<double>(0, 2));
        const T v = y * T(K.at<double>(1, 1)) + T(K.at<double>(1, 2));
        T u1 = T(pimg.x);
        T v1 = T(pimg.y);
        residual[0] = u - u1;
        residual[1] = v - v1;
        return true;
    }
private:
    cv::Point3f pworld;
    cv::Point2f pimg;
};


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

    //-- 读取图像
    cv::Mat img_1 = cv::imread("/home/smz/learning_ref/slambook2-master/ch7/1.png", CV_LOAD_IMAGE_COLOR);
    cv::Mat img_2 = cv::imread("/home/smz/learning_ref/slambook2-master/ch7/2.png", CV_LOAD_IMAGE_COLOR);
    assert(img_1.data && img_2.data && "Can not load images!");

    std::vector<cv::KeyPoint> keypoints_1, keypoints_2;
    std::vector<cv::DMatch> matches;
    find_feature_matches(img_1, img_2, keypoints_1, keypoints_2, matches);
    std::cout << "一共找到了" << matches.size() << "组匹配点" << std::endl;

    // 建立3D点
    cv::Mat d1 = cv::imread("/home/smz/learning_ref/slambook2-master/ch7/1_depth.png", CV_LOAD_IMAGE_UNCHANGED);       // 深度图为16位无符号数,单通道图像
    std::vector<cv::Point3f> pts_3d;
    std::vector<cv::Point2f> pts_2d;
    for (cv::DMatch m:matches) {
        ushort d = d1.ptr<unsigned short>(int(keypoints_1[m.queryIdx].pt.y))[int(keypoints_1[m.queryIdx].pt.x)];
        if (d == 0)   // bad depth
            continue;
        float dd = d / 5000.0;
        cv::Point2d p1 = pixel2cam(keypoints_1[m.queryIdx].pt, K);
        pts_3d.push_back(cv::Point3f(p1.x * dd, p1.y * dd, dd));
        pts_2d.push_back(keypoints_2[m.trainIdx].pt);
    }

    std::cout << "3d-2d pairs: " << pts_3d.size() << std::endl;

    std::chrono::steady_clock::time_point t1 = std::chrono::steady_clock::now();
    cv::Mat r, t;
    solvePnP(pts_3d, pts_2d, K, cv::Mat(), r, t, false); // 调用OpenCV 的 PnP 求解,可选择EPNP,DLS等方法
    cv::Mat R;
    cv::Rodrigues(r, R); // r为旋转向量形式,用Rodrigues公式转换为矩阵
    std::chrono::steady_clock::time_point t2 = std::chrono::steady_clock::now();
    std::chrono::duration<double> time_used = std::chrono::duration_cast<std::chrono::duration<double>>(t2 - t1);
    std::cout << "solve pnp in opencv cost time: " << time_used.count() << " seconds." << std::endl;

    std::cout << "R=" << std::endl << R << std::endl;
    std::cout << "t=" << std::endl << t << std::endl;
    
    double ceres_rot[3],ceres_tran[3],ceres_rho[6];
    ceres_rot[0] = 0;
    ceres_rot[1] = 1;
    ceres_rot[2] = 2;
    ceres_tran[0] = t.at<double>(0,0)+0.1;
    ceres_tran[1] = t.at<double>(1,0)+0.1;
    ceres_tran[2] = t.at<double>(2,0)+0.1;
    ceres::Problem problem;
    for(int i = 0; i  < pts_3d.size();i++)
    {
        problem.AddResidualBlock(new ceres::AutoDiffCostFunction<cost_function,2,3,3>(new cost_function(pts_3d[i],pts_2d[i])),nullptr,ceres_rot,ceres_tran);
    }

    ceres::Solver::Options option;
    option.linear_solver_type = ceres::DENSE_SCHUR;
    option.minimizer_progress_to_stdout=true;
    ceres::Solver::Summary summary;
    t1 = std::chrono::steady_clock::now();
    ceres::Solve(option,&problem,&summary);
    t2 = std::chrono::steady_clock::now();
    time_used = std::chrono::duration_cast<std::chrono::duration<double>>(t2 - t1);
    std::cout << summary.BriefReport() << std::endl;
    std::cout << "solve pnp by ceres cost time: " << time_used.count() << " seconds." << std::endl;
    cv::Mat cam_3d = ( cv::Mat_<double> ( 3,1 )<<ceres_rot[0],ceres_rot[1],ceres_rot[2]);
    cv::Mat cam_9d;
    cv::Rodrigues (cam_3d, cam_9d); 
    std::cout << "cam_9d:" << std::endl << cam_9d << std::endl;

    std::cout << "cam_t:" << ceres_tran[0] << "  " << ceres_tran[1] << "  " << ceres_tran[2] << std::endl;

    return 0;
}

void find_feature_matches(const cv::Mat &img_1, const cv::Mat &img_2,
                          std::vector<cv::KeyPoint> &keypoints_1,
                          std::vector<cv::KeyPoint> &keypoints_2,
                          std::vector<cv::DMatch> &matches) {
    //-- 初始化
    cv::Mat descriptors_1, descriptors_2;
    // used in OpenCV3
    cv::Ptr<cv::FeatureDetector> detector = cv::ORB::create();
    cv::Ptr<cv::DescriptorExtractor> descriptor = cv::ORB::create();
    cv::Ptr<cv::DescriptorMatcher> matcher = cv::DescriptorMatcher::create("BruteForce-Hamming");
    //-- 第一步:检测 Oriented FAST 角点位置
    detector->detect(img_1, keypoints_1);
    detector->detect(img_2, keypoints_2);

    //-- 第二步:根据角点位置计算 BRIEF 描述子
    descriptor->compute(img_1, keypoints_1, descriptors_1);
    descriptor->compute(img_2, keypoints_2, descriptors_2);

    //-- 第三步:对两幅图像中的BRIEF描述子进行匹配,使用 Hamming 距离
    std::vector<cv::DMatch> match;
    // BFMatcher matcher ( NORM_HAMMING );
    matcher->match(descriptors_1, descriptors_2, match);

    //-- 第四步:匹配点对筛选
    double min_dist = 10000, max_dist = 0;

    //找出所有匹配之间的最小距离和最大距离, 即是最相似的和最不相似的两组点之间的距离
    for (int i = 0; i < descriptors_1.rows; i++) {
        double dist = match[i].distance;
        if (dist < min_dist) min_dist = dist;
        if (dist > max_dist) max_dist = dist;
    }

    printf("-- Max dist : %f \n", max_dist);
    printf("-- Min dist : %f \n", min_dist);

    //当描述子之间的距离大于两倍的最小距离时,即认为匹配有误.但有时候最小距离会非常小,设置一个经验值30作为下限.
    for (int i = 0; i < descriptors_1.rows; i++) {
        if (match[i].distance <= cv::max(2 * min_dist, 30.0)) {
        matches.push_back(match[i]);
        }
    }
}

cv::Point2d pixel2cam(const cv::Point2d &p, const cv::Mat &K) {
  return cv::Point2d
    (
      (p.x - K.at<double>(0, 2)) / K.at<double>(0, 0),
      (p.y - K.at<double>(1, 2)) / K.at<double>(1, 1)
    );
}


基本上可以简述为:


struct cost_function  //构建代价函数
{
    cost_function(cv::Point3f p1,cv::Point2f p2):pworld(p1),pimg(p2) {}//将非优化变量输入
    template <typename T> //定义模板函数
    bool operator()(const T* const ceres_r, const T* const ceres_t, T* residual) const 
    { 
        //给出非优化变量和优化变量应该如何计算得到最后的结果(残差),其中优化变量应该是以数组形式输入,残差也需要表示为数组格式
        T p_world[3];
        T p_cam[3];
        p_world[0] = T(pworld.x);
        p_world[1] = T(pworld.y);
        p_world[2] = T(pworld.z);
        ceres::AngleAxisRotatePoint(ceres_r,p_world,p_cam);
        p_cam[0] = p_cam[0] + ceres_t[0];
        p_cam[1] = p_cam[1] + ceres_t[1];
        p_cam[2] = p_cam[2] + ceres_t[2];
        // std::cout<< p_cam[0] << p_cam[1] << p_cam[2] <<std::endl;
        const T x = p_cam[0] / p_cam[2];
        const T y = p_cam[1] / p_cam[2];
        const T u = x * T(K.at<double>(0, 0)) + T(K.at<double>(0, 2));
        const T v = y * T(K.at<double>(1, 1)) + T(K.at<double>(1, 2));
        T u1 = T(pimg.x);
        T v1 = T(pimg.y);
        residual[0] = u - u1;
        residual[1] = v - v1;
        return true;
    }
private:
    cv::Point3f pworld;
    cv::Point2f pimg;
};
    ceres::Problem problem; //构建优化问题
    for(int i = 0; i  < pts_3d.size();i++)
    {
        problem.AddResidualBlock(new ceres::AutoDiffCostFunction<cost_function,2,3,3>(new cost_function(pts_3d[i],pts_2d[i])),nullptr,ceres_rot,ceres_tran);//给出每个问题的误差
    }

==============================================================================

复杂一点的需要给参数更新方式和雅克比矩阵

//  利用ceres实现位姿图优化,位姿图的输入是一个球,数据在文件sphere.g2o中 数据结构如下
/*  sphere.g2o  结构
数据:
VERTEX_SE3:QUAT 2499 1.0318 85.323 -76.0615 0.779315 0.0718127 -0.620437 -0.0506864 
EDGE_SE3:QUAT 0 1 -0.0187953 0.0328449 -0.125146 0.0634648 -0.000250128 0.00237634 0.997981 10000 0 0 0 0 0 10000 0 0 0 0 10000 0 0 0 40000 0 0 40000 0 40000 
顶点:
数据中以VERTEX_SE3:QUAT开头的为顶点,结构为VERTEX_SE3:QUAT 节点id 平移 四元数
边:
以EDGE_SE3:QUAT 开头的为边,结构为 EDGE_SE3:QUAT 节点id1 节点id2 平移 四元数 信息矩阵上三角(6*6的信息矩阵,上三角为6*6/2+6/2=21)
*/
#include <iostream>
#include <fstream>

#include <Eigen/Core>

#include <sophus/so3.hpp>
#include <sophus/se3.hpp>
#include <ceres/ceres.h>

#include <pcl/point_types.h> 
#include <pcl/visualization/pcl_visualizer.h>
#include <pcl/visualization/cloud_viewer.h>  

typedef Eigen::Matrix<double,6,6> Matrix6d;
typedef Eigen::Matrix<double,6,1> Vector6d;

class mypoint 
{
public: 
    mypoint(int _id1, double x,double y,double z ,double q0 ,double q1 ,double q2 ,double q3){
        id1 = _id1;
        param[0] = x;
        param[1] = y;
        param[2] = z;
        param[3] = q0;
        param[4] = q1;
        param[5] = q2;
        param[6] = q3;
        con_param2se3();
    }
    int id1;
    double se3[6];
private:
    void con_param2se3();
    double param[7];
};

void mypoint::con_param2se3()
{
    Eigen::Quaterniond q(param[3], param[4], param[5], param[6]);
    q.normalize();
    Eigen::Matrix<double,6,1> se = Sophus::SE3d( q, Eigen::Vector3d(param[0], param[1], param[2]) ).log();
    for(int i = 0; i < 6; i++)
    {
        se3[i] = se(i,0);
    }
}

class SE3Parameterization : public ceres::LocalParameterization
{
public:
    SE3Parameterization() {}
    virtual ~SE3Parameterization() {}
    virtual bool Plus(const double* x,
                      const double* delta,
                      double* x_plus_delta) const
    {
        Eigen::Map<const Eigen::Matrix<double, 6, 1>> varse(x);
        Eigen::Map<const Eigen::Matrix<double, 6, 1>> delta_varse(delta);

        Sophus::SE3d T = Sophus::SE3d::exp(varse);
        Sophus::SE3d delta_T = Sophus::SE3d::exp(delta_varse);

        Vector6d x_plus_delta_varse = (delta_T * T).log();

        for(int i = 0; i < 6; ++i)
            x_plus_delta[i] = x_plus_delta_varse(i, 0);

        return true;
    }
    virtual bool ComputeJacobian(const double* x,
                                 double* jacobian) const
    {
        ceres::MatrixRef(jacobian, 6, 6) = ceres::Matrix::Identity(6, 6);
        return true;
    }
    virtual int GlobalSize() const { return 6; }
    virtual int LocalSize() const { return 6; }
};


class PoseGraphCostFunction : public ceres::SizedCostFunction<6,6,6>{
public:
    EIGEN_MAKE_ALIGNED_OPERATOR_NEW;

    ~PoseGraphCostFunction(){}
    PoseGraphCostFunction(Sophus::SE3d _se3, Matrix6d _covariance): edge_se3(_se3), convariance(_covariance){}

    virtual bool Evaluate(double const* const* parameters,
                          double *residuals,
                          double **jacobians) const{
        Sophus::SE3d pose_i = Sophus::SE3d::exp(Vector6d(parameters[0]));
        Sophus::SE3d pose_j = Sophus::SE3d::exp(Vector6d(parameters[1]));
        Matrix6d sqrt_info = Eigen::LLT<Matrix6d>(convariance.inverse()).matrixLLT().transpose();
        // Matrix6d sqrt_info = Eigen::LLT<Matrix6d>(convariance.inverse()).matrixLLT().transpose();
        Eigen::Map<Vector6d> residual(residuals);
        // residual = (edge_se3.inverse() * estimate).log();
        residual = (edge_se3.inverse() * pose_i.inverse() * pose_j).log();
        if(jacobians){
            Matrix6d Jr;
            Jr.block(0,0,3,3) = Sophus::SO3d::hat(Sophus::SE3d::exp(residual).so3().log());
            Jr.block(0,3,3,3) = Sophus::SO3d::hat(Sophus::SE3d::exp(residual).translation());
            Jr.block(3,0,3,3) = Eigen::Matrix3d::Zero(3,3);
            Jr.block(3,3,3,3) = Jr.block(0,0,3,3);
            Jr = Matrix6d::Identity() + 0.5 * Jr ;
            Matrix6d jacA = - Jr * pose_j.inverse().Adj();
            Matrix6d jacB = - jacA;
            if(jacobians[0])
            {
                Eigen::Map<Eigen::Matrix<double,6,6, Eigen::RowMajor>> jacobian_i(jacobians[0]);
                jacobian_i = sqrt_info * jacA;
            }
            if(jacobians[1])
            {
                Eigen::Map<Eigen::Matrix<double,6,6, Eigen::RowMajor>> jacobian_j(jacobians[1]);
                jacobian_j = sqrt_info * jacB;
            }
        }
        residual = sqrt_info * (edge_se3.inverse() * pose_i.inverse() * pose_j).log();
        return true;
    }
private:
    const Sophus::SE3d edge_se3;
    const Eigen::Matrix<double,6,6>  convariance;
};



void drawpclpoint(std::vector<mypoint> points)
{
    pcl::PointCloud<pcl::PointXYZRGB> ::Ptr pointCloud( new pcl::PointCloud<pcl::PointXYZRGB> ); 
    for(int i = 0; i < points.size(); i++ )
    {
        pcl::PointXYZRGB pointpcl;
        Eigen::Map<const Eigen::Matrix<double,6,1>> poseAVec6d( points[i].se3 );
        Sophus::SE3d poseSE3 = Sophus::SE3d::exp(poseAVec6d);        
        Eigen::Matrix<double,3,1> point_t = poseSE3.translation().transpose();
        pointpcl.x = point_t(0,0);
        pointpcl.y = point_t(1,0);
        pointpcl.z = point_t(2,0);
        pointpcl.b = 0;
        pointpcl.g = 0;
        pointpcl.r = 255;
        pointCloud->points.push_back( pointpcl );
    }
    // pointCloud->is_dense = true;
    pcl::visualization::CloudViewer viewer("Cloud Viewer");
    viewer.showCloud(pointCloud);
    while (!viewer.wasStopped()) 
    {
        
    }
}


int main(int argc, char** argv)
{
    const std::string file_path = "/home/smz/learning_ref/slambook2-master/ch10/sphere.g2o";
    std::ifstream g2oFile(file_path);
    if(!g2oFile)
    {
        std::cout << "file " << file_path << "doesnot exist" << std::endl;
        return -1; 
    }
    ceres::Problem problem;
    ceres::LocalParameterization *local_param = new SE3Parameterization();


    int pose_num = 0;
    int edge_num = 0;
    std::vector<mypoint> points;

    while(!g2oFile.eof())
    {
        std::string flag;
        g2oFile >> flag;
        if(flag[0] == 'V')
        {
            pose_num++;
            int id;
            double x,y,z,q0,q1,q2,q3;
            g2oFile >> id >> x >> y >> z >> q1 >> q2 >> q3 >> q0;
            mypoint p(id,x,y,z,q0,q1,q2,q3);
            points.push_back(p);
        }
        if(flag[0] == 'E')
        {
            edge_num++;
            int id1,id2;
            double x,y,z,q0,q1,q2,q3;
            g2oFile >> id1 >> id2 >> x >> y >> z >> q1 >> q2 >> q3 >> q0;
            Sophus::SE3d edge_m(Eigen::Quaterniond(q0, q1, q2, q3).normalized(),Eigen::Vector3d(x, y, z));
            Eigen::Matrix<double,6,6> information;
            for(int i = 0;i < 6; i++)
            {
                for(int j = i;j < 6; j++)
                {
                    g2oFile >> information(i,j);
                    if(j != i)
                        information(j, i) = information(i, j);
                }
            }
            ceres::LossFunction *loss = new ceres::HuberLoss(1.0);
            ceres::CostFunction *costfunc = new PoseGraphCostFunction(edge_m, information);
            problem.AddResidualBlock(costfunc, loss, points[id1].se3, points[id2].se3);
            problem.SetParameterization(points[id1].se3, local_param);
            problem.SetParameterization(points[id2].se3, local_param);
        }
    }
    g2oFile.close();


    drawpclpoint(points);

    
    ceres::Solver::Options options;
    options.linear_solver_type = ceres::SPARSE_NORMAL_CHOLESKY;
    options.max_linear_solver_iterations = 50;
    options.minimizer_progress_to_stdout = true;

    ceres::Solver::Summary summary;
    ceres::Solve(options, &problem, &summary);
    std::cout << summary.FullReport() << std::endl;

    std::ofstream txt("./result.g2o");
    for( int i=0; i < points.size(); i++ )
    {
        Eigen::Map<const Eigen::Matrix<double,6,1>> poseAVec6d( points[i].se3 );
        Sophus::SE3d poseSE3 = Sophus::SE3d::exp(poseAVec6d);
        Eigen::Quaterniond q = poseSE3.so3().unit_quaternion();

        txt << "VERTEX_SE3:QUAT" << ' ';
        txt << i << ' ';
        txt << poseSE3.translation().transpose() << ' ';
        txt << q.x() <<' '<< q.y()<< ' ' << q.z() <<' '<< q.w()<<' ' << std::endl;
    }
    g2oFile.open(file_path);
    while(!g2oFile.eof()){
        std::string s;
        std::getline(g2oFile, s);
        if(s[0] != 'E') continue;
        else txt << s << std::endl;
    }
    g2oFile.close();
    txt.close();

    drawpclpoint(points);

    return 0;
}

其中class SE3Parameterization : public ceres::LocalParameterization 构建了优化变量


class SE3Parameterization : public ceres::LocalParameterization
{
public:
    SE3Parameterization() {}
    virtual ~SE3Parameterization() {}
    virtual bool Plus(const double* x,
                      const double* delta,
                      double* x_plus_delta) const
    {
        Eigen::Map<const Eigen::Matrix<double, 6, 1>> varse(x);
        Eigen::Map<const Eigen::Matrix<double, 6, 1>> delta_varse(delta);

        Sophus::SE3d T = Sophus::SE3d::exp(varse);
        Sophus::SE3d delta_T = Sophus::SE3d::exp(delta_varse);

        Vector6d x_plus_delta_varse = (delta_T * T).log();

        for(int i = 0; i < 6; ++i)
            x_plus_delta[i] = x_plus_delta_varse(i, 0);

        return true;
    }
    virtual bool ComputeJacobian(const double* x,
                                 double* jacobian) const
    {
        ceres::MatrixRef(jacobian, 6, 6) = ceres::Matrix::Identity(6, 6);
        return true;
    }
    virtual int GlobalSize() const { return 6; }
    virtual int LocalSize() const { return 6; }
};

输入是变量,增量,变量+增量,用来构建更新方式,同时给出雅克比矩阵,最后给出变量维度和正切空间维度

class PoseGraphCostFunction : public ceres::SizedCostFunction<6,6,6>{
public:
    EIGEN_MAKE_ALIGNED_OPERATOR_NEW;

    ~PoseGraphCostFunction(){}
    PoseGraphCostFunction(Sophus::SE3d _se3, Matrix6d _covariance): edge_se3(_se3), convariance(_covariance){}

    virtual bool Evaluate(double const* const* parameters,
                          double *residuals,
                          double **jacobians) const{
        Sophus::SE3d pose_i = Sophus::SE3d::exp(Vector6d(parameters[0]));
        Sophus::SE3d pose_j = Sophus::SE3d::exp(Vector6d(parameters[1]));
        Matrix6d sqrt_info = Eigen::LLT<Matrix6d>(convariance.inverse()).matrixLLT().transpose();
        // Matrix6d sqrt_info = Eigen::LLT<Matrix6d>(convariance.inverse()).matrixLLT().transpose();
        Eigen::Map<Vector6d> residual(residuals);
        // residual = (edge_se3.inverse() * estimate).log();
        residual = (edge_se3.inverse() * pose_i.inverse() * pose_j).log();
        if(jacobians){
            Matrix6d Jr;
            Jr.block(0,0,3,3) = Sophus::SO3d::hat(Sophus::SE3d::exp(residual).so3().log());
            Jr.block(0,3,3,3) = Sophus::SO3d::hat(Sophus::SE3d::exp(residual).translation());
            Jr.block(3,0,3,3) = Eigen::Matrix3d::Zero(3,3);
            Jr.block(3,3,3,3) = Jr.block(0,0,3,3);
            Jr = Matrix6d::Identity() + 0.5 * Jr ;
            Matrix6d jacA = - Jr * pose_j.inverse().Adj();
            Matrix6d jacB = - jacA;
            if(jacobians[0])
            {
                Eigen::Map<Eigen::Matrix<double,6,6, Eigen::RowMajor>> jacobian_i(jacobians[0]);
                jacobian_i = sqrt_info * jacA;
            }
            if(jacobians[1])
            {
                Eigen::Map<Eigen::Matrix<double,6,6, Eigen::RowMajor>> jacobian_j(jacobians[1]);
                jacobian_j = sqrt_info * jacB;
            }
        }
        residual = sqrt_info * (edge_se3.inverse() * pose_i.inverse() * pose_j).log();
        return true;
    }
private:
    const Sophus::SE3d edge_se3;
    const Eigen::Matrix<double,6,6>  convariance;
};


注意:1、所有变量都是在parameters中,输入的时候需要将其分解分别保存

2、注意行主还是列主的矩阵

其他的就和自动求导的一样

 

 

上一篇:camke(4)配置sophus


下一篇:npm和yarn的常用命令