提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
前言
以下内容与本文基本无关,可以直接跳过
实验室巨佬师兄在调整机器人代码参数时使用的方法是修改机器人事先定义在头文件中的宏定义后再进行编译。且不说代码使用了ROS参数框架,可以使用ROS的参数服务器方法或者dynamic-reconfiguration(希望我拼写对了)的方法对程序运行参数进行初始化或者动态调整,就是这么一遍遍的编译流程看着就头大,只能说幸亏程序不大。
“师兄,为啥不用roslaunch启动yaml文件做程序初始化的参数啊”
“啊…师兄当年写这些程序的时候还不知道这个”
“卧槽,那你怎么写出这么nb的控制程序的”
“可能靠天赋吧”
跳到这里就可以了
yaml-cpp是使用C++对yaml格式的程序进行解析与生成(emitter)的库,满足yaml文件的基本格式
本文涉及的内容:yaml-cpp在ubuntu 18.04下的安装与基本使用思路与示例程序。
本文不涉及的内容:该库在windows等其他环境下的安装与使用;yaml的语法
提示:以下是本篇文章正文内容,下面案例可供参考
一、我为什么要学习YAML-CPP
正如开篇的废话所说的,我就是想“优雅”地对机器人程序进行参数初始化,实现:打开yaml配置文件-修改参数-保存文件-打开程序(-程序崩溃)的流程。
然而我就是不想使用ros提供的参数初始化方法,毕竟万一,万一是吧,我的程序不能在ROS下运行怎么办对不对。反正我不管我不管,我就是想学。
至于为什么是yaml文件和cpp 可能因为我现在不想学json和python吧。
二、我是怎么对YAML-CPP进行安装的
2.1 安装方法
安装新版的yaml-cpp会造成librviz的 readFile() 读取配置文件时候出错,如果你需要使用qt调用rviz做可视化显示的话,安装需要谨慎
如果已经安装了ROS的话可以跳过这一步
我没有找到使用apt安装的方法,索性使用源码安装的方式也不费事:
首先膜一波github大佬:
https://github.com/jbeder/yaml-cpp
不管是用git clone方法还是直接下载安装包的方法,都到下载的文件的目录里面去,然后就是一套linux安装N连:
mkdir build
cd ./build
cmake ..
make install
不过这里我推荐使用cmake-gui 进行编译选项的配置,使用cmake-gui可以直观地看出有什么选项
cmake-gui不是不本文讨论的话题,有需要google或者百度吧
cmake-gui下我的编译选择如下,没有必要和我一样,按照自己实际需求来。
三、YAML-CPP的基本使用方法
假设一个简单的场景,我的程序有以下基本功能:
- 从一个名为init.yaml的文件中读取入程序需要的初始化信息
- 并将程序运行过程中产生的数据保存到另一个名为save.yaml的文件中
3.1 Cmake中的配置
首先是最基本的CMakeLists.txt文件的配置,对下述三条语句经过调整后加入CMakeLists.txt文件的合适位置
find_package(yaml-cpp REQUIRED)
include_directories(${YAML_CPP_INCLUDE_DIR})
target_link_libraries(your_target_name yaml-cpp)
3.2 创建init.yaml文件
#init.yaml
Node_1:
name: robot_node_1
rate: 100
Node_2:
name: robot_node_2
rate: 1
3.3 读入yaml配置文件
/* yaml_cpp.cpp */
#include <yaml-cpp/yaml.h> //需要引入该头文件
/* 读取yaml文件示例代码 */
YAML::Node config;
try
{
config = YAML::LoadFile("pathToYourFile/init.yaml");
}
catch (YAML::BadFile &e)
{
std::cerr << e.what() << '\n';
ROS_ERROR("找不到yaml配置文件");
abort();
}
至此,配置文件init.yaml文件中的内容被读入对象config中,之后我们只需要对config对象进行操作即可获得init.yaml文件中的信息。同时这里使用try-catch块对错误进行捕获,以定位程序运行找不到yaml文件的错误。
那么如何将config中的数据读取出来呢?
/* yaml_cpp.cpp */
/* 读取yaml文件示例代码 */
std::string node_1_name = config["Node_1"]["name"].as<std::string>();
int node_1_rate = config["Node_1"]["rate"].as<int>();
std::string node_2_name = config["Node_2"]["name"].as<std::string>();
int node_2_rate = config["Node_2"]["rate"].as<int>();
cout << "node_1 name: " << node_1_name << endl;
cout << "node_1 rate: " << node_1_rate << endl;
cout << "node_2 name: " << node_2_name << endl;
cout << "node_2 rate: " << node_2_rate << endl;
我的理解是config文件中维护里一个类似与树的数据结构,以init.yaml文件为例,Node_1和Node_2为一个第一层深度的子节点(node),两个子节点又分别维护了名为"name"和"rate"的两个子-子节点,通过config对象的"[]"方法来访问节点,想要获取Node_2的rate数据, 使用下述语句即可将该数据读取到目标变量中:
int node_2_rate = config["Node_2"]["rate"].as<int>();
至此已经能够将init.yaml文件中的数据读取到程序中了,程序的第一个功能完成。
当然为了保证储蓄的鲁棒性,可以使用
if(config["Node_2"]["rate"])
来判断Node_2的rate节点是否在init.yaml文件中进行了定义,如果有定义后再对节点的数据进行读取
3.4写配置文件save.yaml
如果程序运行过程中产生的一些数据我们需要将其保存到一个新的yaml文件中,那么我们需要实现生成yaml文件的功能,有两种方法来生成一个新的yaml文件
3.4.1 使用YAML::Node的方式
/* yaml_cpp.cpp */
#include <fstream> //需要引入该头文件
/* 写yaml文件 */
std::ofstream fout2("save.yaml");
YAML::Node config2;
config2["Robot_1"]["ip"] = "192.168.0.12";
config2["Robot_1"]["port"] = "11311";
fout2 << config2 << endl;
fout2.close();
上述代码比较直观,不再赘述。
3.4.2 使用YAML::Emitter的方式
/* yaml_cpp.cpp */
#include <fstream> //需要引入该头文件
/* 写yaml文件 */
std::ofstream fout("save.yaml");
YAML::Emitter emitter;
emitter << YAML::BeginMap; //建立一个类型为map的节点
emitter << YAML::Key << "Robot_1"; //map的键为:"Robot_1"
emitter << YAML::BeginMap; //嵌套生成一个新的子map
emitter << YAML::Key << "ip" << YAML::Value << "192.168.0.12" ;
emitter << YAML::Key << "port" << YAML::Value << "11311" ;
emitter << YAML::EndMap; //结束嵌套的子map
emitter << YAML::EndMap; //结束根map
fout << emitter.c_str() << endl;
fout.close();
在新版的api中使用了YAML::Emitter类生成一个yaml树,其内部通过状态机的方式控制生成的节点的内容,最后通过emitter的c_str()方法生成了输出到输出文件流中生成save.yaml文件,上述代码生成的文件内容均如下:
#save.yaml
Robot_1:
ip: 192.168.0.12
port: 11311
至此,关于yaml文件的基本读写方法的关键代码就已经完成了。
四、YAML-CPP的一些其他使用方法
4.1 yaml与stl容器的转换
以std::vector为例 yaml文件中节点类型为序列(Sequence)时:
#stl.yaml
Robot:
position: [1.2, 2.6, 3.5, 1.0]
想要将position节点中的数据放入一个vector中,yaml-cpp内置了序列式节点和std::vector的转换:
YAML::Node stl_config;
stl_config = YAML::LoadFile("./stl.yaml");
//与std vector转换
std::vector<double> vec;
vec = stl_config["Robot"]["position"].as<std::vector<double> >();
for (size_t i = 0; i < vec.size(); i++)
{
cout << vec[i] << endl;
}
此时[1.2, 2.6, 3.5, 1.0]已经被保存到对象vec中
将一个已有的vec保存到stl_config中操作类似, 可以直接作为节点的右值出现
stl_config["Robot"]["new_poisition"] = vec;
4.2 与自定义数据结构的转换
做机器人的程序初始化时,需要将yaml文件中一些矩阵的初始值导入Eigen格式的矩阵中,使用以下代码可以实现yaml旋转矩阵到Eigen::Matrix3f的读取
T_RM:
[1.0, 0.0 ,0.0,
0.0, 1.0, 0.0,
0.0, 0.0, 1.0]
/* yaml_cpp.cpp */
namespace YAML
{
template <>
struct convert<Eigen::Matrix3f>
{
static Node encode(const Eigen::Matrix3f &rhs)
{
Node node;
node.push_back(rhs(0,0));node.push_back(rhs(0,1));node.push_back(rhs(0,2));
node.push_back(rhs(1,0));node.push_back(rhs(1,1));node.push_back(rhs(1,2));
node.push_back(rhs(2,0));node.push_back(rhs(2,1));node.push_back(rhs(2,2));
return node;
}
static bool decode(const Node &node, Eigen::Matrix3f &rhs)
{
if (!node.IsSequence() || node.size() != 9)
{
return false;
}
rhs(0,0) = node[0].as<float>(); rhs(0,1) = node[1].as<float>(); rhs(0,2) = node[2].as<float>();
rhs(1,0) = node[3].as<float>(); rhs(1,1) = node[4].as<float>(); rhs(1,2) = node[5].as<float>();
rhs(2,0) = node[6].as<float>(); rhs(2,1) = node[7].as<float>(); rhs(2,2) = node[8].as<float>();
return true;
}
};
}
最后和基础数据类型一样进行转换就可以了
Eigen::Matrix3f t_rm;
t_rm = config["T_RM"].as<Eigen::Matrix3f>();
至此yaml中的T_RM中的数据已经转换到了Eigen::Matrix3f类型的t_rm中;
参考文献
https://blog.csdn.net/weixin_37835423/article/details/87736308