【ROS2】演示:为有损网络使用服务质量设置

 目录

  •  背景

  •  先决条件

  •  运行演示

    •  命令行选项

    •  添加网络流量

 背景 

请阅读有关 QoS 设置的文档页面,以获取有关 ROS 2 中可用支持的背景信息。

在这个演示中,我们将生成一个发布相机图像的节点和另一个订阅图像并在屏幕上显示图像的节点。然后,我们将模拟它们之间的丢包网络连接,并展示不同的服务质量设置如何处理不良链接

 先决条件

本教程假设您已安装并运行 ROS 2 和 OpenCV。有关安装说明,请参阅 OpenCV 文档。您还需要 ROS 包 image_tools 。

cxy@cxy-Ubuntu2404:~$ sudo apt-get install ros-jazzy-image-tools
[sudo] password for cxy: 
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
The following NEW packages will be installed:ros-jazzy-image-tools
0 upgraded, 1 newly installed, 0 to remove and 5 not upgraded.
Need to get 185 kB of archives.
After this operation, 1,052 kB of additional disk space will be used.
Get:1 http://packages.ros.org/ros2/ubuntu noble/main amd64 ros-jazzy-image-tools amd64 0.33.4-1noble.20240703.161128 [185 kB]
Fetched 185 kB in 4s (46.2 kB/s)                
Selecting previously unselected package ros-jazzy-image-tools.
(Reading database ... 270207 files and directories currently installed.)
Preparing to unpack .../ros-jazzy-image-tools_0.33.4-1noble.20240703.161128_amd64.deb ...
Unpacking ros-jazzy-image-tools (0.33.4-1noble.20240703.161128) ...
Setting up ros-jazzy-image-tools (0.33.4-1noble.20240703.161128) ...

源码安装:

git clone https://github.com/ros2/demos.git -b jazzy

运行演示 

在运行演示之前,请确保您的计算机已连接一个可用的网络摄像头。

一旦你安装了 ROS 2,请源化你的设置文件:

source ~/.bashrc

 然后运行:

cxy@cxy-Ubuntu2404:~$ ros2 run image_tools showimage
[INFO] [1721611318.045341212] [showimage]: Subscribing to topic 'image'
[INFO] [1721611322.141314647] [showimage]: Received image #camera_frame
Received image #camera_frame
[INFO] [1721611323.644344772] [showimage]: Received image #camera_frame
Received image #camera_frame

还没有任何事情发生。 showimage 是一个订阅节点,正在等待 image 主题的发布者。

注意:您必须稍后使用 Ctrl-C 关闭 showimage 进程。您不能仅仅关闭窗口。

在一个单独的终端中,源安装文件并运行发布者节点:

cxy@cxy-Ubuntu2404:~$ ros2 run image_tools cam2image
[ WARN:0@1.283] global ./modules/videoio/src/cap_gstreamer.cpp (1405) open OpenCV | GStreamer warning: Cannot query video position: status=0, value=-1, duration=-1
[INFO] [1721611322.128570743] [cam2image]: Publishing image #1
[INFO] [1721611322.161307078] [cam2image]: Publishing image #2

24010e9ea45b894544179632ebb74087.png

这将从您的网络摄像头发布图像。如果您的计算机没有连接摄像头,可以使用命令行选项发布预定义的图像。

ros2 run image_tools cam2image --ros-args -p burger_mode:=True

230ad898567b99d6edbb643dacaba7e3.png

在此窗口中,您将看到终端输出:

[INFO] [1715662452.055277255] [cam2image]: Publishing image #1
[INFO] [1715662452.119336061] [cam2image]: Publishing image #2
[INFO] [1715662452.187315139] [cam2image]: Publishing image #3
...

将弹出一个标题为“视图”的窗口,显示您的摄像头画面。在第一个窗口中,您将看到来自订阅者的输出:

[INFO] [1715662452.188906764] [showimage]: Received image #camera_frame
Received image #camera_frame
[INFO] [1715662452.252836919] [showimage]: Received image #camera_frame
Received image #camera_frame
[INFO] [1715662452.320878578] [showimage]: Received image #camera_frame
Received image #camera_frame
...

 注意

macOS 用户:如果这些示例不起作用或您收到类似 ddsi_conn_write failed -1 的错误,则需要增加系统范围的 UDP 数据包大小

$ sudo sysctl -w net.inet.udp.recvspace=209715
$ sudo sysctl -w net.inet.udp.maxdgram=65500
这些更改在重启后不会保留。如果您希望更改保留,请将这些行添加到 /etc/sysctl.conf (如果文件尚不存在,请创建该文件):
net.inet.udp.recvspace=209715
net.inet.udp.maxdgram=65500

命令行选项

在你的一个终端中,向原始命令添加一个 -h 标志:

cxy@cxy-Ubuntu2404:~$ ros2 run image_tools showimage -h
Usage: showimage [-h] [--ros-args [-p param:=value] ...]
Subscribe to an image topic and show the images.
Example: ros2 run image_tools showimage --ros-args -p reliability:=best_effortOptions:-h, --help  Display this help message and exitParameters:reliability  Reliability QoS setting. Either 'reliable' (default) or 'best_effort'history  History QoS setting. Either 'keep_last' (default) or 'keep_all'.If 'keep_last', then up to N samples are stored where N is the depthdepth    Depth of the publisher queue. Only honored if history QoS is 'keep_last'. Default value is 10show_image  Show the image. Either 'true' (default) or 'false'window_name  Name of the display window. Default value is the topic name
ros2 run image_tools showimage -h
用法: showimage [-h] [--ros-args [-p param:=value] ...]
订阅一个图像主题并显示图像。
示例: ros2 run image_tools showimage --ros-args -p reliability:=best_effort选项:-h, --help  显示此帮助信息并退出参数:reliability  可靠性QoS设置。可以是'reliable'(默认)或'best_effort'history  历史QoS设置。可以是'keep_last'(默认)或'keep_all'。如果是'keep_last',则最多存储N个样本,其中N是深度depth    发布者队列的深度。仅在历史QoS为'keep_last'时有效。默认值为10show_image  显示图像。可以是'true'(默认)或'false'window_name  显示窗口的名称。默认值是主题名称

添加网络流量

 警告

本演示的这一部分在 RTI 的 Connext DDS 和 Fast-DDS 上无法运行。当在同一主机上运行多个节点时,这些 DDS 实现使用共享内存和回环接口。降低回环接口的吞吐量不会影响共享内存,因此两个节点之间的流量不会受到影响。

 注意

下一节是特定于 Linux 的。

然而,对于 macOS 和 Windows,你可以使用“Network Link Conditioner”(xcode 工具套件的一部分)和“Clumsy”(http://jagt.github.io/clumsy/index.html)等工具来实现类似的效果,但它们不会在本教程中介绍。

我们将使用 Linux 网络流量控制工具, tc (http://linux.die.net/man/8/tc)。

sudo tc qdisc add dev lo root netem loss 5%

c73b6f7ed0306ba339bb5c020947c19e.png

这个神奇的咒语将模拟本地回环设备上的 5%数据包丢失。如果您使用更高分辨率的图像(例如 --ros-args -p width:=640 -p height:=480 ),您可能需要尝试较低的数据包丢失率(例如 1% )。

ab7721450c0a72a4c3be3646d52d36de.png

接下来我们启动 cam2image 和 showimage ,我们很快会注意到两个程序似乎都减慢了图像传输的速度。这是由默认 QoS 设置的行为引起的。在有损信道上强制可靠性意味着发布者(在本例中为 cam2image )将重新发送网络数据包,直到收到消费者(即 showimage )的确认。

现在让我们尝试运行两个程序,但使用更合适的设置。首先,我们将使用 -p reliability:=best_effort 选项来启用尽力而为的通信。发布者现在只会尝试传递网络数据包,并且不期望消费者的确认。我们现在看到 showimage 端的一些帧被丢弃了,所以在运行 showimage 的 shell 中的帧号将不再是连续的。

ros2 run image_tools cam2image --ros-args -p reliability:=best_effortros2 run image_tools showimage --ros-args --param reliability:=best_effort

完成后,请记得删除排队规则

sudo tc qdisc delete dev lo root netem loss 5%

68fcbdb7693ab69a615ada9ea5e79540.png

附录:

45f016740e7e70a00616ef2061cbb2cc.png

cxy@cxy-Ubuntu2404:~/second_ros2_ws/src/demos/image_tools$ tree
.
├── CHANGELOG.rst
├── CMakeLists.txt
├── doc
│   └── qos-best-effort.png
├── Doxyfile
├── img
│   └── result.png
├── include
│   └── image_tools
│       ├── cv_mat_sensor_msgs_image_type_adapter.hpp
│       └── visibility_control.h
├── package.xml
├── README.md
├── src
│   ├── burger.cpp
│   ├── burger.hpp
│   ├── cam2image.cpp
│   ├── cv_mat_sensor_msgs_image_type_adapter.cpp
│   ├── policy_maps.hpp
│   └── showimage.cpp
└── test├── cam2image.txt├── showimage.regex└── test_executables_demo.py.in7 directories, 18 files

CMakeLists.txt

cmake_minimum_required(VERSION 3.5)  # 设置CMake的最低版本为3.5project(image_tools)  # 定义项目名称为image_tools# Default to C++17
if(NOT CMAKE_CXX_STANDARD)  # 如果没有设置C++标准set(CMAKE_CXX_STANDARD 17)  # 设置C++标准为C++17set(CMAKE_CXX_STANDARD_REQUIRED ON)  # 强制使用C++17标准
endif()if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")  # 如果使用的是GNU编译器或Clang编译器add_compile_options(-Wall -Wextra -Wpedantic)  # 添加编译选项:显示所有警告、额外警告和严格警告
endif()find_package(ament_cmake REQUIRED)  # 查找ament_cmake包,必需
find_package(rclcpp REQUIRED)  # 查找rclcpp包,必需
find_package(rclcpp_components REQUIRED)  # 查找rclcpp_components包,必需
find_package(sensor_msgs REQUIRED)  # 查找sensor_msgs包,必需
find_package(std_msgs REQUIRED)  # 查找std_msgs包,必需
find_package(OpenCV REQUIRED COMPONENTS core highgui imgcodecs imgproc videoio)  # 查找OpenCV包及其组件add_library(${PROJECT_NAME} SHARED  # 添加共享库src/burger.cppsrc/cam2image.cppsrc/cv_mat_sensor_msgs_image_type_adapter.cppsrc/showimage.cpp
)
target_compile_definitions(${PROJECT_NAME}PRIVATE "IMAGE_TOOLS_BUILDING_DLL")  # 定义编译宏
target_include_directories(${PROJECT_NAME} PUBLIC"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"  # 设置包含目录"$<INSTALL_INTERFACE:include/${PROJECT_NAME}>")
target_link_libraries(${PROJECT_NAME}rclcpp::rclcpp  # 链接rclcpp库${sensor_msgs_TARGETS}  # 链接sensor_msgs库${std_msgs_TARGETS}  # 链接std_msgs库rclcpp_components::component  # 链接rclcpp_components库${OpenCV_LIBS})  # 链接OpenCV库
rclcpp_components_register_node(${PROJECT_NAME} PLUGIN "image_tools::Cam2Image" EXECUTABLE cam2image)  # 注册节点Cam2Image
rclcpp_components_register_node(${PROJECT_NAME} PLUGIN "image_tools::ShowImage" EXECUTABLE showimage)  # 注册节点ShowImageinstall(TARGETS ${PROJECT_NAME} EXPORT ${PROJECT_NAME}  # 安装目标文件ARCHIVE DESTINATION lib  # 安装归档文件到lib目录LIBRARY DESTINATION lib  # 安装库文件到lib目录RUNTIME DESTINATION bin)  # 安装可执行文件到bin目录install(DIRECTORY include/ DESTINATION include/${PROJECT_NAME})  # 安装包含目录# TODO(sloretz) stop exporting old-style CMake variables in the future
ament_export_libraries(${PROJECT_NAME})  # 导出库
ament_export_targets(${PROJECT_NAME})  # 导出目标
ament_export_dependencies(rclcpp_components)  # 导出依赖if(BUILD_TESTING)  # 如果启用了测试find_package(ament_lint_auto REQUIRED)  # 查找ament_lint_auto包,必需ament_lint_auto_find_test_dependencies()  # 查找测试依赖find_package(ament_cmake_pytest REQUIRED)  # 查找ament_cmake_pytest包,必需find_package(launch_testing_ament_cmake REQUIRED)  # 查找launch_testing_ament_cmake包,必需find_package(rmw_implementation_cmake REQUIRED)  # 查找rmw_implementation_cmake包,必需# These are the regex's for validating the output of the executables.set(RCLCPP_DEMO_SHOWIMAGE_EXPECTED_OUTPUT "${CMAKE_CURRENT_SOURCE_DIR}/test/showimage")  # 设置showimage的预期输出set(RCLCPP_DEMO_CAM2IMAGE_EXPECTED_OUTPUT "${CMAKE_CURRENT_SOURCE_DIR}/test/cam2image")  # 设置cam2image的预期输出macro(testing_targets)  # 定义宏testing_targetsset(RCLCPP_DEMO_CAM2IMAGE_EXECUTABLE $<TARGET_FILE:cam2image>)  # 设置cam2image可执行文件set(RCLCPP_DEMO_SHOWIMAGE_EXECUTABLE $<TARGET_FILE:showimage>)  # 设置showimage可执行文件configure_file(test/test_executables_demo.py.intest_showimage_cam2image${target_suffix}.py.genexp@ONLY)  # 配置文件file(GENERATEOUTPUT "${CMAKE_CURRENT_BINARY_DIR}/test_showimage_cam2image${target_suffix}_$<CONFIG>.py"INPUT "${CMAKE_CURRENT_BINARY_DIR}/test_showimage_cam2image${target_suffix}.py.genexp")  # 生成文件add_launch_test("${CMAKE_CURRENT_BINARY_DIR}/test_showimage_cam2image${target_suffix}_$<CONFIG>.py"TARGET test_showimage_cam2image${target_suffix}ENVRCL_ASSERT_RMW_ID_MATCHES=${rmw_implementation}RMW_IMPLEMENTATION=${rmw_implementation}TIMEOUT 30)  # 添加启动测试endmacro()call_for_each_rmw_implementation(testing_targets)  # 为每个rmw实现调用testing_targetsendif()ament_package()  # 定义ament包

package.xml

<!-- 声明XML版本为1.0 -->
<?xml version="1.0"?>
<!-- 声明XML模型的链接和模型类型 -->
<?xml-model href="http://download.ros.org/schema/package_format2.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<!-- 声明包的格式为2 -->
<package format="2"><!-- 声明包的名称为image_tools --><name>image_tools</name><!-- 声明包的版本为0.33.4 --><version>0.33.4</version><!-- 声明包的描述 --><description>Tools to capture and play back images to and from DDS subscriptions and publications.</description><!-- 声明包的维护者 --><maintainer email="aditya.pande@openrobotics.org">Aditya Pande</maintainer><maintainer email="audrow@openrobotics.org">Audrow Nash</maintainer><!-- 声明包的许可证 --><license>Apache License 2.0</license><!-- 声明包的作者 --><author email="dthomas@osrfoundation.org">Dirk Thomas</author><author email="mabel@openrobotics.org">Mabel Zhang</author><!-- 声明构建工具依赖 --><buildtool_depend>ament_cmake</buildtool_depend><!-- 声明构建依赖 --><build_depend>libopencv-dev</build_depend><build_depend>rclcpp</build_depend><build_depend>rclcpp_components</build_depend><build_depend>sensor_msgs</build_depend><build_depend>std_msgs</build_depend><!-- 声明执行依赖 --><exec_depend>libopencv-dev</exec_depend><exec_depend>rclcpp</exec_depend><exec_depend>rclcpp_components</exec_depend><exec_depend>sensor_msgs</exec_depend><exec_depend>std_msgs</exec_depend><!-- 声明测试依赖 --><test_depend>ament_cmake_pytest</test_depend><test_depend>ament_lint_auto</test_depend><test_depend>ament_lint_common</test_depend><test_depend>launch</test_depend><test_depend>launch_ros</test_depend><test_depend>launch_testing</test_depend><test_depend>launch_testing_ament_cmake</test_depend><test_depend>launch_testing_ros</test_depend><test_depend>rmw_implementation_cmake</test_depend><!-- 声明导出信息 --><export><!-- 声明构建类型 --><build_type>ament_cmake</build_type></export>
</package>

showimage.cpp

#include <iostream>  // 包含输入输出流库
#include <sstream>  // 包含字符串流库
#include <string>  // 包含字符串库
#include <vector>  // 包含向量库#include "opencv2/core/mat.hpp"  // 包含OpenCV核心矩阵库
#include "opencv2/highgui.hpp"  // 包含OpenCV高层GUI库
#include "opencv2/imgproc.hpp"  // 包含OpenCV图像处理库#include "rcl_interfaces/msg/parameter_descriptor.hpp"  // 包含参数描述符消息
#include "rclcpp/rclcpp.hpp"  // 包含rclcpp库
#include "rclcpp_components/register_node_macro.hpp"  // 包含注册节点宏
#include "sensor_msgs/msg/image.hpp"  // 包含图像消息#include "image_tools/cv_mat_sensor_msgs_image_type_adapter.hpp"  // 包含自定义类型适配器
#include "image_tools/visibility_control.h"  // 包含可见性控制头文件#include "./policy_maps.hpp"  // 包含策略映射头文件RCLCPP_USING_CUSTOM_TYPE_AS_ROS_MESSAGE_TYPE(image_tools::ROSCvMatContainer,sensor_msgs::msg::Image);  // 使用自定义类型作为ROS消息类型namespace image_tools  // 定义命名空间image_tools
{
class ShowImage : public rclcpp::Node  // 定义ShowImage类,继承自rclcpp::Node
{
public:IMAGE_TOOLS_PUBLICexplicit ShowImage(const rclcpp::NodeOptions & options)  // 显式构造函数: Node("showimage", options)  // 调用基类构造函数,初始化节点名称为"showimage"{setvbuf(stdout, NULL, _IONBF, BUFSIZ);  // 设置标准输出缓冲区// Do not execute if a --help option was providedif (help(options.arguments())) {  // 如果提供了--help选项,则不执行// TODO(jacobperron): Replace with a mechanism for a node to "unload" itself// from a container.exit(0);  // 退出程序}parse_parameters();  // 解析参数initialize();  // 初始化}private:IMAGE_TOOLS_LOCALvoid initialize()  // 初始化函数{// Set quality of service profile based on command line options.auto qos = rclcpp::QoS(rclcpp::QoSInitialization(// The history policy determines how messages are saved until taken by// the reader.// KEEP_ALL saves all messages until they are taken.// KEEP_LAST enforces a limit on the number of messages that are saved,// specified by the "depth" parameter.history_policy_,  // 设置历史策略// Depth represents how many messages to store in history when the// history policy is KEEP_LAST.depth_  // 设置深度));// The reliability policy can be reliable, meaning that the underlying transport layer will try// ensure that every message gets received in order, or best effort, meaning that the transport// makes no guarantees about the order or reliability of delivery.qos.reliability(reliability_policy_);  // 设置可靠性策略auto callback =this {  // 定义回调函数process_image(container, show_image_, this->get_logger());  // 处理图像};RCLCPP_INFO(this->get_logger(), "Subscribing to topic '%s'", topic_.c_str());  // 打印订阅信息sub_ = create_subscription<image_tools::ROSCvMatContainer>(topic_, qos, callback);  // 创建订阅if (window_name_ == "") {  // 如果没有自定义窗口名称// If no custom window name is given, use the topic namewindow_name_ = sub_->get_topic_name();  // 使用主题名称作为窗口名称}}IMAGE_TOOLS_LOCALbool help(const std::vector<std::string> args)  // 帮助函数{if (std::find(args.begin(), args.end(), "--help") != args.end() ||std::find(args.begin(), args.end(), "-h") != args.end())  // 如果提供了--help或-h选项{std::stringstream ss;  // 创建字符串流ss << "Usage: showimage [-h] [--ros-args [-p param:=value] ...]" << std::endl;  // 打印用法ss << "Subscribe to an image topic and show the images." << std::endl;  // 打印描述ss << "Example: ros2 run image_tools showimage --ros-args -p reliability:=best_effort";  // 打印示例ss << std::endl << std::endl;ss << "Options:" << std::endl;  // 打印选项ss << "  -h, --help\tDisplay this help message and exit";  // 打印帮助选项ss << std::endl << std::endl;ss << "Parameters:" << std::endl;  // 打印参数ss << "  reliability\tReliability QoS setting. Either 'reliable' (default) or 'best_effort'";  // 打印可靠性参数ss << std::endl;ss << "  history\tHistory QoS setting. Either 'keep_last' (default) or 'keep_all'.";  // 打印历史参数ss << std::endl;ss << "\t\tIf 'keep_last', then up to N samples are stored where N is the depth";  // 打印深度参数ss << std::endl;ss << "  depth\t\tDepth of the publisher queue. Only honored if history QoS is 'keep_last'.";  // 打印深度参数ss << " Default value is 10";  // 打印默认值ss << std::endl;ss << "  show_image\tShow the image. Either 'true' (default) or 'false'";  // 打印显示图像参数ss << std::endl;ss << "  window_name\tName of the display window. Default value is the topic name";  // 打印窗口名称参数ss << std::endl;std::cout << ss.str();  // 输出帮助信息return true;  // 返回true}return false;  // 返回false}IMAGE_TOOLS_LOCALvoid parse_parameters()  // 解析参数函数{// Parse 'reliability' parameterrcl_interfaces::msg::ParameterDescriptor reliability_desc;  // 创建参数描述符reliability_desc.description = "Reliability QoS setting for the image subscription";  // 设置描述reliability_desc.additional_constraints = "Must be one of: ";  // 设置附加约束for (auto entry : name_to_reliability_policy_map) {  // 遍历可靠性策略映射reliability_desc.additional_constraints += entry.first + " ";  // 添加约束}const std::string reliability_param = this->declare_parameter("reliability", "reliable", reliability_desc);  // 声明参数auto reliability = name_to_reliability_policy_map.find(reliability_param);  // 查找参数if (reliability == name_to_reliability_policy_map.end()) {  // 如果参数无效std::ostringstream oss;  // 创建字符串流oss << "Invalid QoS reliability setting '" << reliability_param << "'";  // 打印错误信息throw std::runtime_error(oss.str());  // 抛出运行时错误}reliability_policy_ = reliability->second;  // 设置可靠性策略// Parse 'history' parameterrcl_interfaces::msg::ParameterDescriptor history_desc;  // 创建参数描述符history_desc.description = "History QoS setting for the image subscription";  // 设置描述history_desc.additional_constraints = "Must be one of: ";  // 设置附加约束for (auto entry : name_to_history_policy_map) {  // 遍历历史策略映射history_desc.additional_constraints += entry.first + " ";  // 添加约束}const std::string history_param = this->declare_parameter("history", name_to_history_policy_map.begin()->first, history_desc);  // 声明参数auto history = name_to_history_policy_map.find(history_param);  // 查找参数if (history == name_to_history_policy_map.end()) {  // 如果参数无效std::ostringstream oss;  // 创建字符串流oss << "Invalid QoS history setting '" << history_param << "'";  // 打印错误信息throw std::runtime_error(oss.str());  // 抛出运行时错误}history_policy_ = history->second;  // 设置历史策略// Declare and get remaining parametersdepth_ = this->declare_parameter("depth", 10);  // 声明深度参数show_image_ = this->declare_parameter("show_image", true); window_name_ = this->declare_parameter("window_name", "");  // 声明窗口名称参数}/// Convert the ROS Image message to an OpenCV matrix and display it to the user.// \param[in] container The image message to show.IMAGE_TOOLS_LOCALvoid process_image(const image_tools::ROSCvMatContainer & container, bool show_image, rclcpp::Logger logger)  // 处理图像函数{RCLCPP_INFO(logger, "Received image #%s", container.header().frame_id.c_str());  // 打印接收图像信息std::cerr << "Received image #" << container.header().frame_id.c_str() << std::endl;  // 打印接收图像信息if (show_image) {  // 如果显示图像cv::Mat frame = container.cv_mat();  // 获取图像矩阵if (frame.type() == CV_8UC3 /* rgb8 */) {  // 如果图像类型为CV_8UC3cv::cvtColor(frame, frame, cv::COLOR_RGB2BGR);  // 转换颜色空间} else if (frame.type() == CV_8UC2) {  // 如果图像类型为CV_8UC2container.is_bigendian() ? cv::cvtColor(frame, frame, cv::COLOR_YUV2BGR_UYVY) :cv::cvtColor(frame, frame, cv::COLOR_YUV2BGR_YUYV);  // 转换颜色空间}// Show the image in a windowcv::imshow(window_name_, frame);  // 在窗口中显示图像// Draw the screen and wait for 1 millisecond.cv::waitKey(1);  // 刷新窗口并等待1毫秒}}rclcpp::Subscription<image_tools::ROSCvMatContainer>::SharedPtr sub_;  // 订阅对象size_t depth_ = rmw_qos_profile_default.depth;  // 深度参数rmw_qos_reliability_policy_t reliability_policy_ = rmw_qos_profile_default.reliability;  // 可靠性策略rmw_qos_history_policy_t history_policy_ = rmw_qos_profile_default.history;  // 历史策略bool show_image_ = true;  // 是否显示图像std::string topic_ = "image";  // 主题名称std::string window_name_;  // 窗口名称
};}  // namespace image_toolsRCLCPP_COMPONENTS_REGISTER_NODE(image_tools::ShowImage)  // 注册节点

cam2image.cpp

#include <chrono>  // 包含时间库
#include <memory>  // 包含智能指针库
#include <sstream>  // 包含字符串流库
#include <string>  // 包含字符串库
#include <utility>  // 包含实用工具库
#include <vector>  // 包含向量库#include "opencv2/core/mat.hpp"  // 包含OpenCV核心矩阵库
#include "opencv2/core.hpp"  // 包含OpenCV核心库
#include "opencv2/highgui.hpp"  // 包含OpenCV高层GUI库
#include "opencv2/videoio.hpp"  // 包含OpenCV视频输入输出库#include "rcl_interfaces/msg/parameter_descriptor.hpp"  // 包含参数描述符消息
#include "rclcpp/rclcpp.hpp"  // 包含rclcpp库
#include "rclcpp_components/register_node_macro.hpp"  // 包含注册节点宏
#include "std_msgs/msg/bool.hpp"  // 包含布尔消息#include "image_tools/cv_mat_sensor_msgs_image_type_adapter.hpp"  // 包含自定义类型适配器
#include "image_tools/visibility_control.h"  // 包含可见性控制头文件#include "./burger.hpp"  // 包含汉堡头文件
#include "./policy_maps.hpp"  // 包含策略映射头文件RCLCPP_USING_CUSTOM_TYPE_AS_ROS_MESSAGE_TYPE(image_tools::ROSCvMatContainer,sensor_msgs::msg::Image);  // 使用自定义类型作为ROS消息类型namespace image_tools  // 定义命名空间image_tools
{
class Cam2Image : public rclcpp::Node  // 定义Cam2Image类,继承自rclcpp::Node
{
public:IMAGE_TOOLS_PUBLICexplicit Cam2Image(const rclcpp::NodeOptions & options)  // 显式构造函数: Node("cam2image", options),  // 调用基类构造函数,初始化节点名称为"cam2image"is_flipped_(false),  // 初始化是否翻转图像为falsepublish_number_(1u)  // 初始化发布编号为1{setvbuf(stdout, NULL, _IONBF, BUFSIZ);  // 设置标准输出缓冲区// Do not execute if a --help option was providedif (help(options.arguments())) {  // 如果提供了--help选项,则不执行// TODO(jacobperron): Replace with a mechanism for a node to "unload" itself// from a container.exit(0);  // 退出程序}parse_parameters();  // 解析参数initialize();  // 初始化}private:IMAGE_TOOLS_LOCALvoid initialize()  // 初始化函数{auto qos = rclcpp::QoS(rclcpp::QoSInitialization(// The history policy determines how messages are saved until taken by// the reader.// KEEP_ALL saves all messages until they are taken.// KEEP_LAST enforces a limit on the number of messages that are saved,// specified by the "depth" parameter.history_policy_,  // 设置历史策略// Depth represents how many messages to store in history when the// history policy is KEEP_LAST.depth_  // 设置深度));// The reliability policy can be reliable, meaning that the underlying transport layer will try// ensure that every message gets received in order, or best effort, meaning that the transport// makes no guarantees about the order or reliability of delivery.qos.reliability(reliability_policy_);  // 设置可靠性策略pub_ = create_publisher<image_tools::ROSCvMatContainer>("image", qos);  // 创建发布者// Subscribe to a message that will toggle flipping or not flipping, and manage the state in a// callbackauto callback = this -> void  // 定义回调函数{this->is_flipped_ = msg->data;  // 设置是否翻转图像RCLCPP_INFO(this->get_logger(), "Set flip mode to: %s", this->is_flipped_ ? "on" : "off");  // 打印翻转模式信息};// Set the QoS profile for the subscription to the flip message.sub_ = create_subscription<std_msgs::msg::Bool>("flip_image", rclcpp::SensorDataQoS(), callback);  // 创建订阅if (!burger_mode_) {  // 如果不是汉堡模式// Initialize OpenCV video capture stream.cap.open(device_id_);  // 打开视频捕获设备// Set the width and height based on command line arguments.cap.set(cv::CAP_PROP_FRAME_WIDTH, static_cast<double>(width_));  // 设置宽度cap.set(cv::CAP_PROP_FRAME_HEIGHT, static_cast<double>(height_));  // 设置高度if (!cap.isOpened()) {  // 如果视频流未打开RCLCPP_ERROR(this->get_logger(), "Could not open video stream");  // 打印错误信息throw std::runtime_error("Could not open video stream");  // 抛出运行时错误}}// Start main timer looptimer_ = this->create_wall_timer(std::chrono::milliseconds(static_cast<int>(1000.0 / freq_)),  // 设置定时器频率this {return this->timerCallback();});  // 设置定时器回调函数}/// Publish camera, or burger, image.IMAGE_TOOLS_LOCALvoid timerCallback()  // 定时器回调函数{cv::Mat frame;  // 定义图像矩阵// Initialize a shared pointer to an Image message.// Get the frame from the video capture.if (burger_mode_) {  // 如果是汉堡模式frame = burger_cap.render_burger(width_, height_);  // 渲染汉堡图像} else {cap >> frame;  // 从视频捕获设备获取图像}// If no frame was grabbed, return earlyif (frame.empty()) {  // 如果未获取到图像return;  // 提前返回}// Conditionally flip the imageif (is_flipped_) {  // 如果需要翻转图像cv::flip(frame, frame, 1);  // 翻转图像}// Conditionally show imageif (show_camera_) {  // 如果需要显示图像// Show the image in a window called "cam2image".cv::imshow("cam2image", frame);  // 在窗口中显示图像// Draw the image to the screen and wait 1 millisecond.cv::waitKey(1);  // 刷新窗口并等待1毫秒}std_msgs::msg::Header header;  // 定义消息头header.frame_id = frame_id_;  // 设置帧IDheader.stamp = this->now();  // 设置时间戳image_tools::ROSCvMatContainer container(frame, header);  // 创建图像容器// Publish the image message and increment the publish_number_.RCLCPP_INFO(get_logger(), "Publishing image #%zd", publish_number_++);  // 打印发布信息pub_->publish(std::move(container));  // 发布图像消息}IMAGE_TOOLS_LOCALbool help(const std::vector<std::string> & args)  // 帮助函数{if (std::find(args.begin(), args.end(), "--help") != args.end() ||std::find(args.begin(), args.end(), "-h") != args.end())  // 如果提供了--help或-h选项{std::stringstream ss;  // 创建字符串流ss << "Usage: cam2image [-h] [--ros-args [-p param:=value] ...]" << std::endl;  // 打印用法ss << "Publish images from a camera stream." << std::endl;  // 打印描述ss << "Example: ros2 run image_tools cam2image --ros-args -p reliability:=best_effort";  // 打印示例ss << std::endl << std::endl;ss << "Options:" << std::endl;  // 打印选项ss << "  -h, --help\tDisplay this help message and exit";  // 打印帮助选项ss << std::endl << std::endl;ss << "Parameters:" << std::endl;  // 打印参数ss << "  reliability\tReliability QoS setting. Either 'reliable' (default) or 'best_effort'";  // 打印可靠性参数ss << std::endl;ss << "  history\tHistory QoS setting. Either 'keep_last' (default) or 'keep_all'.";  // 打印历史参数ss << std::endl;ss << "\t\tIf 'keep_last', then up to N samples are stored where N is the depth";  // 打ss << std::endl;ss << "  depth\t\tDepth of the publisher queue. Only honored if history QoS is 'keep_last'.";  // 打印深度参数ss << " Default value is 10";  // 打印默认值ss << std::endl;ss << "  frequency\tPublish frequency in Hz. Default value is 30";  // 打印频率参数ss << std::endl;ss << "  burger_mode\tProduce images of burgers rather than connecting to a camera";  // 打印汉堡模式参数ss << std::endl;ss << "  show_camera\tShow camera stream. Either 'true' or 'false' (default)";  // 打印显示摄像头参数ss << std::endl;ss << "  device_id\tDevice ID of the camera. 0 (default) selects the default camera device.";  // 打印设备ID参数ss << std::endl;ss << "  width\t\tWidth component of the camera stream resolution. Default value is 320";  // 打印宽度参数ss << std::endl;ss << "  height\tHeight component of the camera stream resolution. Default value is 240";  // 打印高度参数ss << std::endl;ss << "  frame_id\t\tID of the sensor frame. Default value is 'camera_frame'";  // 打印帧ID参数ss << std::endl << std::endl;ss << "Note: try running v4l2-ctl --list-formats-ext to obtain a list of valid values.";  // 打印提示信息ss << std::endl;std::cout << ss.str();  // 输出帮助信息return true;  // 返回true}return false;  // 返回false}IMAGE_TOOLS_LOCALvoid parse_parameters()  // 解析参数函数{// Parse 'reliability' parameterrcl_interfaces::msg::ParameterDescriptor reliability_desc;  // 创建参数描述符reliability_desc.description = "Reliability QoS setting for the image publisher";  // 设置描述reliability_desc.additional_constraints = "Must be one of: ";  // 设置附加约束for (auto entry : name_to_reliability_policy_map) {  // 遍历可靠性策略映射reliability_desc.additional_constraints += entry.first + " ";  // 添加约束}const std::string reliability_param = this->declare_parameter("reliability", "reliable", reliability_desc);  // 声明参数auto reliability = name_to_reliability_policy_map.find(reliability_param);  // 查找参数if (reliability == name_to_reliability_policy_map.end()) {  // 如果参数无效std::ostringstream oss;  // 创建字符串流oss << "Invalid QoS reliability setting '" << reliability_param << "'";  // 打印错误信息throw std::runtime_error(oss.str());  // 抛出运行时错误}reliability_policy_ = reliability->second;  // 设置可靠性策略// Parse 'history' parameterrcl_interfaces::msg::ParameterDescriptor history_desc;  // 创建参数描述符history_desc.description = "History QoS setting for the image publisher";  // 设置描述history_desc.additional_constraints = "Must be one of: ";  // 设置附加约束for (auto entry : name_to_history_policy_map) {  // 遍历历史策略映射history_desc.additional_constraints += entry.first + " ";  // 添加约束}const std::string history_param = this->declare_parameter("history", name_to_history_policy_map.begin()->first, history_desc);  // 声明参数auto history = name_to_history_policy_map.find(history_param);  // 查找参数if (history == name_to_history_policy_map.end()) {  // 如果参数无效std::ostringstream oss;  // 创建字符串流oss << "Invalid QoS history setting '" << history_param << "'";  // 打印错误信息throw std::runtime_error(oss.str());  // 抛出运行时错误}history_policy_ = history->second;  // 设置历史策略// Declare and get remaining parametersdepth_ = this->declare_parameter("depth", 10);  // 声明深度参数freq_ = this->declare_parameter("frequency", 30.0);  // 声明频率参数burger_mode_ = this->declare_parameter("burger_mode", false);  // 声明汉堡模式参数show_camera_ = this->declare_parameter("show_camera", false);  // 声明显示摄像头参数device_id_ = this->declare_parameter("device_id", 0);  // 声明设备ID参数width_ = this->declare_parameter("width", 320);  // 声明宽度参数height_ = this->declare_parameter("height", 240);  // 声明高度参数frame_id_ = this->declare_parameter("frame_id", "camera_frame");  // 声明帧ID参数}rclcpp::Publisher<image_tools::ROSCvMatContainer>::SharedPtr pub_;  // 发布者对象rclcpp::Subscription<std_msgs::msg::Bool>::SharedPtr sub_;  // 订阅者对象rclcpp::TimerBase::SharedPtr timer_;  // 定时器对象cv::VideoCapture cap;  // 视频捕获对象Burger burger_cap;  // 汉堡捕获对象bool is_flipped_;  // 是否翻转图像bool burger_mode_;  // 是否为汉堡模式bool show_camera_;  // 是否显示摄像头size_t depth_;  // 深度参数double freq_;  // 频率参数int device_id_;  // 设备ID参数int width_;  // 宽度参数int height_;  // 高度参数std::string frame_id_;  // 帧ID参数size_t publish_number_;  // 发布编号rmw_qos_reliability_policy_t reliability_policy_;  // 可靠性策略rmw_qos_history_policy_t history_policy_;  // 历史策略
};}  // namespace image_toolsRCLCPP_COMPONENTS_REGISTER_NODE(image_tools::Cam2Image)  // 注册节点

policy_maps.hpp

#ifndef POLICY_MAPS_HPP_  // 如果没有定义POLICY_MAPS_HPP_
#define POLICY_MAPS_HPP_  // 定义POLICY_MAPS_HPP_#include <map>  // 包含map库
#include <string>  // 包含string库namespace image_tools  // 定义命名空间image_tools
{static  // 定义静态变量
std::map<std::string, rmw_qos_reliability_policy_t> name_to_reliability_policy_map = {  // 定义字符串到可靠性策略的映射{"reliable", RMW_QOS_POLICY_RELIABILITY_RELIABLE},  // 可靠性策略映射:reliable{"best_effort", RMW_QOS_POLICY_RELIABILITY_BEST_EFFORT}  // 可靠性策略映射:best_effort
};static  // 定义静态变量
std::map<std::string, rmw_qos_history_policy_t> name_to_history_policy_map = {  // 定义字符串到历史策略的映射{"keep_last", RMW_QOS_POLICY_HISTORY_KEEP_LAST},  // 历史策略映射:keep_last{"keep_all", RMW_QOS_POLICY_HISTORY_KEEP_ALL}  // 历史策略映射:keep_all
};}  // 结束image_tools命名空间#endif  // 结束对POLICY_MAPS_HPP_的定义

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/875011.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Fedora40安装telnet-server启用telnet服务

Fedora40安装telnet-server启用telnet服务 安装 telnet-server sudo yum install telnet-server或 sudo dnf install telnet-server启用服务 fedora40 或 CentosStream9 不能用 yum或dnf安装xinetd, telnet-server 的服务名为: telnet.socket 启用 telnet.socket.service …

三、基础语法2(30小时精通C++和外挂实战)

三、基础语法2&#xff08;30小时精通C和外挂实战&#xff09; B-02内联函数B-04内联函数与宏B-05_constB-06引用B-07引用的本质B-08-汇编1-X86-X64汇编B-09-汇编2-内联汇编B-10-汇编3-MOV指令C-02-汇编5-其他常见指令C-05-汇编8-反汇编分析C-07-const引用、特点 B-02内联函数 …

TreeSelect增加可筛选功能

TreeSelect官方可筛选示例 <template><el-tree-selectv-model"value":data"data"filterablestyle"width: 240px"/><el-divider /><el-divider />filter node method:<el-tree-selectv-model"value":data&q…

数据安全传输--加密算法

目录 古典加密算法与近代加密算法对比 算法分类 对称加密 常见的对称加密算法 在对称加密算法中密钥共享是一个很麻烦的问题 非对称加密 非对称加密过程 常见非对称加密算法 对称加密和非对称加密两者对比结论 DH算法 身份认证和数据认证技术 hash算法 hash算法特点…

PySide(PyQt),自定义图标按钮

1、在Qt Designer中新建画面&#xff0c;并放置3个按钮&#xff08;QPushButton&#xff09;和一个分组框&#xff08;QGroupBox&#xff09;小部件&#xff0c;分别命名为btn_1&#xff0c; btn_2&#xff0c;btn_3和btnStation。 2、将所有小部件的显示文字内容删除。 3、将…

论文复现:Predictive Control of Networked Multiagent Systems via Cloud Computing

Predictive Control of Networked Multiagent Systems via Cloud Computing论文复现 文章目录 Predictive Control of Networked Multiagent Systems via Cloud Computing论文复现论文摘要系统参数初始化系统模型观测器预测过程控制器设计系统的整体框图仿真结果 论文摘要 翻译…

杰发科技Bootloader(2)—— 基于7840的Keil配置地址

序 在7840的sample代码里面有一个简单的Boot跳转APP的示例 PFlash地址从0开始 DFlash的地址从1000000开始 Boot解析 他的boot地址配置为0 Boot的代码主要是这几行&#xff0c;主要作用就是Flash的跳转 int main(void) {SystemClock_Config();InitDebug();printf("demo…

NSAT-8000与Chroma8000相比,有什么独特优势?

在电源模块的广泛应用推动下&#xff0c;测试效率成为行业关注的焦点。纳米软件响应这一需求&#xff0c;推出了NSAT-8000电源自动测试系统&#xff0c;其0代码操作模式大幅简化了测试流程。那么与Chroma 8000系统相比&#xff0c;有什么不同呢&#xff1f; 一、测试项目搭建 C…

nacos get changed dataId error, code: 403

nacos get changed dataId error, code: 403问题解决 问题出现原因&#xff1a;解决办法&#xff1a;需要在运行项目的配置添加权限账号和密码,重启服务 问题出现原因&#xff1a; 由于nacosserver开启了权限验证&#xff0c;项目启动时出现异常 nacos.core.auth.caching.ena…

数据结构->线性结构->顺序存储->静态链表

一、思路 链表由节点组成。 1、分析需求&#xff0c;画图&#xff1a; 2、定义学生结构体&#xff0c;包含姓名、年龄、性别和下一个学生的指针&#xff1a; #include <stdio.h> #define N 20// 定义性别枚举类型&#xff0c;固定值&#xff0c;不是男就是女 typedef e…

torchscript接口

一、定义 定义script、eager、onnx 模式对比案例生成的模型可以被c调用接口解读 二、实现 定义 可以在高性能环境libtorch&#xff08;C &#xff09;中直接加载&#xff0c;实现模型推理&#xff0c;而无需Pytorch训练框架依赖无需代码&#xff0c;直接加载模型&#xff0c…

国中水务:果汁能救“水”吗?

喝下汇源果汁有什么&#xff08;“功效”&#xff09;&#xff1f;这家公司最有发言权。 今天我们聊聊——国中水务。 最近&#xff0c;国中水务公告称拟通过收购&#xff0c;间接控股北京汇源&#xff0c;即将把“垂涎已久”的汇源收入囊中。 两家的故事得从几年前说起&#…

学习大数据DAY21 Linux基本指令2

目录 思维导图 搜索查看查找类 find 从指定目录查找文件 head 与 tail 查看行 cat 查看内容 more 查看大内容 grep 过滤查找 history 查看已经执行过的历史命令 wc 统计文件 du 查看空间 管道符号 | 配合命令使用 上机练习 4 解压安装类 zip unzip 压缩解压 tar …

git跨库合并

1、背景 A为开发环境的代码仓库&#xff0c;B为生产环境的代码仓库。A和B之间不能通信。开发人员的本地电脑可以和A、B通信。 目的 上线时&#xff0c;需要将A代码合并B代码。 2、实现 2.1 添加远程仓库 2.1.1 代码方式 在B代码仓库中,将A添加为远程仓库。 git remote …

【保姆级教程】油猴脚本的安装使用

目录 前言 一、油猴简介 1. 核心功能 2. 应用场景 3. 安全性与兼容性 4. 社区生态 二、教学开始&#xff08;嫌麻烦直接目录跳转开始学习&#xff09; 1.插件安装&#xff08;以Microsoft Edge浏览器为例&#xff09; 2.获取脚本 3.大展身手 三、扩展&#xff08;脚…

2024年7月23日(samba DNS)

​ 回顾 1、关闭防火墙&#xff0c;关闭selinux systemctl stop firewalld systemctl disable firewalld setenforce 0 2、修改静态IP地址 vim /etc/sysconfig/network-scripts/ifcfg-ens33 #修改uuid的目的是为了保证网络的唯一性 3、重启网络服务 systemctl restart netwo…

Ansible的脚本-----playbook剧本【上】

目录 1.playbook剧本组成 2.playbook剧本实战演练 2.1 实战演练一&#xff1a;给被管理主机安装httpd服务 2.2 实战演练二&#xff1a;定义、引用变量 2.3 实战演练三&#xff1a;指定远程主机sudo切换用户 2.4 实战演练四&#xff1a;when条件判断 2.5 实战演练五&…

【Matlab 传感器布局优化】基于群智能算法的wsn覆盖优化研究

一 背景介绍 无线传感器网络&#xff08;Wireless Sensor Network, WSN&#xff09;作为远程环境监测系统应用的关键技术&#xff0c;能够在有限的能源供应下提供高效的传感和通信服务。覆盖控制是保证高效通信和可靠数据传输的重要手段。鉴于复杂的物理环境限制了节点部署方式…

文本编辑三巨头(grep)

目录 正则表达式 元字符 grep 案例 我在编写脚本的时候发现&#xff0c;三个文本编辑的命令&#xff08;grep、sed、awk&#xff0c;被称为文本编辑三剑客&#xff0c;我习惯叫它三巨头&#xff09;用的还挺多的&#xff0c;说实话我一开始学的时候也有些懵&#xff0c;主要…

Unity3D之TCP网络通信(客户端)

文章目录 概述TCP核心类异步机制 Unity中创建TCP客户端Unity中其它脚本获取TCP客户端接受到的数据后续改进 本文将以Unity3D应用项目作为客户端去连接制定的服务器为例进行相关说明。 Unity官网参考资料&#xff1a; https://developer.unity.cn/projects/6572ea1bedbc2a001ef…