(02)Cartographer源码无死角解析-(11) 配置文件加载2→LuaParameterDictionary使用

本人讲解关于slam一系列文章汇总链接:史上最全slam从零开始,针对于本栏目讲解(02)Cartographer源码无死角解析-链接如下:
(02)Cartographer源码无死角解析- (00)目录_最新无死角讲解:https://blog.csdn.net/weixin_43013761/article/details/127350885
 
文末正下方中心提供了本人 联系方式, 点击本人照片即可显示 W X → 官方认证 {\color{blue}{文末正下方中心}提供了本人 \color{red} 联系方式,\color{blue}点击本人照片即可显示WX→官方认证} 文末正下方中心提供了本人联系方式,点击本人照片即可显示WX官方认证
 

一、前言

在上一篇博客中,对 src/cartographer_ros/cartographer_ros/cartographer_ros/node_options.cc 文件的 LoadOptions() 函数进行了整体上的分析,但是没有进行很细致的讲解,其主要的核心都是围绕这 cartographer::common::ConfigurationFileResolver 的类对象 file_resolver 展开,该类的实现于 src/cartographer/cartographer/common/configuration_file_resolver.cc 文件中。其继承自 FileResolver:

// Resolves file paths and file content for the Lua 'read' and 'include'
// functions. Use this to configure where those functions load other files from.
class FileResolver {
public:
	virtual ~FileResolver() {}
	virtual std::string GetFullPathOrDie(const std::string& basename) = 0;
	virtual std::string GetFileContentOrDie(const std::string& basename) = 0;
};

该类十分简单,仅仅定义了一些接口而已,只有一个私有成员变量 configuration_files_directories_。那么下面我们再回到 ConfigurationFileResolver 进行细致讲解。
 

一、构造函数

configuration_file_resolver.h 文件中定义 ConfigurationFileResolver 的构造函数时,使用到了explicit关键字→作用就是防止类构造函数的隐式自动转换。比如在一个构造函数中,其期待传入的数据类型并非 int 类型,而用户误传入了一个 int 10 进去,其就有可能发生隐式转换,比如编译器把 int 10 封装成一个类对象传入。 构造函数的实现如下:

/**
 * @brief Construct a new Configuration File Resolver:: Configuration File Resolver object
 * 
 * @param[in] configuration_files_directories 配置文件目录
 */
ConfigurationFileResolver::ConfigurationFileResolver(
    const std::vector<std::string>& configuration_files_directories)
    : configuration_files_directories_(configuration_files_directories) {
  configuration_files_directories_.push_back(kConfigurationFilesDirectory);
}

传入的参数 configuration_files_directories(配置文件目录) 直接赋值给 configuration_files_directories_,然后还会把 kConfigurationFilesDirectory 这个变量追加到 configuration_files_directories_ 之中,该变量可以在 build_isolated/cartographer/install/cartographer/common/config.h 中找到,

namespace cartographer {
namespace common {

constexpr char kConfigurationFilesDirectory[] =
    "/my_work/ROS/work/cartographer_detailed_comments_ws/install_isolated/share/cartographer/configuration_files";
constexpr char kSourceDirectory[] = "/my_work/ROS/work/cartographer_detailed_comments_ws/src/cartographer";

}  // namespace common
}  // namespace cartographer

其是一个编译生成的文件,那么其是如何生成的呢?又在哪里配置? 来看 src/cartographer/cartographer/common/config.h.cmake 文件:

namespace cartographer {
namespace common {

constexpr char kConfigurationFilesDirectory[] =
    "@CARTOGRAPHER_CONFIGURATION_FILES_DIRECTORY@";
constexpr char kSourceDirectory[] = "@PROJECT_SOURCE_DIR@";

}  // namespace common
}  // namespace cartographer

其仅仅配置了两个变量,分别为 kConfigurationFilesDirectory,kSourceDirectory。
 

二、GetFileContentOrDie 与 GetFullPathOrDie

了解构造函数之后,再来看 ConfigurationFileResolver::GetFileContentOrDie() 与 ConfigurationFileResolver::GetFullPathOrDie()函数:

/**
 * @brief 在所有的配置文件目录中 根据给定配置文件的名字 搜索 配置文件
 * 
 * @param[in] basename 给定配置文件的名字
 * @return std::string 如果搜索成功, 返回配置文件的全路径名
 */
std::string ConfigurationFileResolver::GetFullPathOrDie(
    const std::string& basename) {
  for (const auto& path : configuration_files_directories_) {
    const std::string filename = path + "/" + basename;
    std::ifstream stream(filename.c_str());
    // 只要找到就退出
    if (stream.good()) {
      LOG(INFO) << "Found '" << filename << "' for '" << basename << "'.";
      return filename;
    }
  }
  // 如果找不到配置文件就退出整个程序
  LOG(FATAL) << "File '" << basename << "' was not found.";
}



/**
 * @brief 读取配置文件内容
 * 
 * @param[in] basename 文件名
 * @return std::string 文件内容的数据流
 */
std::string ConfigurationFileResolver::GetFileContentOrDie(
    const std::string& basename) {
  CHECK(!basename.empty()) << "File basename cannot be empty." << basename;

  // 根据文件名查找是否在给定文件夹中存在
  const std::string filename = GetFullPathOrDie(basename);
  std::ifstream stream(filename.c_str());

  // 读取配置文件内容并返回
  return std::string((std::istreambuf_iterator<char>(stream)),
                     std::istreambuf_iterator<char>());
}

}  // namespace common
}  // namespace cartographer

GetFullPathOrDie () 函数中是一个循环,其会对 configuration_files_directories_ 中的所有目录进行查找,查找 basename(.lua) 文件,找到,或者没有找到都会打印相应的信息。最后GetFileContentOrDie() 函数从找到的文件中读出内容返回。
 

三、LuaParameterDictionary

LoadOptions() 函数中,在其调用:

const std::string code =file_resolver->GetFileContentOrDie(configuration_basename);

之后,其进一步会执行:

  // 根据给定的字符串, 生成一个lua字典
  cartographer::common::LuaParameterDictionary lua_parameter_dictionary(
      code, std::move(file_resolver));

LuaParameterDictionary 于 src/cartographer/cartographer/common/lua_parameter_dictionary.cc 文件中实现,先来看看其头文件 lua_parameter_dictionary.h,可以看到如下:

#include "cartographer/common/lua.h"
#include "cartographer/common/port.h"
#include "glog/logging.h"

其上的 lua.h 内容为:

#ifndef CARTOGRAPHER_COMMON_LUA_H_
#define CARTOGRAPHER_COMMON_LUA_H_

// 这个头文件包含了使用lua所需的所有头文件
#include 

#endif  // CARTOGRAPHER_COMMON_LUA_H_

可以看到这里的使用了 lua 这个标准库,那么再来看 LuaParameterDictionary 的构造函数:

LuaParameterDictionary::LuaParameterDictionary(
    const std::string& code, std::unique_ptr<FileResolver> file_resolver)
    : LuaParameterDictionary(code, ReferenceCount::YES,
                             std::move(file_resolver)) {}

LuaParameterDictionary::LuaParameterDictionary(
    const std::string& code, ReferenceCount reference_count,
    std::unique_ptr<FileResolver> file_resolver)
    : L_(luaL_newstate()),
      index_into_reference_table_(-1),
      file_resolver_(std::move(file_resolver)),
      reference_count_(reference_count) {
	......
}

可以看到其有两个重载函数,在初始化 LuaParameterDictionary 的时候,传入的为两个参数 code、file_resolver。所以其是调用第一个重载函数,其使用初始化列表对进行初始化的时候,会调用第二个重载函数,多了一个传入的参数为 ReferenceCount::YES。ReferenceCount 是枚举类型: enum class ReferenceCount { YES, NO };

lua相关的这个库还是比较复杂的,更多Lua的学习可以参考:https://www.cnblogs.com/chevin/p/5884657.html,
太细节的东西这里就不进行讲解了。在第二个重载函数中,其中有如下代码(有兴趣的朋友可以深入了解一下):

  // 将choose、read、LuaInclude注册为Lua的全局函数变量,使得Lua可以调用C函数
  lua_register(L_, "choose", LuaChoose);
  lua_register(L_, "include", LuaInclude);
  lua_register(L_, "read", LuaRead);

 

四、LuaParameterDictionary使用

个人感觉对于 LuaParameterDictionary 只需要会使用就行了,对于其使用无法就是 LoadOptions() 中调用的四句代码,另外本人在debug的时候,也并没有看到其中具体内容,如下:
(02)Cartographer源码无死角解析-(11) 配置文件加载2→LuaParameterDictionary使用_第1张图片
其实也没有太多的信息可用,也就是可以看看其中的包含的 include 文件名而已,不过其并不影响对其的使用,如 src/cartographer/cartographer/common/lua_parameter_dictionary.cc 文件中的如下函数:

  std::string GetString(const std::string& key);
  double GetDouble(const std::string& key);
  int GetInt(const std::string& key);
  bool GetBool(const std::string& key);
  int GetNonNegativeInt(const std::string& key);
  std::vector<double> GetArrayValuesAsDoubles();
  ......

都是从 LuaParameterDictionary 字典获取值是使用的,即传入一个 key,返回其对应的 value,同时还会根据调用的函数不同,对数据进行校验。其实我们会配置,更改参数就行了,不用理解得那么细。平常情况下,我们使用 yml 文件就可以了,并没有这么复杂。
 

五、结语

通过前面的学习,已经大概知道 Cartographer 是如何加载配置文件的了,在回到 node_main.cc 文件:

  // 根据Lua配置文件中的内容, 为node_options, trajectory_options 赋值
  std::tie(node_options, trajectory_options) =
      LoadOptions(FLAGS_configuration_directory, FLAGS_configuration_basename);

  // MapBuilder类是完整的SLAM算法类
  // 包含前端(TrajectoryBuilders,scan to submap) 与 后端(用于查找回环的PoseGraph) 
  auto map_builder =
      cartographer::mapping::CreateMapBuilder(node_options.map_builder_options);

  // Node类的初始化, 将ROS的topic传入SLAM, 也就是MapBuilder
  Node node(node_options, std::move(map_builder), &tf_buffer,
            FLAGS_collect_metrics);

后续将会围绕 CreateMapBuilder 与 Node 展开,进行详细讲解。

 
 
 

你可能感兴趣的:(自动驾驶,无人机,机器人,Cartographer,增强现实)