从 bag 文件中读取数据(C++) [16419]
目标: 在不使用 CLI 的情况下从 bag 中读取数据。 [16420]
教程级别: 高级 [16408]
时间: 10分钟 [16421]
内容 [16422]
背景 [16410]
rosbag2
不仅提供了 ros2 bag
命令行工具,还提供了用于从您自己的源代码读取和写入 bag 的 C++ API。这允许您读取 bag 中的内容,而无需播放 bag,这在某些情况下可能非常有用。 [16423]
先决条件 [16411]
你应该在常规的 ROS 2 配置中安装了 rosbag2
包。 [16424]
如果你是通过在 Linux 上安装 Debian 包进行安装的,它可能是默认安装的。如果没有安装,可以使用以下命令安装: [16425]
sudo apt install ros-humble-rosbag2
本教程讨论了如何使用 ROS 2 bags。您应该已经完成了 基本的 ROS 2 bag 教程,我们将使用您在那里创建的 subset
bag。 [16426]
任务 [16427]
1 创建一个包 [16428]
在一个新的终端中 源化你的 ROS 2 安装,这样 ros2
命令才能正常工作。 [16429]
在新的或现有的 工作空间 中,导航到 src
目录并创建一个新包: [16430]
ros2 pkg create --build-type ament_cmake --license Apache-2.0 bag_reading_cpp --dependencies rclcpp rosbag2_cpp turtlesim
您的终端将返回一条消息,验证包 bag_reading_cpp
及其所有必要的文件和文件夹的创建。--dependencies
参数将自动将必要的依赖行添加到 package.xml
和 CMakeLists.txt
中。在本例中,包将使用 rosbag2_cpp
包以及 rclcpp
包。还需要依赖于 turtlesim
包,以便使用自定义 turtlesim 消息。 [16431]
2 编写 C++ 读取器 [16434]
在包的 src
目录中,创建一个名为 simple_bag_reader.cpp
的新文件,并将以下代码粘贴到其中。 [16435]
#include <chrono>
#include <functional>
#include <iostream>
#include <memory>
#include <string>
#include "rclcpp/rclcpp.hpp"
#include "rclcpp/serialization.hpp"
#include "rosbag2_cpp/reader.hpp"
#include "turtlesim/msg/pose.hpp"
using namespace std::chrono_literals;
class PlaybackNode : public rclcpp::Node
{
public:
PlaybackNode(const std::string & bag_filename)
: Node("playback_node")
{
publisher_ = this->create_publisher<turtlesim::msg::Pose>("/turtle1/pose", 10);
timer_ = this->create_wall_timer(
100ms, std::bind(&PlaybackNode::timer_callback, this));
reader_.open(bag_filename);
}
private:
void timer_callback()
{
while (reader_.has_next()) {
rosbag2_storage::SerializedBagMessageSharedPtr msg = reader_.read_next();
if (msg->topic_name != "/turtle1/pose") {
continue;
}
rclcpp::SerializedMessage serialized_msg(*msg->serialized_data);
turtlesim::msg::Pose::SharedPtr ros_msg = std::make_shared<turtlesim::msg::Pose>();
serialization_.deserialize_message(&serialized_msg, ros_msg.get());
publisher_->publish(*ros_msg);
std::cout << '(' << ros_msg->x << ", " << ros_msg->y << ")\n";
break;
}
}
rclcpp::TimerBase::SharedPtr timer_;
rclcpp::Publisher<turtlesim::msg::Pose>::SharedPtr publisher_;
rclcpp::Serialization<turtlesim::msg::Pose> serialization_;
rosbag2_cpp::Reader reader_;
};
int main(int argc, char ** argv)
{
if (argc != 2) {
std::cerr << "Usage: " << argv[0] << " <bag>" << std::endl;
return 1;
}
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<PlaybackNode>(argv[1]));
rclcpp::shutdown();
return 0;
}
2.1 检查代码 [16436]
顶部的 #include
语句是包的依赖项。注意包含了来自 rosbag2_cpp
包的头文件,用于处理袋文件所需的函数和结构。 [16437]
接下来的行创建了一个节点,该节点将从 bag 文件中读取数据并播放回来。 [16438]
class PlaybackNode : public rclcpp::Node
现在,我们可以创建一个计时器回调,它将以 10 Hz 运行。我们的目标是在每次运行回调时向 /turtle1/pose
主题重放一条消息。请注意,构造函数接受 bag 文件的路径作为参数。 [16439]
public:
PlaybackNode(const std::string & bag_filename)
: Node("playback_node")
{
publisher_ = this->create_publisher<turtlesim::msg::Pose>("/turtle1/pose", 10);
timer_ = this->create_wall_timer(
100ms, std::bind(&PlaybackNode::timer_callback, this));
我们还在构造函数中打开了 bag 文件。 [16440]
reader_.open(bag_filename);
现在,在我们的计时器回调内部,我们循环遍历 bag 中的消息,直到读取到来自我们所需主题的消息为止。请注意,序列化消息除了主题名称之外还具有时间戳元数据。 [16441]
void timer_callback()
{
while (reader_.has_next()) {
rosbag2_storage::SerializedBagMessageSharedPtr msg = reader_.read_next();
if (msg->topic_name != "/turtle1/pose") {
continue;
}
然后,我们从刚刚读取的序列化数据构造了一个 rclcpp::SerializedMessage
对象。此外,我们需要创建一个 ROS 2 反序列化消息,它将保存我们反序列化的结果。然后,我们可以将这两个对象传递给 rclcpp::Serialization::deserialize_message
方法。 [16442]
rclcpp::SerializedMessage serialized_msg(*msg->serialized_data);
turtlesim::msg::Pose::SharedPtr ros_msg = std::make_shared<turtlesim::msg::Pose>();
serialization_.deserialize_message(&serialized_msg, ros_msg.get());
最后,我们发布反序列化后的消息并将 xy 坐标打印到终端上。我们还跳出循环,以便在下一个计时器回调期间发布下一条消息。 [16443]
publisher_->publish(*ros_msg);
std::cout << '(' << ros_msg->x << ", " << ros_msg->y << ")\n";
break;
}
我们还必须声明在整个节点中使用的私有变量。 [16444]
rclcpp::TimerBase::SharedPtr timer_;
rclcpp::Publisher<turtlesim::msg::Pose>::SharedPtr publisher_;
rclcpp::Serialization<turtlesim::msg::Pose> serialization_;
rosbag2_cpp::Reader reader_;
};
最后,我们创建了主函数,该函数将检查用户是否传递了 bag 文件路径的参数,并启动我们的节点。 [16445]
int main(int argc, char ** argv)
{
if (argc != 2) {
std::cerr << "Usage: " << argv[0] << " <bag>" << std::endl;
return 1;
}
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<PlaybackNode>(argv[1]));
rclcpp::shutdown();
return 0;
}
2.2 添加可执行文件 [16446]
现在打开 CMakeLists.txt
文件。 [16447]
在包含 find_package(rosbag2_cpp REQUIRED)
的依赖项块下面,添加以下代码行。 [16448]
add_executable(simple_bag_reader src/simple_bag_reader.cpp)
ament_target_dependencies(simple_bag_reader rclcpp rosbag2_cpp turtlesim)
install(TARGETS
simple_bag_reader
DESTINATION lib/${PROJECT_NAME}
)
3. 构建和运行 [16449]
返回到工作空间的根目录并构建您的新包。 [16450]
colcon build --packages-select bag_reading_cpp
colcon build --packages-select bag_reading_cpp
colcon build --merge-install --packages-select bag_reading_cpp
接下来,使用 source
命令执行设置文件。 [16451]
source install/setup.bash
source install/setup.bash
call install/setup.bat
现在,运行脚本。确保将 /path/to/setup
替换为您的 setup
bag 的路径。 [16452]
ros2 run bag_reading_cpp simple_bag_reader /path/to/setup
您应该会在控制台上看到乌龟的 (x, y) 坐标被打印出来。 [16453]