2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ROS 2Advent Calendar 2023

Day 25

ROS 2でmessage_filterを使用するサンプル

Posted at

概要 

ROS 2でmessage_filtersを使いたいときにネット上に正常に動くサンプルコードがなかったので、なるべく分かりやすくて実用性のあるサンプルコードを作成する。

message_filtersの概要

複数のトピックからのメッセージを同期的に処理するためのライブラリ。
ロボットのようなリアルタイムシステムでは、複数のセンサからのデータを同時に取得し、それらのデータを使用して処理を行うことが頻繁にあり、message_filtersを使うとそれらのデータを同期して処理することができる。

message_filtersには以下の特徴がある

  • 複数のソースからのメッセージ同期: 異なるトピックやソースからのメッセージを時間的に同期して、単一のコールバック関数で処理することができる。

  • 様々な同期ポリシー: 厳密な同期や近似的な同期など複数の同期ポリシーを選択でき、用途に応じた同期ポリシーを選択できる。

message_filtersの説明はこちらの記事も参考になります。

作成したサンプル

realsense_rosからのRBG画像トピック(/camera/color/image_raw) とDepth画像トピック(/camera/aligned_depth_to_color/image_raw)をSbscribe して横に並べて合成した画像を作成し Publishする。

rosgraph.png

作成したコード

実際にROS2 Humbleでcolcon buildして動いたやつ。callbackの中身は適当。

#include "rclcpp/rclcpp.hpp"
#include "sensor_msgs/msg/image.hpp"
#include <cv_bridge/cv_bridge.h>
#include <message_filters/subscriber.h>
#include <message_filters/sync_policies/approximate_time.h>
#include <message_filters/time_synchronizer.h>
#include <opencv2/core/core.hpp>

class ImageCombiner : public rclcpp::Node {
public:
  message_filters::Subscriber<sensor_msgs::msg::Image> rgb_sub_;
  message_filters::Subscriber<sensor_msgs::msg::Image> depth_sub_;
  rclcpp::Publisher<sensor_msgs::msg::Image>::SharedPtr combined_image_pub_;
  typedef message_filters::sync_policies::ApproximateTime<sensor_msgs::msg::Image, sensor_msgs::msg::Image> approximate_policy;
  message_filters::Synchronizer<approximate_policy> sync_;

  ImageCombiner()
      : Node("image_combiner_node"),
        sync_(approximate_policy(10), rgb_sub_, depth_sub_) {
    rgb_sub_.subscribe(this, "/camera/color/image_raw");
    depth_sub_.subscribe(this, "/camera/aligned_depth_to_color/image_raw");
    combined_image_pub_ = this->create_publisher<sensor_msgs::msg::Image>("/output_image", 10);

    sync_.registerCallback(&ImageCombiner::topic_callback, this);
  }

public:
  void topic_callback(const sensor_msgs::msg::Image::SharedPtr rgb_image, const sensor_msgs::msg::Image::SharedPtr depth_image) {
    // Convert ROS Image messages to OpenCV Mat
    cv::Mat rgb_cv_image = cv_bridge::toCvShare(rgb_image, "bgr8")->image;
    cv::Mat depth_cv_image = cv_bridge::toCvShare(depth_image, "16UC1")->image;

    // Normalize depth image for visualization
    cv::normalize(depth_cv_image, depth_cv_image, 0, 255, cv::NORM_MINMAX, CV_8UC1);

    // Convert normalized 8-bit depth image to 3-channel for visualization
    cv::cvtColor(depth_cv_image, depth_cv_image, cv::COLOR_GRAY2BGR);

    // Combine the images side by side
    cv::Mat combined_cv_image;
    cv::hconcat(rgb_cv_image, depth_cv_image, combined_cv_image);

    // Convert combined OpenCV Mat to ROS Image message
    sensor_msgs::msg::Image::SharedPtr combined_image = cv_bridge::CvImage(rgb_image->header, "bgr8", combined_cv_image).toImageMsg();
    combined_image_pub_->publish(*combined_image);
    RCLCPP_INFO(this->get_logger(), "Combined image published");
  }
};

int main(int argc, char *argv[]) {
  rclcpp::init(argc, argv);
  rclcpp::spin(std::make_shared<ImageCombiner>());
  rclcpp::shutdown();
  return 0;
}

CMakeLists.txt

details
cmake_minimum_required(VERSION 3.5)
project(message_filters_sample_ros2)

# Default to C++14
if(NOT CMAKE_CXX_STANDARD)
  set(CMAKE_CXX_STANDARD 14)
endif()

if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
  add_compile_options(-Wall -Wextra -Wpedantic)
endif()

# find dependencies
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(sensor_msgs REQUIRED)
find_package(cv_bridge REQUIRED)
find_package(message_filters REQUIRED)
find_package(OpenCV REQUIRED)

include_directories(include)

add_executable(message_filters_sample src/message_filters_sample.cpp)

ament_target_dependencies(message_filters_sample
  rclcpp
  sensor_msgs
  cv_bridge
  message_filters
  OpenCV
)

install(TARGETS
  message_filters_sample
  DESTINATION lib/${PROJECT_NAME})

ament_package()

実行

realsense_rosの実行

ros2 launch realsense2_camera rs_launch.py align_depth.enable:=true
ros2 run message_filters_sample_ros2 message_filters_sample 

image.png

余談
こんなことしなくてもRGBD.msg
というメッセージ型で時刻同期された画像が取得できるようになったようです。

RealSenseの時刻同期したRGBD画像をSubscribeする(realsense2_camera_msgs)
https://ar-ray.hatenablog.com/entry/2023/11/30/083000

各Sync Policyの説明

ApproximateEpsilonTime、LatestTimeはIronで追加された機能。(説明はChatGPTで要約したので間違っているかも)

ExactTime

ExactTimeポリシーは、メッセージが完全に同じタイムスタンプを持つ場合にのみメッセージを同期させるために使用されます。この厳密な時間同期は、高度な同時性が要求される場合に適していますが、センサーからのデータが完全に一致するタイミングでないとメッセージを処理しません。

ApproximateTime

ApproximateTimeポリシーは、タイムスタンプが完全に一致しなくても近い時間内にあるメッセージを同期させるために使用されます。これは、異なるセンサーからのデータが完全に一致するタイミングを持たないが、ある程度近い時間内に取得される場合に適しています。このポリシーは、実用上はより柔軟性があり、多くの実際のシナリオに適用可能です。

ApproximateEpsilonTime

ApproximateEpsilonTimeポリシーは、ExactTimeのように動作しますが、指定された許容範囲(イプシロン)内のタイムスタンプの差異を許容します。これにより、厳密な同期が必要ながらも、わずかなタイムスタンプのずれが許容される場合に有効です。イプシロンの値はユーザーが定義でき、より柔軟な同期が可能になります。

LatestTime

LatestTimeポリシーは、最大9つまでのメッセージをそれぞれのレートで同期させることができる新しいタイムポリシーです。ゼロオーダーホールドを用いてアップサンプリングすることで、最新のメッセージを取得し、それらを同期させます。これは、複数のセンサーからの最新のデータを効果的に組み合わせたい場合に有効です。

おわりに

RealSenseの画像を同期してSubscribeしてcallbackを実行するサンプルを作成した。

参照するコードによって記法がバラバラなので、ROS2のドキュメントにmessage_filtersの項目を追加して記法を統一するようにしてほしい。

後日 ROS2 Iron環境でSync Policyを変更した時のcallbackの実行タイミングを調査して追記したい。

参考

Iron での新機能について

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?