FunnyWii
FunnyWii
Published on 2024-10-29 / 34 Visits
0
0

ROS2的消息发布和订阅&图像发布和订阅

ROS2的编译

colcon 是 ROS2 编译的工具。ROS2 的工作空间与 ROS1 保持一样的目录结构:

<workspace>
├── build        # 编译时自动生成,包含编译的中间文件
├── install      # 编译时自动生成,包含编译的结果:可执行文件,库文件,message 头文件等
├── log          # 编译时的日志,方便在编译失败时查找问题(ros2 新增)
└── src
	├── ros_pkg1  # 功能包1
	├── ros_pkg2
	├── ros_pkg3
    ......

需要注意的是,ROS2在执行 colcon 命令编译时,需要在workspace 目录,因为编译时自动生成的build,install,log 等文件夹会与 colcon 所在目录保持同级。

colcon 编译

  1. 编译整个工作空间

colcon build 

编译默认是选用 ninja 作为构建方式并且使用 8线程 进行代码的编译。编译的结果在install文件夹中是以 package 为单位存放的。

  1. 编译工作空间,并建立软连接,可以让launch修改后不必重新编译,如果install的文件需要拷贝到另外的机器使用,不要使用这个参数,否则会找不到源文件(Python)。

colcon build --symlink-install
  1. 编译工作空间指定pkg

colcon build --symlink-install --packages-select packages1
  1. 编译工作空间多个pkg

colcon build --symlink-install --packages-select packages1 packages2 packages3
  1. 忽略某个包

编译除了指定忽略的pkg外的其他所有pkg,同样也可以在 package 目录下创建 COLCON_IGNORE 空文件以忽略编译。

colcon build --packages-ignore packages2
  1. 清除已有的编译缓存

如果不加 --cmake-clean-cache 参数,系统如果发现编译完成后生成三个文件夹install, build, log中有相关包的信息,则会跳过这个包的重新编译。

colcon build --cmake-clean-cache 
  1. 合并安装

colcon build --merge-install

catkin_make 功能一样,将编译结果合并安装,比如头文件都放在 install/include 目录下, 库文件都放在 install/lib 文件夹下。

rosdep

rosdep 是一种依赖管理工具,可以与包和外部库一起工作。它是用于识别和安装依赖项以构建或安装包的命令行实用程序。rosdep 不是一个独立的包管理器;它是一个元包管理器,使用其对系统和依赖项的了解来查找适合在特定平台上安装的包。实际的安装是使用系统包管理器完成的。

rosdep 在编译和部署时解决开发的软件包编译及运行依赖的问题,最常见的就是解决 xxxConfig.cmake not found 的问题。

通常在构建工作空间之前调用rosdep,用于安装工作空间内包的依赖项。

  1. rosdep init

初始化远程服务器的地址。

  1. rosdep update

将 ros package 的依赖关系缓存在本地。

  1. rosdep install --from-paths src --ignore-src src -y

通过查询每一个 ros_package 下的 package.xml 文件中的值来确定要下载的依赖包并进行安装。

工作空间中的package.xmlrosdep 查找依赖集合的文件。package.xml 中依赖项列表完整且正确非常重要。

  1. <depend>

构建时和运行时都提供的依赖关系。对于C++,如果不确定,可以全部使用此标签。纯Python项目没有build阶段,改用 <exec_depend>

  1. <build_depend>

仅在构建时使用特定的依赖项,而在运行时不需要,可以使用 <build_depend> 标签。

  1. <exec_depend>

仅在运行时需要的依赖。

bloom

一个打包工具,可以将代码制作成 debrpm 包,然后安装到目标机器上进行测试和部署。

安装:

sudo apt-get install python3-bloom fakeroot

工作方式:

  1. 读取 ros package 中的 CMakeLists.txtinstall 确定安装位置。

  2. 读取 ros package 中的 package.xml 文件确定版本号及依赖项。

  3. 基于以上信息进行软件的打包。

使用参考:

cd <ros package 的目录下>
bloom-generate rosdebian --os-name <系统名字> --ros-distro <ros版本名字>
fakeroot debian/rules binary

ROS2 节点

下面是ROS Graph,ROS的Node可以向任意数量的Topic发布数据,同时可以订阅任意数量的Topic。

ros2_framework.gif

创建工作空间

如果是创建C++的工作空间

ros2 pkg create --build-type ament_cmake msg_demo --dependencies rclcpp example_interfaces

如果是创建Python的工作空间

ros2 pkg create --build-type ament_python msg_demo --dependencies rclcpp example_interfaces

其中--dependencies 可以在创建时指定,也可以手动在package.xml 添加。

当然我们也可以直接下载ROS官方的Demo...

git clone https://github.com/ros2/examples -b foxy

进入examples/rclcpp/topics 路径后,运行 colcon build

然后对构建的软件包运行测试,此时不需引用工作空间。 colcon test会确保测试在正确的环境中运行,并且可以访问它们的依赖项:

colcon test

引用工作空间:

source install/setup.bash
或者
. install/setup.bash

分别在两个终端中运行:

ros2 run examples_rclcpp_minimal_subscriber subscriber_member_function
ros2 run examples_rclcpp_minimal_publisher publisher_member_function

图像的发布和订阅

Publisher

这是一个简单的静态图像发布节点(chatgpt写的)

#include "rclcpp/rclcpp.hpp"
#include "sensor_msgs/msg/image.hpp"
#include <cv_bridge/cv_bridge.h>
#include <opencv2/opencv.hpp>

class ImagePublisher : public rclcpp::Node
{
public:
    ImagePublisher() : Node("image_publisher"), count_(0)
    {
        publisher_ = this->create_publisher<sensor_msgs::msg::Image>("image", 10);
        timer_ = this->create_wall_timer(
            std::chrono::milliseconds(100),
            std::bind(&ImagePublisher::timer_callback, this));
    }

private:
    void timer_callback()
    {
        auto message = sensor_msgs::msg::Image();
        cv::Mat image = cv::imread("/home/jetson/Documents/CV/ros2_demo/image_demo/hand-landmarks.png");
        cv::putText(image, "Frame: " + std::to_string(count_),
                    cv::Point(50, 50), cv::FONT_HERSHEY_SIMPLEX, 1, cv::Scalar(0, 255, 0), 2);
        cv_bridge::CvImage cv_image;
        cv_image.header.stamp = this->get_clock()->now();
        cv_image.header.frame_id = "camera";
        cv_image.encoding = "bgr8";
        cv_image.image = image;
        cv_image.toImageMsg(message);
        publisher_->publish(message);
        count_++;
    }
    rclcpp::TimerBase::SharedPtr timer_;
    rclcpp::Publisher<sensor_msgs::msg::Image>::SharedPtr publisher_;
    size_t count_;
};

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

Subscriber

#include "rclcpp/rclcpp.hpp"
#include "sensor_msgs/msg/image.hpp"
#include "cv_bridge/cv_bridge.h"
#include <opencv2/opencv.hpp>

class ImageSubscriberNode : public rclcpp::Node
{
public:
    ImageSubscriberNode() : Node("image_subscriber")
    {
        subscription_ = this->create_subscription<sensor_msgs::msg::Image>(
            "image", 10,
            std::bind(&ImageSubscriberNode::image_callback, this, std::placeholders::_1));
    }

private:
    void image_callback(const sensor_msgs::msg::Image::SharedPtr msg)
    {
        cv_bridge::CvImagePtr cv_ptr;
        try
        {
            cv_ptr = cv_bridge::toCvCopy(msg, sensor_msgs::image_encodings::BGR8);
        }
        catch (const cv_bridge::Exception& e)
        {
            RCLCPP_ERROR(this->get_logger(), "Could not convert from '%s' to 'bgr8'.", msg->encoding.c_str());
            return;
        }
        cv::imshow("Received Image", cv_ptr->image);
        cv::waitKey(10);
    }

    rclcpp::Subscription<sensor_msgs::msg::Image>::SharedPtr subscription_;
};

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

CMakeLists.txt

下面是图像发布和订阅的CMakeLists.txt

cmake_minimum_required(VERSION 3.5)
project(image_demo)

# Default to C99
if(NOT CMAKE_C_STANDARD)
  set(CMAKE_C_STANDARD 99)
endif()

# 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(image_transport REQUIRED)
find_package(std_msgs REQUIRED)  # 确保 std_msgs 被找到
find_package(cv_bridge REQUIRED)  # 确保 cv_bridge 被找到

set(OpenCV_DIR "/home/nvidia/opencv-4.5.4/build")
find_package(OpenCV)
message(STATUS "OpenCV library status:")
message(STATUS "    config: ${OpenCV_DIR}")
message(STATUS "    version: ${OpenCV_VERSION}")
message(STATUS "    libraries: ${OpenCV_LIBS}")
message(STATUS "    include path: ${OpenCV_INCLUDE_DIRS}")
include_directories(${OpenCV_INCLUDE_DIRS} ${CUDA_INCLUDE_DIRS})

add_executable(image_publisher src/image_publisher.cpp)
add_executable(image_subscriber src/image_subscriber.cpp)

ament_target_dependencies(image_publisher
  rclcpp
  sensor_msgs
  image_transport
  cv_bridge
  OpenCV
)
ament_target_dependencies(image_subscriber
  rclcpp
  sensor_msgs
  image_transport
  cv_bridge
  OpenCV
)

install(TARGETS
  image_publisher
  image_subscriber
  DESTINATION lib/${PROJECT_NAME})

if(BUILD_TESTING)
  find_package(ament_lint_auto REQUIRED)
  # the following line skips the linter which checks for copyrights
  # uncomment the line when a copyright and license is not present in all source files
  #set(ament_cmake_copyright_FOUND TRUE)
  # the following line skips cpplint (only works in a git repo)
  # uncomment the line when this package is not in a git repo
  #set(ament_cmake_cpplint_FOUND TRUE)
  ament_lint_auto_find_test_dependencies()
endif()

ament_package()

ROS2的ament_cmake 是基于CMake改进而来的。现在来拆解一下这个CMakeLists

第一行指定里Cmake的最低版本,第二行是功能包名称,注意应与package.xml 中保持一致。

cmake_minimum_required(VERSION 3.5)
project(image_demo)

查找依赖项,添加非ROS2功能包的依赖项时,需要将头文件路径在include_directories中写出。而依赖项为ROS2功能包时,无需此操作。

find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(sensor_msgs REQUIRED)
find_package(image_transport REQUIRED)
find_package(std_msgs REQUIRED)  # 确保 std_msgs 被找到
find_package(cv_bridge REQUIRED)  # 确保 cv_bridge 被找到

set(OpenCV_DIR "/home/nvidia/opencv-4.5.4/build")
find_package(OpenCV)
message(STATUS "OpenCV library status:")
message(STATUS "    config: ${OpenCV_DIR}")
message(STATUS "    version: ${OpenCV_VERSION}")
message(STATUS "    libraries: ${OpenCV_LIBS}")
message(STATUS "    include path: ${OpenCV_INCLUDE_DIRS}")
include_directories(${OpenCV_INCLUDE_DIRS} ${CUDA_INCLUDE_DIRS})

add_executable用于构建执行文件。

add_executable(image_publisher src/image_publisher.cpp)
add_executable(image_subscriber src/image_subscriber.cpp)

ament_target_dependencies是官方推荐的添加依赖项方式。使依赖项的库、头文件和自身依赖项被正确找到。若依赖项为ROS2功能包,最好使用ament_target_dependencies。若功能包有多个库,将一并包含。此外,ament_target_dependencies只能链接find_package()找到的包,如果是自定义的库文件,仍需要使用target_link_libraries的方式链接。

ament_target_dependencies(image_publisher
  rclcpp
  sensor_msgs
  image_transport
  cv_bridge
  OpenCV
)
ament_target_dependencies(image_subscriber
  rclcpp
  sensor_msgs
  image_transport
  cv_bridge
  OpenCV
)

install安装的是执行文件。安装路径是 image_demo/install/image_demo/lib/image_demo

install(TARGETS
  image_publisher
  image_subscriber
  DESTINATION lib/${PROJECT_NAME})

下面是编译单元测试文件

if(BUILD_TESTING)
  find_package(ament_lint_auto REQUIRED)
  # the following line skips the linter which checks for copyrights
  # uncomment the line when a copyright and license is not present in all source files
  #set(ament_cmake_copyright_FOUND TRUE)
  # the following line skips cpplint (only works in a git repo)
  # uncomment the line when this package is not in a git repo
  #set(ament_cmake_cpplint_FOUND TRUE)
  ament_lint_auto_find_test_dependencies()
endif()

项目安装通过ament_package()完成,并且每个软件包必须执行一次这个调用。ament_package()会安装package.xml文件,用ament索引注册该软件包,并安装CMake的配置文件,以便其他软件包可以用find_package()找到该软件包。由于ament_package()会从CMakeLists.txt文件中收集大量信息,因此它应该是CMakeLists.txt文件中的最后一个调用。

ament_package()

参考文章

[1] https://blog.csdn.net/u014603518/article/details/127717928

[2] https://www.ncnynl.com/archives/202407/6415.html

[3] http://fishros.org/doc/ros2/humble/Tutorials/Intermediate/Rosdep.html。。


Comment