slam14讲后端优化代码注释(一)(第二版ch9)

slam14讲后端优化代码注释(一)(第二版ch9)

  • bundle_adjustment_g2o.cpp
  • bundle_adjustment_ceres.cpp
  • common.cpp
  • common.h
  • random.h
  • rotation.h
  • SnavelyReprojectionError.h

bundle_adjustment_g2o.cpp

#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include "common.h"
#include "sophus/se3.hpp"

using namespace Sophus;
using namespace Eigen;
using namespace std;

/// 姿态和内参的结构 
struct PoseAndIntrinsics {
           //包含 R T f k1 k2
    PoseAndIntrinsics() {
     }

    /// set from given data address
    explicit PoseAndIntrinsics(double *data_addr) {
     
        rotation = SO3d::exp(Vector3d(data_addr[0], data_addr[1], data_addr[2]));
        translation = Vector3d(data_addr[3], data_addr[4], data_addr[5]);
        focal = data_addr[6];
        k1 = data_addr[7];
        k2 = data_addr[8];
    }

    /// 将估计值放入内存
    void set_to(double *data_addr) {
     
        auto r = rotation.log();
        for (int i = 0; i < 3; ++i) data_addr[i] = r[i];
        for (int i = 0; i < 3; ++i) data_addr[i + 3] = translation[i];
        data_addr[6] = focal;
        data_addr[7] = k1;
        data_addr[8] = k2;
    }

    SO3d rotation;
    Vector3d translation = Vector3d::Zero();
    double focal = 0;
    double k1 = 0, k2 = 0;
};

/// 位姿加相机内参的顶点,9维,前三维为so3,接下去为t, f, k1, k2
class VertexPoseAndIntrinsics : public g2o::BaseVertex<9, PoseAndIntrinsics> {
         //优化变量维度  数据类型
public:
    EIGEN_MAKE_ALIGNED_OPERATOR_NEW;

    VertexPoseAndIntrinsics() {
     }

    virtual void setToOriginImpl() override {
                                           //顶点初始化
        _estimate = PoseAndIntrinsics();
    }

    virtual void oplusImpl(const double *update) override {
                           //顶点估计值更新
        _estimate.rotation = SO3d::exp(Vector3d(update[0], update[1], update[2])) * _estimate.rotation;
        _estimate.translation += Vector3d(update[3], update[4], update[5]);
        _estimate.focal += update[6];
        _estimate.k1 += update[7];
        _estimate.k2 += update[8];
    }

    /// 根据估计值投影一个点
    Vector2d project(const Vector3d &point) {
     
        Vector3d pc = _estimate.rotation * point + _estimate.translation;
        pc = -pc / pc[2];                                               //归一化平面  BAL数据集说明  
        double r2 = pc.squaredNorm();
        //去畸变 1 + k1*r2 + k2*r2*r2 
        double distortion = 1.0 + r2 * (_estimate.k1 + _estimate.k2 * r2);      
        return Vector2d(_estimate.focal * distortion * pc[0],           //像素平面
                        _estimate.focal * distortion * pc[1]);
    }

    virtual bool read(istream &in) {
     }

    virtual bool write(ostream &out) const {
     }
};

class VertexPoint : public g2o::BaseVertex<3, Vector3d> {
     
public:
    EIGEN_MAKE_ALIGNED_OPERATOR_NEW;

    VertexPoint() {
     }

    virtual void setToOriginImpl() override {
                      //顶点初始化
        _estimate = Vector3d(0, 0, 0);
    }

    virtual void oplusImpl(const double *update) override {
                  //顶点估计值更新
        _estimate += Vector3d(update[0], update[1], update[2]);
    }

    virtual bool read(istream &in) {
     }

    virtual bool write(ostream &out) const {
     }
};

class EdgeProjection :
    public g2o::BaseBinaryEdge<2, Vector2d, VertexPoseAndIntrinsics, VertexPoint> {
     
public:
    EIGEN_MAKE_ALIGNED_OPERATOR_NEW;

    virtual void computeError() override {
     
        auto v0 = (VertexPoseAndIntrinsics *) _vertices[0];
        auto v1 = (VertexPoint *) _vertices[1];
        auto proj = v0->project(v1->estimate());
        _error = proj - _measurement;                               //误差项
    }
    // 没有实现linearizeOplus() 的重写时,   g2o会自动提供一个数值计算的雅克比

    // use numeric derivatives
    virtual bool read(istream &in) {
     }

    virtual bool write(ostream &out) const {
     }

};

void SolveBA(BALProblem &bal_problem);

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

    if (argc != 2) {
     
        cout << "usage: bundle_adjustment_g2o bal_data.txt" << endl;
        return 1;
    }

    BALProblem bal_problem(argv[1]);   //读取BAL数据集
    bal_problem.Normalize();           //对相机参数和路标点3D数据进行处理
    bal_problem.Perturb(0.1, 0.5, 0.5); //给路标3D点添加噪声
    bal_problem.WriteToPLYFile("initial.ply");  //生成噪声ply文件
    SolveBA(bal_problem);
    bal_problem.WriteToPLYFile("final.ply"); //生成优化后的ply文件

    return 0;
}

void SolveBA(BALProblem &bal_problem) {
     
    const int point_block_size = bal_problem.point_block_size();  //3d点的数据大小
    const int camera_block_size = bal_problem.camera_block_size();//相机位姿的数据大小
    double *points = bal_problem.mutable_points();                //3d点的首地址
    double *cameras = bal_problem.mutable_cameras();              //相机位姿的首地址

    // pose dimension 9, landmark is 3     T 6维 + point 3维  ,观测点 point  3维
    typedef g2o::BlockSolver<g2o::BlockSolverTraits<9, 3>> BlockSolverType;              //块求解器
    typedef g2o::LinearSolverCSparse<BlockSolverType::PoseMatrixType> LinearSolverType;  //线性求解器
    // use LM
    auto solver = new g2o::OptimizationAlgorithmLevenberg(                              //总求解器
        g2o::make_unique<BlockSolverType>(g2o::make_unique<LinearSolverType>()));
    g2o::SparseOptimizer optimizer;                                                     //建立稀疏矩阵优化器
    optimizer.setAlgorithm(solver);                                                      //设置求解器
    optimizer.setVerbose(true);                                                           //打开调试输出

    /// build g2o problem
    const double *observations = bal_problem.observations();                          //得到观测的像素坐标
    // vertex
    //加入顶点
    //因为顶点有很多个,所以需要容器
    //容器 vertex_pose_intrinsics 和 vertex_points存放两顶点的地址
    vector<VertexPoseAndIntrinsics *> vertex_pose_intrinsics;
    vector<VertexPoint *> vertex_points;
    for (int i = 0; i < bal_problem.num_cameras(); ++i) {
                                   //相机位姿顶点
        VertexPoseAndIntrinsics *v = new VertexPoseAndIntrinsics();
        double *camera = cameras + camera_block_size * i;
        v->setId(i);
        v->setEstimate(PoseAndIntrinsics(camera));                                    //设置初始估计值
        optimizer.addVertex(v);                                                      //增加pose顶点
        vertex_pose_intrinsics.push_back(v);
    }
    for (int i = 0; i < bal_problem.num_points(); ++i) {
                                   //观测点顶点
        VertexPoint *v = new VertexPoint();
        double *point = points + point_block_size * i;
        v->setId(i + bal_problem.num_cameras());
        v->setEstimate(Vector3d(point[0], point[1], point[2]));
        // g2o在BA中需要手动设置待Marg的顶点
        v->setMarginalized(true);                                                  //手动设置需要边缘化的顶点
        optimizer.addVertex(v);                                                    //增加point顶点
        vertex_points.push_back(v);
    }

    // edge
    for (int i = 0; i < bal_problem.num_observations(); ++i) {
                         //g2o的边是误差
        EdgeProjection *edge = new EdgeProjection;
        edge->setVertex(0, vertex_pose_intrinsics[bal_problem.camera_index()[i]]);
        edge->setVertex(1, vertex_points[bal_problem.point_index()[i]]);
        edge->setMeasurement(Vector2d(observations[2 * i + 0], observations[2 * i + 1]));    //设置观测值
        edge->setInformation(Matrix2d::Identity());
        edge->setRobustKernel(new g2o::RobustKernelHuber());                     //设置鲁棒核函数
        optimizer.addEdge(edge);
    }

    optimizer.initializeOptimization();
    optimizer.optimize(40);

    // set to bal problem    将优化后的值 储存到bal_problem
    for (int i = 0; i < bal_problem.num_cameras(); ++i) {
     
        double *camera = cameras + camera_block_size * i;
        auto vertex = vertex_pose_intrinsics[i];
        auto estimate = vertex->estimate();
        estimate.set_to(camera);
    }
    for (int i = 0; i < bal_problem.num_points(); ++i) {
     
        double *point = points + point_block_size * i;
        auto vertex = vertex_points[i];
        for (int k = 0; k < 3; ++k) point[k] = vertex->estimate()[k];
    }
}

bundle_adjustment_ceres.cpp

#include 
#include 
#include "common.h"
#include "SnavelyReprojectionError.h"

using namespace std;

void SolveBA(BALProblem &bal_problem);

int main(int argc, char **argv) {
     
    if (argc != 2) {
     
        cout << "usage: bundle_adjustment_ceres bal_data.txt" << endl;
        return 1;
    }

    BALProblem bal_problem(argv[1]);
    bal_problem.Normalize();                //数据归一化   
    bal_problem.Perturb(0.1, 0.5, 0.5);    //添加噪声 (相机旋转、相机平移、路标点)
    bal_problem.WriteToPLYFile("initial.ply");
    SolveBA(bal_problem);                   //求解
    bal_problem.WriteToPLYFile("final.ply");

    return 0;
}

void SolveBA(BALProblem &bal_problem) {
     
    const int point_block_size = bal_problem.point_block_size();              //point数据的字节数
    const int camera_block_size = bal_problem.camera_block_size();            //pose数据的字节数
    double *points = bal_problem.mutable_points();                            //存储路标点point的首地址
    double *cameras = bal_problem.mutable_cameras();                          //存储相机位姿pose的首地址

    // Observations is 2 * num_observations long array observations    
    // [u_1, u_2, ... u_n], where each u_i is two dimensional, the x
    // and y position of the observation.
    const double *observations = bal_problem.observations();                   //存储观测值的首地址
    ceres::Problem problem;                                                    //构建ceres的最小二乘问题

    for (int i = 0; i < bal_problem.num_observations(); ++i) {
     
        ceres::CostFunction *cost_function;                                     //代价函数   

        // Each Residual block takes a point and a camera as input
        // and outputs a 2 dimensional Residual
        cost_function = SnavelyReprojectionError::Create(observations[2 * i + 0], observations[2 * i + 1]);
        /*
           在代价函数的计算模型中实现  用于添加误差项时的第一个参数 实现自动求导
        */
        // If enabled use Huber's loss function.
        ceres::LossFunction *loss_function = new ceres::HuberLoss(1.0);                     //鲁棒核函数

        // Each observation corresponds to a pair of a camera and a point
        // which are identified by camera_index()[i] and point_index()[i]
        // respectively.


        //得到 与观测值匹配的相机位姿和观测点3d点
        double *camera = cameras + camera_block_size * bal_problem.camera_index()[i];     
        double *point = points + point_block_size * bal_problem.point_index()[i];

        problem.AddResidualBlock(cost_function, loss_function, camera, point);         //添加误差项
        //参数 :: 代价函数的对象  , 核函数(不使用时nullptr) ,待估计的参数(可多个)
    }

    // show some information here ...
    std::cout << "bal problem file loaded..." << std::endl;
    std::cout << "bal problem have " << bal_problem.num_cameras() << " cameras and "
              << bal_problem.num_points() << " points. " << std::endl;
    std::cout << "Forming " << bal_problem.num_observations() << " observations. " << std::endl;

    std::cout << "Solving ceres BA ... " << endl;
    //配置ceres求解器
    ceres::Solver::Options options;                                               
    options.linear_solver_type = ceres::LinearSolverType::SPARSE_SCHUR;    //采用schur消元求解增量方程
    options.minimizer_progress_to_stdout = true;                           //输出到cout
    ceres::Solver::Summary summary;                                        //优化信息
    ceres::Solve(options, &problem, &summary);                             //开始优化(参数:配置器 ,最小二乘问题。优化器)
    std::cout << summary.FullReport() << "\n";                             //输出结果
}

common.cpp

#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include "common.h"
#include "rotation.h"
#include "random.h"

/*
Map定义

Map(PointerArgType dataPtr, Index rows, Index cols, const StrideType& stride = StrideType())
可以看出,构建map变量,需要三个信息:指向数据的指针,构造矩阵的行数和列数

map相当于引用普通的c++数组,进行矩阵操作,而不用copy数据
*/
typedef Eigen::Map<Eigen::VectorXd> VectorRef;
typedef Eigen::Map<const Eigen::VectorXd> ConstVectorRef;

template<typename T>
void FscanfOrDie(FILE *fptr, const char *format, T *value) {
     
    int num_scanned = fscanf(fptr, format, value); // 从stream流中连续读取能够匹配format格式的字符到参数列表中对应的变量里。
    if (num_scanned != 1)
        std::cerr << "Invalid UW data file. ";
}

void PerturbPoint3(const double sigma, double *point) {
                   //添加方差为sigma的高斯噪声
    for (int i = 0; i < 3; ++i)
        point[i] += RandNormal() * sigma;
}

double Median(std::vector<double> *data) {
     
    int n = data->size();
    std::vector<double>::iterator mid_point = data->begin() + n / 2;
    std::nth_element(data->begin(), mid_point, data->end());
    return *mid_point;
}

BALProblem::BALProblem(const std::string &filename, bool use_quaternions) {
     
    FILE *fptr = fopen(filename.c_str(), "r");   //打开只读文件

    if (fptr == NULL) {
     
        std::cerr << "Error: unable to open file " << filename;
        return;
    };

/************************************BAL数据集说明***********************************************************/
/*
我们使用了针孔相机模型,我们为每个相机估计了一些参数,旋转矩阵R,平移矩阵t,焦距f,径向失真参数K1,K2,将3D点投影到相机中的公式为:

P  =  R * X + t       (conversion from world to camera coordinates)//把世界坐标转换为相机坐标
p  = -P / P.z         (perspective division)//相机坐标归一化处理
p' =  f * r(p) * p    (conversion to pixel coordinates)//转换得到像素坐标
r(p) = 1.0 + k1 * ||p||^2 + k2 * ||p||^4.

这给出了像素投影,其中图像的原点是图像的中心,正x轴指向右,正y轴指向上(此外,在相机坐标系中,正z- 轴向后指向,因此相机正在向下看负z轴,如在OpenGL中那样。
数据格式:
  
   
...
   

...


...


其中,相机和点索引从0开始。每个相机是一组9个参数 - R,t,f,k1和k2。 旋转R被指定为罗德里格斯的向量。



例:
1. BAL数据集说明

第一行:

16 22106 83718

16个相机,22106个点,共进行83718次相机对点的观测

第2行到83719行:

6 18595     3.775000e+01 4.703003e+01

第6个相机观测18595个点,得到的相机的观测数据为3.775000e+01 4.703003e+01

第83720行到83720+16*9=83864

共16个相机的9纬参数:-R(3维),t(3维),f(焦距),k1,k2畸变参数

第83864到83864+3*22106=150182

22106个点的三维坐标


*/

    // This wil die horribly on invalid files. Them's the breaks.
    FscanfOrDie(fptr, "%d", &num_cameras_);  //读取总的相机数
    FscanfOrDie(fptr, "%d", &num_points_);   //读取总的路标数
    FscanfOrDie(fptr, "%d", &num_observations_);  //读取总的观测数据个数

    std::cout << "Header: " << num_cameras_
              << " " << num_points_
              << " " << num_observations_;

    point_index_ = new int[num_observations_];             //取出3D路标点的标号
    camera_index_ = new int[num_observations_];            //相机的标号
    observations_ = new double[2 * num_observations_];     //观测的像素点

    num_parameters_ = 9 * num_cameras_ + 3 * num_points_;  //每个相机9个参数,每个路标3个参数
    parameters_ = new double[num_parameters_];             //参数的总大小

    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);     //观测到的像素坐标
        }
    }

    //每个相机是一组9个参数,-R:3维(罗德里格斯向量)  t:3维  f,k1,k2。后面是3D路标的数据3维
    for (int i = 0; i < num_parameters_; ++i) {
     
        FscanfOrDie(fptr, "%lf", parameters_ + i);
    }

    fclose(fptr);

    use_quaternions_ = use_quaternions;
    if (use_quaternions) {
     
        // Switch the angle-axis rotations to quaternions.
        num_parameters_ = 10 * num_cameras_ + 3 * num_points_;
        double *quaternion_parameters = new double[num_parameters_];
        double *original_cursor = parameters_;
        double *quaternion_cursor = quaternion_parameters;
        for (int i = 0; i < num_cameras_; ++i) {
     
            AngleAxisToQuaternion(original_cursor, quaternion_cursor);   //将角轴转换为四元数
            quaternion_cursor += 4;                     
            original_cursor += 3;                      
            for (int j = 4; j < 10; ++j) {
                              //其他参数 t(3维),f(焦距),k1,k2畸变参数 直接赋值
                *quaternion_cursor++ = *original_cursor++;
            }
        }
        // Copy the rest of the points.
        for (int i = 0; i < 3 * num_points_; ++i) {
         //三维点坐标直接赋值
            *quaternion_cursor++ = *original_cursor++;
        }
        // Swap in the quaternion parameters.
        delete[]parameters_;
        parameters_ = quaternion_parameters;
    }
}


//输出到普通文件
void BALProblem::WriteToFile(const std::string &filename) const {
     
    FILE *fptr = fopen(filename.c_str(), "w");            //打开可写文件 

    if (fptr == NULL) {
     
        std::cerr << "Error: unable to open file " << filename;
        return;
    }

    fprintf(fptr, "%d %d %d %d\n", num_cameras_, num_cameras_, num_points_, num_observations_);  //第一行 :相机数(两次是不是作者写晕了~) 路标个数,观测个数

    //每行输出  那个相机 观测那个路标点 的 观测值 x , y   (四个数据)
    for (int i = 0; i < num_observations_; ++i) {
     
        fprintf(fptr, "%d %d", camera_index_[i], point_index_[i]);
        for (int j = 0; j < 2; ++j) {
     
            fprintf(fptr, " %g", observations_[2 * i + j]);
        }
        fprintf(fptr, "\n");       
    }

    //输出相机坐标到文件
    for (int i = 0; i < num_cameras(); ++i) {
     
        double angleaxis[9];
        if (use_quaternions_) {
     
            //OutPut in angle-axis format.
            QuaternionToAngleAxis(parameters_ + 10 * i, angleaxis);//统一角轴方式保存
            //这里用memcpy()函数进行复制,从 parameters_ + 10 * i + 4 位置开始的 6 * sizeof(double) 内存空间的数据放入起始地址为angleaxis + 3的内存空间里
            memcpy(angleaxis + 3, parameters_ + 10 * i + 4, 6 * sizeof(double));
        } else {
     
             //这里用memcpy()函数进行复制,从 parameters_ + 9 * i + 4 位置开始的 9 * sizeof(double) 内存空间的数据放入起始地址为angleaxis 的内存空间里
            memcpy(angleaxis, parameters_ + 9 * i, 9 * sizeof(double));
        }
        for (int j = 0; j < 9; ++j) {
     
            fprintf(fptr, "%.16g\n", angleaxis[j]);//输出到文件
        }
    }

    //输出观测路标点到文件
    const double *points = parameters_ + camera_block_size() * num_cameras_;
    for (int i = 0; i < num_points(); ++i) {
     
        const double *point = points + i * point_block_size();
        for (int j = 0; j < point_block_size(); ++j) {
     
            fprintf(fptr, "%.16g\n", point[j]);
        }
    }

    fclose(fptr);   //打开文件就要关闭文件
}

// Write the problem to a PLY file for inspection in Meshlab or CloudCompare
void BALProblem::WriteToPLYFile(const std::string &filename) const {
     
    std::ofstream of(filename.c_str());

    of << "ply"
       << '\n' << "format ascii 1.0"
       << '\n' << "element vertex " << num_cameras_ + num_points_
       << '\n' << "property float x"
       << '\n' << "property float y"
       << '\n' << "property float z"
       << '\n' << "property uchar red"
       << '\n' << "property uchar green"
       << '\n' << "property uchar blue"
       << '\n' << "end_header" << std::endl;

    // Export extrinsic data (i.e. camera centers) as green points.
    //创建两个数组,用于承接CameraToAngelAxisAndCenter()解析出来的相机旋转姿态和相机位置中心
    double angle_axis[3];
    double center[3];
    for (int i = 0; i < num_cameras(); ++i) {
     
        //循环写入,首先写入的相机中心点参数,个数控制肯定是相机数据个数
        const double *camera = cameras() + camera_block_size() * i;
        //从cameras头指针开始,每次步进相机维度,这里为9,就读到了每个相机参数
        CameraToAngelAxisAndCenter(camera, angle_axis, center);
        //用CameraToAngelAxisAndCenter()函数将从相机参数中解析出来相机姿势和相机位置。当然这里只用位置了。
        of << center[0] << ' ' << center[1] << ' ' << center[2]
           << "0 255 0" << '\n';
        //坐标依次写入文件,再加上颜色数据,最后来个回车。
    }

    // Export the structure (i.e. 3D Points) as white points.
    //相机写完是路标点,用路标个数控制循环次数
    const double *points = parameters_ + camera_block_size() * num_cameras_;
    for (int i = 0; i < num_points(); ++i) {
     
         //同样,从路标数据开头位置处,依次偏移路标维度
        const double *point = points + i * point_block_size();
        for (int j = 0; j < point_block_size(); ++j) {
     
            of << point[j] << ' ';
             //维度循环,写入xyz
        }
        //加上颜色,最后要有回车
        of << "255 255 255\n";
    }
    of.close();
}


//camera数据中的旋转向量以及平移向量解析相机世界坐标系的姿态,(依旧是旋转向量)和位置(世界坐标系下的XYZ)
//具体参数说明:
//camera要解析的相机参数,前三维旋转,接着三维平移
//angle_axis解析出的相机姿态承接数组,也是旋转向量形式
//center是相机原点在世界坐标系下的定义
void BALProblem::CameraToAngelAxisAndCenter(const double *camera,
                                            double *angle_axis,
                                            double *center) const {
     
    VectorRef angle_axis_ref(angle_axis, 3);
    if (use_quaternions_) {
     
        QuaternionToAngleAxis(camera, angle_axis);
    } else {
     
        angle_axis_ref = ConstVectorRef(camera, 3);
    }

    // c = -R't
    
    //如何计算center
    //center是相机原点在世界坐标系下的定义
    //PW_center:世界坐标系下的相机坐标
    //PC_center:相机坐标系下的相机原点坐标(0,0,0)
    //根据相机坐标系与世界坐标系的转换关系:PW_center×R+t=PC_center
    //PW_center= -R't
    //旋转向量的反向过程(求逆)和旋转向量取负一样。
    Eigen::VectorXd inverse_rotation = -angle_axis_ref;
    AngleAxisRotatePoint(inverse_rotation.data(),
                         camera + camera_block_size() - 6,
                         center);
    VectorRef(center, 3) *= -1.0;

    
}

void BALProblem::AngleAxisAndCenterToCamera(const double *angle_axis,
                                            const double *center,
                                            double *camera) const {
     
    ConstVectorRef angle_axis_ref(angle_axis, 3);
    if (use_quaternions_) {
     
        AngleAxisToQuaternion(angle_axis, camera);
    } else {
     
        VectorRef(camera, 3) = angle_axis_ref;
    }

    // t = -R * c
    AngleAxisRotatePoint(angle_axis, center, camera + camera_block_size() - 6);
    VectorRef(camera + camera_block_size() - 6, 3) *= -1.0;
}


//有点数据初始化的意思,功能上看就是对待优化的point和camera数据进行了一下处理,
//用法上也是在读入了数据后,直接调用此程序进行处理。
//目前我也没弄懂这些数据处理是做什么用
void BALProblem::Normalize() {
     
    // Compute the marginal median of the geometry
    std::vector<double> tmp(num_points_);
    Eigen::Vector3d median;
    double *points = mutable_points();  //获取路标3D点的位置  即parameters_ 中首个3d坐标的地址
    for (int i = 0; i < 3; ++i) {
     
        for (int j = 0; j < num_points_; ++j) {
           
            tmp[j] = points[3 * j + i];                //将point的3d点 逐个放在tmp(vector
        }
        median(i) = Median(&tmp);                      //返回中位数,如果是偶数,取平均值   即median为point的中位数 分别对应 x,y,z
    }

    for (int i = 0; i < num_points_; ++i) {
     
        VectorRef point(points + 3 * i, 3);        //typedef Eigen::Map VectorRef;
        tmp[i] = (point - median).lpNorm<1>();     //(每个点 - 中位数) 的LP范数   
    }
        //关于.lpNorm<1>()参见下面网址帖子,是一个范数模板函数
        //http://blog.csdn.net/skybirdhua1989/article/details/17584797
        //这里用的是L1范数:||x||为x向量各个元素绝对值之和。
        //简单数一下p范数:向量各元素绝对值的p阶和的p阶根
        //lpNorm<>()函数定义是这么说的:returns the p-th root of the sum of the p-th powers of the absolute values
        //很明显,p为1的话就是各元素绝对值之和,为2就是模长
    const double median_absolute_deviation = Median(&tmp);    //(每个点 - 中位数) 的LP范数   的中位数 

    // Scale so that the median absolute deviation of the resulting
    // reconstruction is 100

    const double scale = 100.0 / median_absolute_deviation;

    // X = scale * (X - median)
    for (int i = 0; i < num_points_; ++i) {
     
        VectorRef point(points + 3 * i, 3);
        point = scale * (point - median); //对每个3D点进行处理,MAP是引用,会改变原数据
    }

    double *cameras = mutable_cameras();  /// camera参数的起始地址
    double angle_axis[3];
    double center[3];
    for (int i = 0; i < num_cameras_; ++i) {
     
        double *camera = cameras + camera_block_size() * i;
        CameraToAngelAxisAndCenter(camera, angle_axis, center);
        // center = scale * (center - median)
        VectorRef(center, 3) = scale * (VectorRef(center, 3) - median);
        AngleAxisAndCenterToCamera(angle_axis, center, camera);
    }
}

void BALProblem::Perturb(const double rotation_sigma,
                         const double translation_sigma,
                         const double point_sigma) {
     
    assert(point_sigma >= 0.0);
    assert(rotation_sigma >= 0.0);
    assert(translation_sigma >= 0.0);

    double *points = mutable_points();
    if (point_sigma > 0) {
                          // 给观测路标增加噪声   
        for (int i = 0; i < num_points_; ++i) {
     
            PerturbPoint3(point_sigma, points + 3 * i);
        }
    }

    for (int i = 0; i < num_cameras_; ++i) {
     
        double *camera = mutable_cameras() + camera_block_size() * i;

        double angle_axis[3];
        double center[3];
        // Perturb in the rotation of the camera in the angle-axis
        // representation
       //这里相机是被分成两块,旋转和平移,
      //旋转是考虑到四元数形式,增加了一步用CameraToAngelAxisAndCenter()从camera中取出三维的angle_axis,
      //然后添加噪声,添加完后再用AngleAxisAndCenterToCamera()重构camera参数
      //平移部分就直接用PerturbPoint3()添加了
        CameraToAngelAxisAndCenter(camera, angle_axis, center);   //主要是通过camera求angle_axis以及center(相机原点在世界坐标系下的坐标)
        if (rotation_sigma > 0.0) {
                       //添加旋转噪声
            PerturbPoint3(rotation_sigma, angle_axis);
        }
        AngleAxisAndCenterToCamera(angle_axis, center, camera);//将添加噪声之后的值重新加到camera中

        if (translation_sigma > 0.0)            //添加平移噪声
            PerturbPoint3(translation_sigma, camera + camera_block_size() - 6);
    }

}

common.h

#pragma once

/// 从文件读入BAL dataset
class BALProblem {
     
public:
    /// load bal data from text file
    explicit BALProblem(const std::string &filename, bool use_quaternions = false);

    ~BALProblem() {
         //析构函数  delete指针
        delete[] point_index_;
        delete[] camera_index_;
        delete[] observations_;
        delete[] parameters_;
    }

    /// save results to text file
    void WriteToFile(const std::string &filename) const;

    /// save results to ply pointcloud
    void WriteToPLYFile(const std::string &filename) const;

    void Normalize();                //数据归一化

    void Perturb(const double rotation_sigma,
                 const double translation_sigma,
                 const double point_sigma);       //添加高斯噪声

    int camera_block_size() const {
      return use_quaternions_ ? 10 : 9; }    //返回相机位姿参数的内存大小 (是否使用四元数)
 
    int point_block_size() const {
      return 3; }                             //返回观测点的数据内存大小

    int num_cameras() const {
      return num_cameras_; }                        //返回相机数

    int num_points() const {
      return num_points_; }                         //返回路标点数

    int num_observations() const {
      return num_observations_; }             //返回观测数

    int num_parameters() const {
      return num_parameters_; }                 //返回相机位姿和路标点的个数

    const int *point_index() const {
      return point_index_; }                //返回观测值(像素坐标)对应的观测路标索引

    const int *camera_index() const {
      return camera_index_; }              //返回观测值(像素坐标)对应的相机位姿索引

    const double *observations() const {
      return observations_; }           //存储观测值的首地址

    const double *parameters() const {
      return parameters_; }              //参数(pose和point)首地址

    const double *cameras() const {
      return parameters_; }                                           //相机参数首地址

    const double *points() const {
      return parameters_ + camera_block_size() * num_cameras_; }       //路标point的首地址

    /// camera参数的起始地址
    //mutable 修饰的代表在使用时慧改变原有值
    double *mutable_cameras() {
      return parameters_; }                                                    //相机参数首地址

    double *mutable_points() {
      return parameters_ + camera_block_size() * num_cameras_; }                //路标point的首地址

    double *mutable_camera_for_observation(int i) {
     
        return mutable_cameras() + camera_index_[i] * camera_block_size();                            //第i个观测值对应的相机参数地址
    }

    double *mutable_point_for_observation(int i) {
     
        return mutable_points() + point_index_[i] * point_block_size();                                //第i个观测值对应的路标point
    }

    const double *camera_for_observation(int i) const {
                                                   //第i个观测值对应的相机参数地址
        return cameras() + camera_index_[i] * camera_block_size();
    }

    const double *point_for_observation(int i) const {
                                                    //第i个观测值对应的路标point
        return points() + point_index_[i] * point_block_size();
    }

private:
    void CameraToAngelAxisAndCenter(const double *camera,
                                    double *angle_axis,
                                    double *center) const;

    void AngleAxisAndCenterToCamera(const double *angle_axis,
                                    const double *center,
                                    double *camera) const;

    int num_cameras_;
    int num_points_;
    int num_observations_;
    int num_parameters_;
    bool use_quaternions_;

    int *point_index_;      // 每个observation对应的point index
    int *camera_index_;     // 每个observation对应的camera index
    double *observations_;
    double *parameters_;
};

random.h

#ifndef RAND_H
#define RAND_H

#include 
#include 

inline double RandDouble()
{
     
    double r = static_cast<double>(rand());
    return r / RAND_MAX;
}
/*
Marsaglia polar method算法:  得到满足N(0,1)正态分布的随机点
  和y是圆: x*x+y*y <=1内的随机点
  RandNormal()处理后  x*w是服从标准正态分布的样本
*/
inline double RandNormal()
{
     
    double x1, x2, w;
    do{
     
        x1 = 2.0 * RandDouble() - 1.0;
        x2 = 2.0 * RandDouble() - 1.0;
        w = x1 * x1 + x2 * x2;
    }while( w >= 1.0 || w == 0.0);

    w = sqrt((-2.0 * log(w))/w);
    return x1 * w;
}

#endif // random.h

rotation.h

#ifndef ROTATION_H
#define ROTATION_H

#include 
#include 
#include 

//
// math functions needed for rotation conversion. 

// dot and cross production 

template<typename T>  //点乘
inline T DotProduct(const T x[3], const T y[3]) {
     
    return (x[0] * y[0] + x[1] * y[1] + x[2] * y[2]);
}

template<typename T>  //叉乘
inline void CrossProduct(const T x[3], const T y[3], T result[3]) {
     
    result[0] = x[1] * y[2] - x[2] * y[1];
    result[1] = x[2] * y[0] - x[0] * y[2];
    result[2] = x[0] * y[1] - x[1] * y[0];
}


//


// Converts from a angle anxis to quaternion : 


/*
轴角变四元数:
[q1,q2,q3]=[a0,a1,a2]*sin(theta/2)/theta
这里theta=sqrt(a0*a0+a1*a1+a2*a2)

定义sin(theta/2)/theta为k
当角不为0的时候,如常计算k. 和 q0=cos(theta/2)

当角趋近于0的时候,k趋近于0.5.q0趋近于1
*/
template<typename T>                //角轴转换成四元数
inline void AngleAxisToQuaternion(const T *angle_axis, T *quaternion) {
     
    const T &a0 = angle_axis[0];
    const T &a1 = angle_axis[1];
    const T &a2 = angle_axis[2];
    const T theta_squared = a0 * a0 + a1 * a1 + a2 * a2;

    if (theta_squared > T(std::numeric_limits<double>::epsilon())) {
     
        const T theta = sqrt(theta_squared);
        const T half_theta = theta * T(0.5);
        const T k = sin(half_theta) / theta;
        quaternion[0] = cos(half_theta);
        quaternion[1] = a0 * k;
        quaternion[2] = a1 * k;
        quaternion[3] = a2 * k;
    } else {
      // in case if theta_squared is zero
        const T k(0.5);
        quaternion[0] = T(1.0);
        quaternion[1] = a0 * k;
        quaternion[2] = a1 * k;
        quaternion[3] = a2 * k;
    }
}



/*
四元数变轴角,推导见  视觉slam第二版p60

[a0,a1,a2]=[q1,q2,q3]*theta/sin(theta/2)

定义theta/sin(theta/2)为k
这里sin(theta/2)为sqrt(q1*q1+q2*q2+q3*q3)

阈值用sin(theta/2)的平方。
如果不为0,theta为2*arctan(-sin(theta/2),-q0)或者2*arctan(sin(theta/2),q0).

如果为0,k直接趋近于2.
*/
template<typename T>
inline void QuaternionToAngleAxis(const T *quaternion, T *angle_axis) {
     
    const T &q1 = quaternion[1];
    const T &q2 = quaternion[2];
    const T &q3 = quaternion[3];
    const T sin_squared_theta = q1 * q1 + q2 * q2 + q3 * q3;

    // For quaternions representing non-zero rotation, the conversion
    // is numercially stable
    if (sin_squared_theta > T(std::numeric_limits<double>::epsilon())) {
     
        const T sin_theta = sqrt(sin_squared_theta);
        const T &cos_theta = quaternion[0];

        // If cos_theta is negative, theta is greater than pi/2, which
        // means that angle for the angle_axis vector which is 2 * theta
        // would be greater than pi...

        const T two_theta = T(2.0) * ((cos_theta < 0.0)
                                      ? atan2(-sin_theta, -cos_theta)
                                      : atan2(sin_theta, cos_theta));
        const T k = two_theta / sin_theta;

        angle_axis[0] = q1 * k;
        angle_axis[1] = q2 * k;
        angle_axis[2] = q3 * k;
    } else {
     
        // For zero rotation, sqrt() will produce NaN in derivative since
        // the argument is zero. By approximating with a Taylor series,
        // and truncating at one term, the value and first derivatives will be
        // computed correctly when Jets are used..
        const T k(2.0);
        angle_axis[0] = q1 * k;
        angle_axis[1] = q2 * k;
        angle_axis[2] = q3 * k;
    }
}


//求旋转后的点,用轴角算的。
//这里theta=sqrt(a0*a0+a1*a1+a2*a2),用theta的平方做了一个阈值判断。
//如果不为0.计算w=[a0,a1,a2]/theta,也就是n.
//计算w cross pt结果为w_cross_pt.(1-cos(theta))*w*pt为tmp.tmp是一个数值来着。
//result[0]=cos(theta)*pt[0]+sin(theta)*w_cross_pt[0]+w[0]*tmp



//如果为0.
//cos(theta)=1,sin(theta)~theta
//所以R=I+hat(w)*theta=I+hat(angle_axis)
//令w_cross_pt为angle_axis和pt的叉乘结果
//所以result[0]=pt[0]+w_cross_pt[0]
template<typename T>
inline void AngleAxisRotatePoint(const T angle_axis[3], const T pt[3], T result[3]) {
     
    const T theta2 = DotProduct(angle_axis, angle_axis);
    if (theta2 > T(std::numeric_limits<double>::epsilon())) {
     
        // Away from zero, use the rodriguez formula
        //
        //   result = pt costheta +
        //            (w x pt) * sintheta +
        //            w (w . pt) (1 - costheta)
        //
        // We want to be careful to only evaluate the square root if the
        // norm of the angle_axis vector is greater than zero. Otherwise
        // we get a division by zero.
        //
        const T theta = sqrt(theta2);
        const T costheta = cos(theta);
        const T sintheta = sin(theta);
        const T theta_inverse = 1.0 / theta;

        //角轴的单位向量
        const T w[3] = {
     angle_axis[0] * theta_inverse,
                        angle_axis[1] * theta_inverse,
                        angle_axis[2] * theta_inverse};

        // Explicitly inlined evaluation of the cross product for
        // performance reasons.
        /*const T w_cross_pt[3] = { w[1] * pt[2] - w[2] * pt[1],
                                  w[2] * pt[0] - w[0] * pt[2],
                                  w[0] * pt[1] - w[1] * pt[0] };*/
        T w_cross_pt[3];
        CrossProduct(w, pt, w_cross_pt);

        const T tmp = DotProduct(w, pt) * (T(1.0) - costheta);
        //    (w[0] * pt[0] + w[1] * pt[1] + w[2] * pt[2]) * (T(1.0) - costheta);

        result[0] = pt[0] * costheta + w_cross_pt[0] * sintheta + w[0] * tmp;
        result[1] = pt[1] * costheta + w_cross_pt[1] * sintheta + w[1] * tmp;
        result[2] = pt[2] * costheta + w_cross_pt[2] * sintheta + w[2] * tmp;
    } else {
     
        // Near zero, the first order Taylor approximation of the rotation
        // matrix R corresponding to a vector w and angle w is
        //
        //   R = I + hat(w) * sin(theta)
        //
        // But sintheta ~ theta and theta * w = angle_axis, which gives us
        //
        //  R = I + hat(w)
        //
        // and actually performing multiplication with the point pt, gives us
        // R * pt = pt + w x pt.
        //
        // Switching to the Taylor expansion near zero provides meaningful
        // derivatives when evaluated using Jets.
        //
        // Explicitly inlined evaluation of the cross product for
        // performance reasons.
        /*const T w_cross_pt[3] = { angle_axis[1] * pt[2] - angle_axis[2] * pt[1],
                                  angle_axis[2] * pt[0] - angle_axis[0] * pt[2],
                                  angle_axis[0] * pt[1] - angle_axis[1] * pt[0] };*/
        T w_cross_pt[3];
        CrossProduct(angle_axis, pt, w_cross_pt);

        result[0] = pt[0] + w_cross_pt[0];
        result[1] = pt[1] + w_cross_pt[1];
        result[2] = pt[2] + w_cross_pt[2];
    }
}

#endif // rotation.h

SnavelyReprojectionError.h

#ifndef SnavelyReprojection_H
#define SnavelyReprojection_H

#include 
#include "ceres/ceres.h"
#include "rotation.h"

class SnavelyReprojectionError {
     
public:
    SnavelyReprojectionError(double observation_x, double observation_y) : observed_x(observation_x),
                                                                           observed_y(observation_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 predictions[2];
        CamProjectionWithDistortion(camera, point, predictions);  //得到观测点的像素坐标  

        //计算误差  实质上就是重投影误差模型
        residuals[0] = predictions[0] - T(observed_x);
        residuals[1] = predictions[1] - T(observed_y);

        return true;
    }

    // camera : 9 dims array
    // [0-2] : angle-axis rotation
    // [3-5] : translateion
    // [6-8] : camera parameter, [6] focal length, [7-8] second and forth order radial distortion
    // point : 3D location.
    // predictions : 2D predictions with center of the image plane.
    template<typename T>
    static inline bool CamProjectionWithDistortion(const T *camera, const T *point, T *predictions) {
     
        // Rodrigues' formula
        T p[3];                                      
        AngleAxisRotatePoint(camera, point, p);          //将观测点旋转 再进行平移
        // camera[3,4,5] are the translation
        p[0] += camera[3];
        p[1] += camera[4];
        p[2] += camera[5];
        //以上得到相机坐标系下的3d  观测点point


        //归一化平面  去除深度
        // Compute the center fo distortion
        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 = T(1.0) + r2 * (l1 + l2 * r2);


        //投影得到像素坐标
        const T &focal = camera[6];
        predictions[0] = focal * distortion * xp;
        predictions[1] = focal * distortion * yp;

        return true;
    }

    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)));
    }

private:
    double observed_x;
    double observed_y;
};

#endif // SnavelyReprojection.h


你可能感兴趣的:(slam,c++,自动驾驶,计算机视觉,数学)