RootBA源码解读1—接口设计

15 篇文章 1 订阅

rootBA是TUM开源的BA问题求解器。虽然是一个单纯的求解器,但是它的开源代码架构却非常的严密,从接口到模板类的使用,求解,再到结果输出,但非常值得仔细品味,接下来就来一起读读。

从ReadMe中,我们可以看到,rootBA的可执行文件的运行指令是

./bin/bal --input data/rootba/bal/ladybug/problem-49-7776-pre.txt

从CMakeLists中看到对应的cpp文件是bal.cpp。

add_executable(bal bal.cpp)
target_link_libraries(bal rootba_solver rootba_ceres rootba_cli)

接下来,来看看bal.cpp的内容

int main(int argc, char** argv) {
//set namespace
  using namespace rootba;
//set glog
  FLAGS_logtostderr = true;
  google::InitGoogleLogging(argv[0]);
  google::InstallFailureSignalHandler();

  // parse cli and load config
  BalAppOptions options;
  if (!parse_bal_app_arguments(
          "Solve BAL problem with solver determined by config.", argc, argv,
          options)) {
    return 1;
  }

 别看参数加载的代码没几行,但是仔细看看会发现内容真不少,首先BalAppOptions就不简单,它是一个继承类

struct BalAppOptions : public VisitableOptions<BalAppOptions> {
  BEGIN_VISITABLES(BalAppOptions);

  VISITABLE(BalDatasetOptions, dataset);
  VISITABLE(SolverOptions, solver);

  END_VISITABLES;
};

看了一下库的介绍,用它的目的应该就是为了把stuct的变量名称和数值都打印出来?

再来看看parse_bal_app_arguments这个函数

这里又用了clipp,json两个第三方库,用来进行参数读取。值得注意的是,rootba_config。toml这个文件可以用于被读取参数,但是!项目中没有,所以,如果需要自定义参数,你需要新建一个文件来写入这些参数。

bool parse_bal_app_arguments(const std::string& application_summary, int argc,
                             char** argv, BalAppOptions& options) {
  using namespace clipp;
  using nlohmann::json;

  // command line parsing
  std::string working_dir;
  std::string config_path("rootba_config.toml");
  bool dump_config = false;
  //std::string ground_truth_path;
  json parsed_dataset_options = json::object();
  json parsed_solver_options = json::object();

 接着就是一个cli的初始化,进行command line的读取。

  // prepare arguments
  auto cli =
      (option("-C", "--directory") &
           value("DIR", working_dir) %
               "Change to given directory before doing anything else.",
       option("--config") & value("PATH", config_path) % "path to config file",
      //  option("--ground_truth")&
      //      value("Ground truth",options.ground_truth) % "path to ground truth file",
       option("--dump-config").set(dump_config) %
           "print effective config and exit",
       (option("") % "dummy",
        cli_options(parsed_dataset_options, options.dataset, "")) %
           "dataset options",
       (option("") % "dummy",
        cli_options(parsed_solver_options, options.solver, "")) %
           "solver options");

其中,cli_options是用于把command line读取的参数映射到json。 

// Returns cli group for given options (recursive). When parsing cli arguments,
// the actually passed arguments are then stored in the json object
// parse_output. You can then pass this json object to options._load() to set
// the parsed values.
clipp::group cli_options(nlohmann::json& parse_output,
                         const OptionsInterface& options,
                         const std::string& prefix);

}  // namespace rootba

接下来的的参数读取流程,我认为最重要的就是要明白,参数都在bal_dataset_options.cpp和 bal_residual_options.cpp,如果现在命令行添加参数,只需要在这两个文件添加即可。按照作者的说法也需要在docstr里添加,按我的理解就是在docs/Configuration.md添加相关说明。值得注意的是,如果你的参数包括下划线,比如hello_world,那么在终端输入的时候,会被自动转化为hello-world.

原因如下,(找了一天bug的我TT)


inline std::string to_cli_name(std::string name) {
  std::replace(name.begin(), name.end(), '_', '-');
  return name;
}

关于参数读取的代码,作者调用了两个之前没怎么接触过的第三方库,clipp核visit_struct。真的是在细节上很能花功夫。

clipp/README.md at master · muellan/clipp · GitHubeasy to use, powerful & expressive command line argument parsing for modern C++ / single header / usage & doc generation - clipp/README.md at master · muellan/clipphttps://github.com/muellan/clipp/blob/master/README.md
visit_struct/README.md at master · garbageslam/visit_struct · GitHubA miniature library for struct-field reflection in C++ - visit_struct/README.md at master · garbageslam/visit_structhttps://github.com/garbageslam/visit_struct/blob/master/README.md

在读取完相关参数后,我们就可以开始初始化problem了。

首先是采用ceres进行求解的代码,先忽视。 然后是用bal求解的过程了。

   BalPipelineSummary summary;

    if (!options.solver.use_double) {
#ifdef ROOTBA_INSTANTIATIONS_FLOAT
      // load dataset
      auto bal_problem = load_normalized_bal_problem<float>(
          options.dataset, &summary.dataset, &summary.timing);

      // run solver
      bundle_adjust_manual(bal_problem, options.solver, &summary.solver,
                           &summary.timing);

      // postprocess
      bal_problem.postprocress(options.dataset, &summary.timing);
#else
      LOG(FATAL) << "Compiled without float support.";
#endif
    } else {
#ifdef ROOTBA_INSTANTIATIONS_DOUBLE
      // load dataset
      auto bal_problem = load_normalized_bal_problem<double>(
          options.dataset, &summary.dataset, &summary.timing);

      // run solver
      bundle_adjust_manual(bal_problem, options.solver, &summary.solver,
                           &summary.timing);

      // postprocess
      bal_problem.postprocress(options.dataset, &summary.timing);
#else
      LOG(FATAL) << "Compiled without double support.";
#endif
    }

    // log summary
    BaLog log;
    log_summary(log, summary);
    log.save_json(options.solver.log);
  }

可以看到,宏定义里区分了double和float。但是,它只是用于编译的时候判断是否编译这部分代码,换句话说,它是为了节省编译时间。而在CMakeLIsts中的配置,两种都默认编译了。而真正可以选择数据类型的,是options.solver.use_double这个参数。而修改它可以通过添加toml参数或者直接在cli输入参数完成。

option(ROOTBA_INSTANTIATIONS_DOUBLE "Instatiate templates for Scalar=double." ON)
option(ROOTBA_INSTANTIATIONS_FLOAT "Instatiate templates for Scalar=float." ON)

 到目前为止,我们构建问题之前的工作基本完成。

关于接口的读取,我认为值得学习的有以下几点

1. 利用clipp,visit_struct完成了接口的读入,个人的感觉是这样的接口设计下,参数的添加修改还是比较方便的,用command line 和 yoml文件都可以实现,需要修改的代码也不多。

2. 利用宏定义避免了无意义的编译,即使可以通过配置文件选择数据类型double /float,但是作者还是在CMakeLists中设定了是否把对应部分添加到项目中的开关,从而减少编译时间。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值