ROS2参数编写指南

 

如何安全可靠优雅的解析ROS2的参数!

ROS2参数编写指南

本文主要感谢cxwl的sgf同事提供的参考资料1,里面详细描述了应该如何编写ROS的参数,为了减少跳转次数(有兴趣的读者可以自行参考资料1获得详细的教程),将其中描述的规范总结如下:

  1. 使用vscode的插件YAML (by redhat)来“绑定”json文件和yaml文件,实现使用json文件作为schema,校验yaml文件结构的有效性,并实现参数“键”的自动填充
  2. 在json文件中定义schema,作为yaml文件结构的标准定义
  3. 在yaml文件中编写参数,得益于插件的功能,可以快速编写符合要求的参数文件
  4. 编写启动的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"
  }
}
申明注意点:
  1. 路径都是相对于打开的工作区而言。比如你打开的文件夹是 ~/ros2_ws, 那么json文件的绝对路径就是 ~/ros2_ws/src/avm_psd/schema/psd_params_200w.schema.json
  2. 更多的“链接” 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"]
}
补充说明:
  1. 只有object才可以嵌套更多的参数
  2. 支持的基本类型可以看资料7
  3. 在例子中的 “$ref”: “#/definitions/avm_psd_node” 用法可以减少嵌套,方便阅读。首先需要在definitions字段定义 avm_psd_nodefoxglove_translator_node 这两个对象才行
  4. 其他更高级的用法不在本文的讨论范围,欢迎发邮件交流

定义param.yaml

在已经编写好schema.json的情况下,此时编写yaml文件是十分方便的。

比如上面给出的例子,最顶层的“键”应该是avm_psd,在插件的强大辅助下,只需要键入avm就会自动补全,并且将所有的键都列出来,我们只需要填写对应的值即可,而且有的键还会自动填充对应的值。

总的来说有几个好处:
  1. 自动填充键,只需我们填充值即可
  2. 如果缺失了什么requied键会报错
  3. 如果类型不匹配也会报错
有几点需要注意:
  1. 对于浮点类型,如果值恰好等于整数,可能会自动填一个整数,需要手动补上 .0,否则ros在解析的时候会报错,提示你传递整型给了double类型。
  2. 如果没有自动跳出来键,说明你的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);
几点说明:
  1. 对于数字类型,ROS2只支持 int64_t 和 double,因此模板参数填写int float 等时,实际是有发生隐式的类型转换的。
  2. 基于上面所述,如果在解析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

参考资料

  1. ROS Node Parameter Coding Guidelines
  2. 一个schema.json示例
  3. 一个param.yaml示例
  4. 一个launch.py示例
  5. 带namespace的多节点yaml示例
  6. yaml文件中通过变量引用来设置键对应的值
  7. json的类型关键字