美文网首页
ceres solver 07 例子:Bundle Adjust

ceres solver 07 例子:Bundle Adjust

作者: book_02 | 来源:发表于2020-01-03 20:23 被阅读0次

    Ceres主要是为了解决大规模BA(Bundle Adjustment)问题的。

    BA是通过最小化重投影误差来同时优化3D点和相机位姿的。

    1. BA问题的模型

    假设世界坐标系到相机坐标系的变换为: 旋转 R,平移 t
    相机的参数为:焦距 f ,畸变参数 k1、k2

    所以3D点X转换为图像坐标的过程如下:

    1. P = R * X + t (从世界坐标系转化到相机坐标系)
    2. p = -P / P.z (归一到z=1平面,为下面转化到图像坐标系做准备)
    3. p' = f * r(p) * p (转换到图像坐标系下,考虑了畸变)

    其中 r(p) = 1.0 + k1 * ||p||^2 + k2 * ||p||^4

    所以考虑相机的畸变,3D点X到图像坐标p'是个非线性变换。

    2. 根据上述模型定义代价函数

    定义代价函数如下:

    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 - observed_x;
        residuals[1] = predicted_y - 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;
    };
    

    其中observed_xobserved_y为与3D点对应的已知的图像图标,为观测值,predicted_xpredicted_x是根据上述模型求出的图像坐标,为计算值。残差是重投影误差,即是他们之间的差。

    通过最小化重投影误差,同时优化3D点和相机位姿。

    这里定义了一个静态函数Create来方便实例化SnavelyReprojectionError对象,使用的是自动求导的方式。

    3. Ceres 求解过程

    完整的 Ceres 求解过程如下:

    3.1 定义代价函数

    上面定义了代价函数SnavelyReprojectionError

    3.2 构建问题,添加残差项

      ceres::Problem problem;
      for (int i = 0; i < bal_problem.num_observations(); ++i) {
        ceres::CostFunction* cost_function = 
            SnavelyReprojectionError::Create(observations[2 * i + 0],
                                             observations[2 * i + 1]);
                                             
        problem.AddResidualBlock(cost_function,
                                 NULL /* squared loss */,
                                 bal_problem.mutable_camera_for_observation(i),
                                 bal_problem.mutable_point_for_observation(i));
      }
    

    bal_problem.mutable_camera_for_observation(i)bal_problem.mutable_point_for_observation(i)分别是从文件里读取的相机位姿和3D点,observations数组里则是从文件里去读的与3D点对应的图像坐标点。

    3.3 定义求解器的选项参数,并求解

      ceres::Solver::Options options;
      options.linear_solver_type = ceres::DENSE_SCHUR;
      options.minimizer_progress_to_stdout = true;
    
      ceres::Solver::Summary summary;
      ceres::Solve(options, &problem, &summary);
      std::cout << summary.FullReport() << "\n";
    

    求解器类型用了ceres::DENSE_SCHUR,因为对于BA问题有特殊的稀疏性结构的特点,ceres::DENSE_SCHURceres::DENSE_QRSPARSE_NORMAL_CHOLESKY都合适

    4. 完整代码

    #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_;                     }
      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 == NULL) {
          return false;
        };
    
        FscanfOrDie(fptr, "%d", &num_cameras_);
        FscanfOrDie(fptr, "%d", &num_points_);
        FscanfOrDie(fptr, "%d", &num_observations_);
    
        point_index_ = new int[num_observations_];
        camera_index_ = new int[num_observations_];
        observations_ = new double[2 * num_observations_];
    
        num_parameters_ = 9 * num_cameras_ + 3 * num_points_;
        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);
          }
        }
    
        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).
    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 - observed_x;
        residuals[1] = predicted_y - 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;
    };
    
    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]);
        problem.AddResidualBlock(cost_function,
                                 NULL /* squared loss */,
                                 bal_problem.mutable_camera_for_observation(i),
                                 bal_problem.mutable_point_for_observation(i));
      }
    
      // 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;
      options.linear_solver_type = ceres::DENSE_SCHUR;
      options.minimizer_progress_to_stdout = true;
    
      ceres::Solver::Summary summary;
      ceres::Solve(options, &problem, &summary);
      std::cout << summary.FullReport() << "\n";
      return 0;
    }
    

    5. 总结

    总结使用步骤如下:

    1. 定义代价函数。根据BA问题模型定义代价函数
    2. 构建问题。添加残差项。这里要根据传感器获取的3D点、图像点、和粗略计算的相机位姿,不断地添加残差项。
    3. 求解问题。定义求解器的选项参数和求解器的结果报告

    6. 参考

    http://ceres-solver.org/nnls_tutorial.html#bundle-adjustment
    http://grail.cs.washington.edu/projects/bal/

    相关文章

      网友评论

          本文标题:ceres solver 07 例子:Bundle Adjust

          本文链接:https://www.haomeiwen.com/subject/pfstactx.html