如何安全可靠优雅的解析ROS2的参数!
ROS2参数编写指南
本文主要感谢cxwl的sgf同事提供的参考资料1,里面详细描述了应该如何编写ROS的参数,为了减少跳转次数(有兴趣的读者可以自行参考资料1获得详细的教程),将其中描述的规范总结如下:
- 使用vscode的插件YAML (by redhat)来“绑定”json文件和yaml文件,实现使用json文件作为schema,校验yaml文件结构的有效性,并实现参数“键”的自动填充
- 在json文件中定义schema,作为yaml文件结构的标准定义
- 在yaml文件中编写参数,得益于插件的功能,可以快速编写符合要求的参数文件
- 编写启动的launch.py文件
安装YAML插件并配置
在vscode插件市场安装yaml插件就不赘述了
为了不污染用户/远程环境,笔者选择在工作区进行设置。
打开vscode的设置界面,搜索 yaml.schemas
,注意选择工作区进行修改。之后就会自动打开或创建${workspacefolder}/.vscode/settings.json
文件
加上类似于下面这样的配置:
{
"yaml.schemas": {
"src/avm_psd/schema/psd_params_200w.schema.json": "src/avm_psd/config/psd_200w.param.yaml"
}
}
- 路径都是相对于打开的工作区而言。比如你打开的文件夹是
~/ros2_ws
, 那么json文件的绝对路径就是~/ros2_ws/src/avm_psd/schema/psd_params_200w.schema.json
- 更多的“链接” json的yaml的方法可以直接参看插件的页面
定义schema.json
这个文件是便于我们快速生成yaml文件的关键,如schema一词所指,它是我们yaml参数文件的一个蓝本
可以直接参考资料2中给出的例子,笔者对于这个也是初上手,所以测试下来写了一个如下的简单例子:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"avm_psd": {
"type": "object",
"properties": {
"avm_psd_node": {
"$ref": "#/definitions/avm_psd_node"
},
"foxglove_translator_node": {
"$ref": "#/definitions/foxglove_translator_node"
}
},
"required": ["avm_psd_node", "foxglove_translator_node"]
}
},
"required": ["avm_psd"],
"definitions": {
...
}
}
其实本质上就是几种基本类型的复杂嵌套,因此只给出两层结构改如何编写:
"param_name": {
"type": "object",
"properties": {
"nested_param_name": {
"type": "string",
"default": "a simple demo",
"description": "the description of the nested parameter."
},
"another_nested_param_name": {
...
}
},
"required": ["nested_param_name", "another_nested_param_name"]
}
- 只有object才可以嵌套更多的参数
- 支持的基本类型可以看资料7
- 在例子中的 “$ref”: “#/definitions/avm_psd_node” 用法可以减少嵌套,方便阅读。首先需要在definitions字段定义
avm_psd_node
和foxglove_translator_node
这两个对象才行 - 其他更高级的用法不在本文的讨论范围,欢迎发邮件交流
定义param.yaml
在已经编写好schema.json的情况下,此时编写yaml文件是十分方便的。
比如上面给出的例子,最顶层的“键”应该是avm_psd,在插件的强大辅助下,只需要键入avm就会自动补全,并且将所有的键都列出来,我们只需要填写对应的值即可,而且有的键还会自动填充对应的值。
总的来说有几个好处:- 自动填充键,只需我们填充值即可
- 如果缺失了什么requied键会报错
- 如果类型不匹配也会报错
- 对于浮点类型,如果值恰好等于整数,可能会自动填一个整数,需要手动补上
.0
,否则ros在解析的时候会报错,提示你传递整型给了double类型。 - 如果没有自动跳出来键,说明你的yaml配置没有配对。
编写launch文件
下面给出笔者的例子,也可以直接看资料4中的例子
import os
from ament_index_python.packages import get_package_share_directory
from launch import LaunchDescription
from launch.substitutions import LaunchConfiguration
from launch.actions import DeclareLaunchArgument
from launch_ros.actions import Node
import yaml
def generate_launch_description():
params_file_path = os.path.join(
get_package_share_directory('avm_psd'),
"config/psd_200w.param.yaml"
)
b_file_path = os.path.join(
get_package_share_directory('avm_psd'),
"models/e64fb24b_psd_extracted_noquant_nominmax.bin"
)
p_file_path = os.path.join(
get_package_share_directory('avm_psd'),
"models/e64fb24b_psd_extracted_noquant_nominmax.param"
)
lookuptable_file_path = os.path.join(
get_package_share_directory('avm_psd'),
"config/joint_model_psd_4fisheye_lookuptable.bin"
)
with open(params_file_path, 'r') as f:
node_params = yaml.safe_load(f)['avm_psd']
avm_psd_node_params = node_params['avm_psd_node']['ros__parameters']
avm_psd_node_params['b_file'] = b_file_path
avm_psd_node_params['p_file'] = p_file_path
avm_psd_node_params['lookuptable_file'] = lookuptable_file_path
foxglove_translator_node_params = node_params['foxglove_translator_node']['ros__parameters']
arguments = [
DeclareLaunchArgument("input/image", default_value="/fish4/yuv200w"),
DeclareLaunchArgument("output/objects", default_value="/rviz/avm_psd/psd"),
DeclareLaunchArgument("output/viz", default_value="/foxglove/avm_psd/psd")
]
avm_psd_node_runner = Node(
package='avm_psd',
executable='avm_psd_node',
namespace='avm_psd',
output='screen',
remappings=[
("psd_sub_name", LaunchConfiguration("input/image")),
("psd_pub_name", LaunchConfiguration("output/objects")),
],
parameters=[avm_psd_node_params],
)
foxglove_translator_node_runner = Node(
package='avm_psd',
executable='avm_foxglove_translator',
namespace='avm_psd',
output='screen',
remappings=[
("sub_name", LaunchConfiguration("output/objects")),
("pub_name", LaunchConfiguration("output/viz")),
],
parameters=[foxglove_translator_node_params],
)
return LaunchDescription(
arguments +
[avm_psd_node_runner, foxglove_translator_node_runner]
)
在cpp中使用参数
/**
* declare 中没有给定默认值,这样如果参数不存在就会报错。
*/
this->declare_parameter<bool>("open_time_consumer");
this->get_parameter("open_time_consumer", m_avm_psd_params_->open_time_consumer);
- 对于数字类型,ROS2只支持 int64_t 和 double,因此模板参数填写int float 等时,实际是有发生隐式的类型转换的。
- 基于上面所述,如果在解析array类型的时候,最终参数的类型一定要是
std::vector<int64_t>
或者std::vector<double>
,因为此时无法隐式转换。
说了再多不如给出实际的例子,因此将读者研究后的代码片段放上来
schema.json
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"avm_psd": {
"type": "object",
"properties": {
"avm_psd_node": {
"$ref": "#/definitions/avm_psd_node"
},
"foxglove_translator_node": {
"$ref": "#/definitions/foxglove_translator_node"
}
},
"required": ["avm_psd_node", "foxglove_translator_node"]
}
},
"required": ["avm_psd"],
"definitions": {
"avm_psd_node": {
"type": "object",
"properties": {
"ros__parameters": {
"type": "object",
"properties": {
"open_time_consumer": {
"type": "boolean",
"default": false,
"description": "Count the time taken for the last 100 times and print it."
},
"psd_sub_name": {
"type": "string",
"default": "/fish4/yuv200w",
"description": "The topic name of msg which need to be preprocessed."
},
"origin_height": {
"type": "integer",
"default": 1080,
"description": "The height of origin input image."
},
"origin_width": {
"type": "integer",
"default": 1920,
"description": "The width of origin input image."
},
"height": {
"type": "integer",
"default": 640,
"description": "The height of the resized image."
},
"width": {
"type": "integer",
"default": 512,
"description": "The width of the resized image."
},
"channels": {
"type": "integer",
"default": 3,
"description": "The channels of the resized image."
},
"padding_top": {
"type": "integer",
"default": 0,
"description": "The num of pixes to be padded on the top of the resized image."
},
"padding_bottom": {
"type": "integer",
"default": 0,
"description": "The num of pixes to be padded on the bottom of the resized image."
},
"padding_left": {
"type": "integer",
"default": 0,
"description": "The num of pixes to be padded on the left of the resized image."
},
"padding_right": {
"type": "integer",
"default": 0,
"description": "The num of pixes to be padded on the right of the resized image."
},
"npu_id": {
"type": "integer",
"minimum": 0,
"maximum": 3,
"default": 0,
"description": "The core id of the npu."
},
"num_threads": {
"type": "integer",
"minimum": 1,
"maximum": 8,
"default": 4,
"description": "The num of threads to be used when infer by npu."
},
"out_dims": {
"type": "array",
"items": {
"type": "integer"
},
"minItems": 4,
"maxItems": 4,
"default": [1, 80, 64, 60],
"description": "The output dims <B, H, W, C> of the model."
},
"upscale_factor": {
"type": "integer",
"default": 2,
"description": "The upscale factor of the channels to space operator in model"
},
"center_threshold": {
"type": "number",
"minimum": 0.0,
"maximum": 1.0,
"default": 0.5,
"description": "The threshold of the confidence channel, which is used to filter the feature map. It will be arcsigmoided when use."
},
"center_nms_distance": {
"type": "number",
"default": 5.0,
"description": "The distance to be used in nms when filter the overlapped center 2D box."
},
"occupied_threshold": {
"type": "number",
"minimum": 0.0,
"maximum": 1.0,
"default": 0.5,
"description": "The threshold of the occupied channel, which is used to filter the feature map. It will be arcsigmoided when use."
},
"corner_threshold": {
"type": "number",
"minimum": 0.0,
"maximum": 1.0,
"default": 0.5,
"description": "The threshold of the corner confidence channel, which is used to filter the feature map. It will be arcsigmoided when use."
},
"corner_nms_distance": {
"type": "number",
"default": 3.0,
"description": "The distance to be used in nms when filter the overlapped corner 2D box."
},
"psd_pub_name": {
"type": "string",
"default": "/rviz/avm_psd/psd",
"description": "The topic name of msg which need to be published."
},
"frame_id": {
"type": "string",
"default": "world",
"description": "The frame id of the msg which need to be published."
},
"scale": {
"type": "number",
"default": 4.0,
"description": "The scale that the results need to be enlarged to display on a 1920*1080 image."
}
},
"required": ["open_time_consumer",
"psd_sub_name",
"origin_height", "origin_width",
"height", "width", "channels",
"padding_top", "padding_bottom", "padding_left", "padding_right",
"npu_id", "num_threads",
"out_dims",
"upscale_factor",
"center_threshold", "center_nms_distance",
"occupied_threshold",
"corner_threshold", "corner_nms_distance",
"psd_pub_name", "frame_id", "scale"
]
}
},
"required": ["ros__parameters"]
},
"foxglove_translator_node": {
"type": "object",
"properties": {
"ros__parameters": {
"type": "object",
"properties": {
"sub_name": {
"type":"string",
"default": "/rviz/avm_psd/psd",
"description": "The topic name of msg which published by avm_psd_node, which need to be translated to visualized in foxglove studio."
},
"pub_name": {
"type": "string",
"default": "/foxglove/avm_psd/psd",
"description": "The topic name of the translated msg which need to be published."
},
"scale": {
"type": "number",
"default": 1.0,
"description": "The result which published by avm_psd_node is enlarged to display on a 1920*1080 image. This scale is used to scale the result to the whatever resolution you want."
},
"duration_nsec": {
"type": "integer",
"minimum": 10000000,
"maximum": 1000000000,
"default": 100000000,
"description": "The duration of the msg which published by avm_psd_node."
},
"points_annotation_type": {
"type": "integer",
"minimum": 0,
"maximum": 4,
"default": 2,
"description": "The type of the points annotation, which defined by foxglove team. 0: UNKNOWN, 1: POINTS, 2: LINE_LOOP, 3: LINE_STRIP, 4: LINE_LIST."
},
"thickness": {
"type": "number",
"minimum": 1.0,
"default": 5.0,
"description": "The thickness of the points annotation."
},
"outline_color": {
"type": "array",
"items": {
"type": "number",
"minimum": 0.0,
"maximum": 1.0
},
"minItems": 4,
"maxItems": 4,
"default": [1.0, 0.0, 0.0, 1.0],
"description": "The outline color of the points annotation. [r, g, b, a]"
},
"fill_color": {
"type": "array",
"items": {
"type": "number",
"minimum": 0.0,
"maximum": 1.0
},
"minItems": 4,
"maxItems": 4,
"default": [0.0, 0.0, 0.0, 0.0],
"description": "The fill color of the points annotation. [r, g, b, a]"
}
},
"required": ["sub_name",
"pub_name",
"scale",
"duration_nsec",
"points_annotation_type", "thickness", "outline_color", "fill_color"]
}
},
"required": ["ros__parameters"]
}
}
}
param.yaml
# Copyright 2023 Arm Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
avm_psd:
avm_psd_node:
ros__parameters:
open_time_consumer: true
psd_sub_name: /fish4/yuv200w
origin_height: 1080
origin_width: 1920
height: 640
width: 512
channels: 3
padding_top: 0
padding_bottom: 0
padding_left: 0
padding_right: 0
npu_id: 0
num_threads: 4
out_dims:
- 1
- 80
- 64
- 60
upscale_factor: 2
center_threshold: 0.5
center_nms_distance: 5.0
occupied_threshold: 0.5
corner_threshold: 0.5
corner_nms_distance: 3.0
psd_pub_name: /rviz/avm_psd/psd
frame_id: world
scale: 4.0
foxglove_translator_node:
ros__parameters:
sub_name: /rviz/avm_psd/psd
pub_name: /foxglove/avm_psd/psd
scale: 1.0
duration_nsec: 100000000
points_annotation_type: 2
thickness: 5.0
outline_color:
- 1.0
- 0.0
- 0.0
- 1.0
fill_color:
- 0.0
- 0.0
- 0.0
- 0.0
luanch.py
上面已经放过了
cpp解析参数代码片
void AvmPsdNode::readParams() {
this->declare_parameter<bool>("open_time_consumer");
this->declare_parameter<std::string>("psd_sub_name");
this->declare_parameter<int>("origin_height");
this->declare_parameter<int>("origin_width");
this->declare_parameter<int>("height");
this->declare_parameter<int>("width");
this->declare_parameter<int>("channels");
this->declare_parameter<int>("padding_top");
this->declare_parameter<int>("padding_bottom");
this->declare_parameter<int>("padding_left");
this->declare_parameter<int>("padding_right");
this->declare_parameter<int>("npu_id");
this->declare_parameter<int>("num_threads");
this->declare_parameter<std::string>("p_file");
this->declare_parameter<std::string>("b_file");
this->declare_parameter<std::vector<int>>("out_dims");
this->declare_parameter<int>("upscale_factor");
this->declare_parameter<float>("center_threshold");
this->declare_parameter<float>("center_nms_distance");
this->declare_parameter<float>("occupied_threshold");
this->declare_parameter<float>("corner_threshold");
this->declare_parameter<float>("corner_nms_distance");
this->declare_parameter<std::string>("psd_pub_name");
this->declare_parameter<std::string>("frame_id");
this->declare_parameter<float>("scale");
this->get_parameter("open_time_consumer", m_avm_psd_params_->open_time_consumer);
this->get_parameter("psd_sub_name", m_avm_psd_params_->psd_sub_name);
this->get_parameter("origin_height", m_avm_psd_params_->preprocess_params.origin_height);
this->get_parameter("origin_width", m_avm_psd_params_->preprocess_params.origin_width);
this->get_parameter("height", m_avm_psd_params_->preprocess_params.height);
this->get_parameter("width", m_avm_psd_params_->preprocess_params.width);
this->get_parameter("channels", m_avm_psd_params_->preprocess_params.channels);
this->get_parameter("padding_top", m_avm_psd_params_->preprocess_params.padding_top);
this->get_parameter("padding_bottom", m_avm_psd_params_->preprocess_params.padding_bottom);
this->get_parameter("padding_left", m_avm_psd_params_->preprocess_params.padding_left);
this->get_parameter("padding_right", m_avm_psd_params_->preprocess_params.padding_right);
this->get_parameter("npu_id", m_avm_psd_params_->net_params.net_options.npu_id);
this->get_parameter("num_threads", m_avm_psd_params_->net_params.net_options.num_threads);
this->get_parameter("p_file", m_avm_psd_params_->net_params.p_file);
this->get_parameter("b_file", m_avm_psd_params_->net_params.b_file);
std::vector<int64_t> out_dims_vec;
this->get_parameter("out_dims", out_dims_vec);
memcpy((void *)&(m_avm_psd_params_->post_params.out_dims), (void *)out_dims_vec.data(), sizeof(int64_t) * out_dims_vec.size());
this->get_parameter("upscale_factor", m_avm_psd_params_->post_params.upscale_factor);
this->get_parameter("center_threshold", m_avm_psd_params_->post_params.center_threshold);
this->get_parameter("center_nms_distance", m_avm_psd_params_->post_params.center_nms_distance);
this->get_parameter("occupied_threshold", m_avm_psd_params_->post_params.occupied_threshold);
this->get_parameter("corner_threshold", m_avm_psd_params_->post_params.corner_threshold);
this->get_parameter("corner_nms_distance", m_avm_psd_params_->post_params.corner_nms_distance);
this->get_parameter("psd_pub_name", m_avm_psd_params_->pub_params.psd_pub_name);
this->get_parameter("frame_id", m_avm_psd_params_->pub_params.frame_id);
this->get_parameter("scale", m_avm_psd_params_->pub_params.scale);
}
void FoxgloveTranslator::readParams() {
this->declare_parameter<std::string>("sub_name");
this->declare_parameter<std::string>("pub_name");
this->declare_parameter<double>("scale");
this->declare_parameter<int64_t>("duration_nsec");
this->declare_parameter<int64_t>("points_annotation_type");
this->declare_parameter<double>("thickness");
this->declare_parameter<std::vector<double>>("outline_color");
this->declare_parameter<std::vector<double>>("fill_color");
this->get_parameter("sub_name", m_foxg_trans_params_->sub_name);
this->get_parameter("pub_name", m_foxg_trans_params_->pub_name);
this->get_parameter("scale", m_foxg_trans_params_->psd_params.scale);
this->get_parameter("duration_nsec", m_foxg_trans_params_->psd_params.framePoints.duration_nsec);
this->get_parameter("points_annotation_type", m_foxg_trans_params_->psd_params.framePoints.points_annotation_type);
this->get_parameter("thickness", m_foxg_trans_params_->psd_params.framePoints.thickness);
std::vector<double> outline_color_vec;
this->get_parameter("outline_color", outline_color_vec);
m_foxg_trans_params_->psd_params.framePoints.outline_color_r = outline_color_vec[0];
m_foxg_trans_params_->psd_params.framePoints.outline_color_g = outline_color_vec[1];
m_foxg_trans_params_->psd_params.framePoints.outline_color_b = outline_color_vec[2];
m_foxg_trans_params_->psd_params.framePoints.outline_color_a = outline_color_vec[3];
std::vector<double> fill_color_vec;
this->get_parameter("fill_color", fill_color_vec);
m_foxg_trans_params_->psd_params.framePoints.fill_color_r = fill_color_vec[0];
m_foxg_trans_params_->psd_params.framePoints.fill_color_g = fill_color_vec[1];
m_foxg_trans_params_->psd_params.framePoints.fill_color_b = fill_color_vec[2];
m_foxg_trans_params_->psd_params.framePoints.fill_color_a = fill_color_vec[3];
}
文件结构
.
├── config
│ ├── psd_200w.param.yaml
├── launch
│ └── avm_psd_foxg_200w.launch.py
├── schema
│ └── psd_params_200w.schema.json